You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2018/08/07 06:19:28 UTC
[openmeetings] branch master updated: [OPENMEETINGS-1897] fabricjs
is updated
This is an automated email from the ASF dual-hosted git repository.
solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git
The following commit(s) were added to refs/heads/master by this push:
new 7a46c9b [OPENMEETINGS-1897] fabricjs is updated
7a46c9b is described below
commit 7a46c9b73269b964f5bbfa2309d9a5816c640849
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Tue Aug 7 13:18:57 2018 +0700
[OPENMEETINGS-1897] fabricjs is updated
---
.../org/apache/openmeetings/web/room/wb/fabric.js | 41929 +++++++++++++------
1 file changed, 28328 insertions(+), 13601 deletions(-)
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js
index d60c339..aef061d 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js
@@ -1,14024 +1,28751 @@
/* Licensed MIT https://github.com/kangax/fabric.js/blob/master/LICENSE */
+/* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */
+/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
-var fabric = fabric || {
- version: "2.2.4"
-};
-
-if (typeof exports !== "undefined") {
- exports.fabric = fabric;
-} else if (typeof define === "function" && define.amd) {
- define([], function() {
- return fabric;
- });
+var fabric = fabric || { version: '2.3.4' };
+if (typeof exports !== 'undefined') {
+ exports.fabric = fabric;
}
-
-if (typeof document !== "undefined" && typeof window !== "undefined") {
- fabric.document = document;
- fabric.window = window;
-} else {
- fabric.document = require("jsdom").jsdom(decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E"), {
- features: {
- FetchExternalResources: [ "img" ]
- }
- });
- fabric.jsdomImplForWrapper = require("jsdom/lib/jsdom/living/generated/utils").implForWrapper;
- fabric.nodeCanvas = require("jsdom/lib/jsdom/utils").Canvas;
- fabric.window = fabric.document.defaultView;
- DOMParser = require("xmldom").DOMParser;
+/* _AMD_START_ */
+else if (typeof define === 'function' && define.amd) {
+ define([], function() { return fabric; });
+}
+/* _AMD_END_ */
+if (typeof document !== 'undefined' && typeof window !== 'undefined') {
+ fabric.document = document;
+ fabric.window = window;
+}
+else {
+ // assume we're running under node.js when document/window are not present
+ fabric.document = require('jsdom')
+ .jsdom(
+ decodeURIComponent('%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E'),
+ { features: {
+ FetchExternalResources: ['img']
+ }
+ });
+ fabric.jsdomImplForWrapper = require('jsdom/lib/jsdom/living/generated/utils').implForWrapper;
+ fabric.nodeCanvas = require('jsdom/lib/jsdom/utils').Canvas;
+ fabric.window = fabric.document.defaultView;
+ DOMParser = require('xmldom').DOMParser;
}
-fabric.isTouchSupported = "ontouchstart" in fabric.window;
+/**
+ * True when in environment that supports touch events
+ * @type boolean
+ */
+fabric.isTouchSupported = 'ontouchstart' in fabric.window;
-fabric.isLikelyNode = typeof Buffer !== "undefined" && typeof window === "undefined";
+/**
+ * True when in environment that's probably Node.js
+ * @type boolean
+ */
+fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
+ typeof window === 'undefined';
-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", "paint-order", "instantiated_by_use" ];
+/* _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", "paint-order",
+ "instantiated_by_use"
+];
+/* _FROM_SVG_END_ */
+/**
+ * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
+ */
fabric.DPI = 96;
+fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
+fabric.fontPaths = { };
+fabric.iMatrix = [1, 0, 0, 1, 0, 0];
+fabric.canvasModule = 'canvas';
-fabric.reNum = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)";
-
-fabric.fontPaths = {};
-
-fabric.iMatrix = [ 1, 0, 0, 1, 0, 0 ];
-
-fabric.canvasModule = "canvas";
-
+/**
+ * Pixel limit for cache canvases. 1Mpx , 4Mpx should be fine.
+ * @since 1.7.14
+ * @type Number
+ * @default
+ */
fabric.perfLimitSizeTotal = 2097152;
+/**
+ * Pixel limit for cache canvases width or height. IE fixes the maximum at 5000
+ * @since 1.7.14
+ * @type Number
+ * @default
+ */
fabric.maxCacheSideLimit = 4096;
+/**
+ * Lowest pixel limit for cache canvases, set at 256PX
+ * @since 1.7.14
+ * @type Number
+ * @default
+ */
fabric.minCacheSideLimit = 256;
-fabric.charWidthsCache = {};
+/**
+ * Cache Object for widths of chars in text rendering.
+ */
+fabric.charWidthsCache = { };
+/**
+ * if webgl is enabled and available, textureSize will determine the size
+ * of the canvas backend
+ * @since 2.0.0
+ * @type Number
+ * @default
+ */
fabric.textureSize = 2048;
+/**
+ * Enable webgl for filtering picture is available
+ * A filtering backend will be initialized, this will both take memory and
+ * time since a default 2048x2048 canvas will be created for the gl context
+ * @since 2.0.0
+ * @type Boolean
+ * @default
+ */
fabric.enableGLFiltering = true;
-fabric.devicePixelRatio = fabric.window.devicePixelRatio || fabric.window.webkitDevicePixelRatio || fabric.window.mozDevicePixelRatio || 1;
-
+/**
+ * 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;
+/**
+ * Browser-specific constant to adjust CanvasRenderingContext2D.shadowBlur value,
+ * which is unitless and not rendered equally across browsers.
+ *
+ * Values that work quite well (as of October 2017) are:
+ * - Chrome: 1.5
+ * - Edge: 1.75
+ * - Firefox: 0.9
+ * - Safari: 0.95
+ *
+ * @since 2.0.0
+ * @type Number
+ * @default 1
+ */
fabric.browserShadowBlurConstant = 1;
+/**
+ * This object contains the result of arc to beizer conversion for faster retrieving if the same arc needs to be converted again.
+ * It was an internal variable, is accessible since version 2.3.4
+ */
+fabric.arcToSegmentsCache = { };
+
+/**
+ * This object keeps the results of the boundsOfCurve calculation mapped by the joined arguments necessary to calculate it.
+ * It does speed up calculation, if you parse and add always the same paths, but in case of heavy usage of freedrawing
+ * you do not get any speed benefit and you get a big object in memory.
+ * The object was a private variable before, while now is appended to the lib so that you have access to it and you
+ * can eventually clear it.
+ * It was an internal variable, is accessible since version 2.3.4
+ */
+fabric.boundsOfCurveCache = { };
+
+/**
+ * If disabled boundsOfCurveCache is not used. For apps that make heavy usage of pencil drawing probably disabling it is better
+ * @default true
+ */
+fabric.cachesBoundsOfCurve = true;
+
fabric.initFilterBackend = function() {
- if (fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize)) {
- console.log("max texture size: " + fabric.maxTextureSize);
- return new fabric.WebglFilterBackend({
- tileSize: fabric.textureSize
- });
- } else if (fabric.Canvas2dFilterBackend) {
- return new fabric.Canvas2dFilterBackend();
- }
+ if (fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize)) {
+ console.log('max texture size: ' + fabric.maxTextureSize);
+ return (new fabric.WebglFilterBackend({ tileSize: fabric.textureSize }));
+ }
+ else if (fabric.Canvas2dFilterBackend) {
+ return (new fabric.Canvas2dFilterBackend());
+ }
};
-if (typeof document !== "undefined" && typeof window !== "undefined") {
- window.fabric = fabric;
+
+if (typeof document !== 'undefined' && typeof window !== 'undefined') {
+ // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system)
+ window.fabric = fabric;
}
+
(function() {
- 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);
- }
+
+ /**
+ * @private
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ function _removeEventListener(eventName, handler) {
+ if (!this.__eventListeners[eventName]) {
+ return;
}
- function observe(eventName, handler) {
- if (!this.__eventListeners) {
- this.__eventListeners = {};
- }
- 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;
+ var eventListener = this.__eventListeners[eventName];
+ if (handler) {
+ eventListener[eventListener.indexOf(handler)] = false;
}
- function stopObserving(eventName, handler) {
- if (!this.__eventListeners) {
- return;
- }
- if (arguments.length === 0) {
- for (eventName in this.__eventListeners) {
- _removeEventListener.call(this, eventName);
- }
- } 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;
+ else {
+ fabric.util.array.fill(eventListener, false);
}
- 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;
+ }
+
+ /**
+ * 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 = { };
}
- fabric.Observable = {
- observe: observe,
- stopObserving: stopObserving,
- fire: fire,
- on: observe,
- off: stopObserving,
- trigger: fire
- };
+ // 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: [],
- 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.requestRenderAll();
- return this;
+
+ _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
+ * Use of this function is highly discouraged for groups.
+ * you can add a bunch of objects with the add method but then you NEED
+ * to run a addWithUpdate call for the Group class or position/bbox will be wrong.
+ * @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.requestRenderAll();
+ 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
+ * Use of this function is highly discouraged for groups.
+ * you can add a bunch of objects with the insertAt method but then you NEED
+ * to run a addWithUpdate call for the Group class or position/bbox will be wrong.
+ * @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.requestRenderAll();
+ 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.requestRenderAll();
+ return this;
+ },
+
+ /**
+ * Executes given function for each object in this group
+ * @param {Function} callback
+ * Callback invoked with current object as first argument,
+ * index - as second and an array of all objects - as third.
+ * Callback is invoked in a context of Global Object (e.g. `window`)
+ * when no `context` argument is given
+ *
+ * @param {Object} context Context (aka thisObject)
+ * @return {Self} thisArg
+ * @chainable
+ */
+ forEachObject: function(callback, context) {
+ var objects = this.getObjects();
+ for (var i = 0, len = objects.length; i < len; i++) {
+ callback.call(context, objects[i], i, objects);
+ }
+ return this;
+ },
+
+ /**
+ * Returns an array of children objects of this instance
+ * Type parameter introduced in 1.3.10
+ * @param {String} [type] When specified, only objects of this type are returned
+ * @return {Array}
+ */
+ getObjects: function(type) {
+ if (typeof type === 'undefined') {
+ return this._objects;
+ }
+ return this._objects.filter(function(o) {
+ return o.type === type;
+ });
+ },
+
+ /**
+ * Returns object at specified index
+ * @param {Number} index
+ * @return {Self} thisArg
+ */
+ item: function (index) {
+ return this.getObjects()[index];
+ },
+
+ /**
+ * Returns true if collection contains no objects
+ * @return {Boolean} true if collection is empty
+ */
+ isEmpty: function () {
+ return this.getObjects().length === 0;
+ },
+
+ /**
+ * Returns a size of a collection (i.e: length of an array containing its objects)
+ * @return {Number} Collection size
+ */
+ size: function() {
+ return this.getObjects().length;
+ },
+
+ /**
+ * Returns true if collection contains an object
+ * @param {Object} object Object to check against
+ * @return {Boolean} `true` if collection contains an object
+ */
+ contains: function(object) {
+ return this.getObjects().indexOf(object) > -1;
+ },
+
+ /**
+ * Returns number representation of a collection complexity
+ * @return {Number} complexity
+ */
+ complexity: function () {
+ return this.getObjects().reduce(function (memo, current) {
+ memo += current.complexity ? current.complexity() : 0;
+ return memo;
+ }, 0);
+ }
+};
+
+
+/**
+ * @namespace fabric.CommonMethods
+ */
+fabric.CommonMethods = {
+
+ /**
+ * Sets object's properties from options
+ * @param {Object} [options] Options object
+ */
+ _setOptions: function(options) {
+ for (var prop in options) {
+ this.set(prop, options[prop]);
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [filler] Options object
+ * @param {String} [property] property to set the Gradient to
+ */
+ _initGradient: function(filler, property) {
+ if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) {
+ this.set(property, new fabric.Gradient(filler));
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [filler] Options object
+ * @param {String} [property] property to set the Pattern to
+ * @param {Function} [callback] callback to invoke after pattern load
+ */
+ _initPattern: function(filler, property, callback) {
+ if (filler && filler.source && !(filler instanceof fabric.Pattern)) {
+ this.set(property, new fabric.Pattern(filler, callback));
+ }
+ else {
+ callback && callback();
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [options] Options object
+ */
+ _initClipping: function(options) {
+ if (!options.clipTo || typeof options.clipTo !== 'string') {
+ return;
+ }
+
+ var functionBody = fabric.util.getFunctionBody(options.clipTo);
+ if (typeof functionBody !== 'undefined') {
+ this.clipTo = new Function('ctx', functionBody);
+ }
+ },
+
+ /**
+ * @private
+ */
+ _setObject: function(obj) {
+ for (var prop in obj) {
+ this._set(prop, obj[prop]);
+ }
+ },
+
+ /**
+ * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.
+ * @param {String|Object} key Property name or object (if object, iterate over the object properties)
+ * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ set: function(key, value) {
+ if (typeof key === 'object') {
+ this._setObject(key);
+ }
+ else {
+ if (typeof value === 'function' && key !== 'clipTo') {
+ this._set(key, value(this.get(key)));
+ }
+ else {
+ this._set(key, value);
+ }
+ }
+ return this;
+ },
+
+ _set: function(key, value) {
+ this[key] = value;
+ },
+
+ /**
+ * Toggles specified property from `true` to `false` or from `false` to `true`
+ * @param {String} property Property to toggle
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ toggle: function(property) {
+ var value = this.get(property);
+ if (typeof value === 'boolean') {
+ this.set(property, !value);
+ }
+ return this;
+ },
+
+ /**
+ * Basic getter
+ * @param {String} property Property name
+ * @return {*} value of a property
+ */
+ get: function(property) {
+ return this[property];
+ }
+};
+
+
+(function(global) {
+
+ var sqrt = Math.sqrt,
+ atan2 = Math.atan2,
+ pow = Math.pow,
+ abs = Math.abs,
+ PiBy180 = Math.PI / 180,
+ PiBy2 = Math.PI / 2;
+
+ /**
+ * @namespace fabric.util
+ */
+ fabric.util = {
+
+ /**
+ * Calculate the cos of an angle, avoiding returning floats for known results
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} angle the angle in radians or in degree
+ * @return {Number}
+ */
+ cos: function(angle) {
+ if (angle === 0) { return 1; }
+ if (angle < 0) {
+ // cos(a) = cos(-a)
+ angle = -angle;
+ }
+ var angleSlice = angle / PiBy2;
+ switch (angleSlice) {
+ case 1: case 3: return 0;
+ case 2: return -1;
+ }
+ return Math.cos(angle);
},
- 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.requestRenderAll();
- return this;
+
+ /**
+ * Calculate the sin of an angle, avoiding returning floats for known results
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} angle the angle in radians or in degree
+ * @return {Number}
+ */
+ sin: function(angle) {
+ if (angle === 0) { return 0; }
+ var angleSlice = angle / PiBy2, sign = 1;
+ if (angle < 0) {
+ // sin(-a) = -sin(a)
+ sign = -1;
+ }
+ switch (angleSlice) {
+ case 1: return sign;
+ case 2: return 0;
+ case 3: return -sign;
+ }
+ return Math.sin(angle);
},
- remove: function() {
- var objects = this.getObjects(), index, somethingRemoved = false;
- for (var i = 0, length = arguments.length; i < length; i++) {
- index = objects.indexOf(arguments[i]);
- if (index !== -1) {
- somethingRemoved = true;
- objects.splice(index, 1);
- this._onObjectRemoved && this._onObjectRemoved(arguments[i]);
- }
- }
- this.renderOnAddRemove && somethingRemoved && this.requestRenderAll();
- return this;
+
+ /**
+ * 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;
},
- 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 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;
},
- getObjects: function(type) {
- if (typeof type === "undefined") {
- return this._objects;
- }
- return this._objects.filter(function(o) {
- return o.type === type;
- });
+
+ /**
+ * 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;
},
- item: function(index) {
- return this.getObjects()[index];
+
+ /**
+ * 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;
},
- isEmpty: function() {
- return this.getObjects().length === 0;
+
+ /**
+ * 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);
},
- size: function() {
- return this.getObjects().length;
+
+ /**
+ * 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 = fabric.util.sin(radians),
+ cos = fabric.util.cos(radians),
+ rx = vector.x * cos - vector.y * sin,
+ ry = vector.x * sin + vector.y * cos;
+ return {
+ x: rx,
+ y: ry
+ };
},
- contains: function(object) {
- return this.getObjects().indexOf(object) > -1;
+
+ /**
+ * 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]
+ );
},
- complexity: function() {
- return this.getObjects().reduce(function(memo, current) {
- memo += current.complexity ? current.complexity() : 0;
- return memo;
- }, 0);
- }
-};
-fabric.CommonMethods = {
- _setOptions: function(options) {
- for (var prop in options) {
- this.set(prop, options[prop]);
- }
+ /**
+ * 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 = maxX - minX,
+ 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 = maxY - minY;
+
+ return {
+ left: minX,
+ top: minY,
+ width: width,
+ height: height
+ };
},
- _initGradient: function(filler, property) {
- if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) {
- this.set(property, new fabric.Gradient(filler));
- }
+
+ /**
+ * 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;
},
- _initPattern: function(filler, property, callback) {
- if (filler && filler.source && !(filler instanceof fabric.Pattern)) {
- this.set(property, new fabric.Pattern(filler, callback));
- } else {
- callback && callback();
- }
+
+ /**
+ * 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));
},
- _initClipping: function(options) {
- if (!options.clipTo || typeof options.clipTo !== "string") {
- return;
- }
- var functionBody = fabric.util.getFunctionBody(options.clipTo);
- if (typeof functionBody !== "undefined") {
- this.clipTo = new Function("ctx", functionBody);
- }
+
+ /**
+ * 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 array of attributes for given svg that fabric parses
+ * @memberOf fabric.util
+ * @param {String} type Type of svg element (eg. 'circle')
+ * @return {Array} string names of supported attributes
+ */
+ getSvgAttributes: function(type) {
+ var attributes = [
+ 'instantiated_by_use',
+ 'style',
+ 'id',
+ 'class'
+ ];
+ switch (type) {
+ case 'linearGradient':
+ attributes = attributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']);
+ break;
+ case 'radialGradient':
+ attributes = attributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']);
+ break;
+ case 'stop':
+ attributes = attributes.concat(['offset', 'stop-color', 'stop-opacity']);
+ break;
+ }
+ return attributes;
+ },
+
+ /**
+ * 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 */
+ var onLoadCallback = function () {
+ callback && callback.call(context, img);
+ img = img.onload = img.onerror = null;
+ };
+
+ img.onload = onLoadCallback;
+ /** @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;
+ }
+
+ // IE10 / IE11-Fix: SVG contents from data: URI
+ // will only be available if the IMG is present
+ // in the DOM (and visible)
+ if (url.substring(0,14) === 'data:image/svg') {
+ img.onload = null;
+ fabric.util.loadImageInDom(img, onLoadCallback);
+ }
+
+ img.src = url;
},
- _setObject: function(obj) {
- for (var prop in obj) {
- this._set(prop, obj[prop]);
+
+ /**
+ * Attaches SVG image with data: URL to the dom
+ * @memberOf fabric.util
+ * @param {Object} img Image object with data:image/svg src
+ * @param {Function} callback Callback; invoked with loaded image
+ * @return {Object} DOM element (div containing the SVG image)
+ */
+ loadImageInDom: function(img, onLoadCallback) {
+ var div = fabric.document.createElement('div');
+ div.style.width = div.style.height = '1px';
+ div.style.left = div.style.top = '-100%';
+ div.style.position = 'absolute';
+ div.appendChild(img);
+ fabric.document.querySelector('body').appendChild(div);
+ /**
+ * Wrap in function to:
+ * 1. Call existing callback
+ * 2. Cleanup DOM
+ */
+ img.onload = function () {
+ onLoadCallback();
+ div.parentNode.removeChild(div);
+ div = null;
+ };
+ },
+
+ /**
+ * 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);
+ klass.fromObject(o, function (obj, error) {
+ error || (enlivenedObjects[index] = obj);
+ reviver && reviver(o, obj, error);
+ onLoaded();
+ });
+ });
},
- set: function(key, value) {
- if (typeof key === "object") {
- this._setObject(key);
- } else {
- if (typeof value === "function" && key !== "clipTo") {
- this._set(key, value(this.get(key)));
- } else {
- this._set(key, value);
- }
+
+ /**
+ * Create and wait for loading of patterns
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} patterns Objects to enliven
+ * @param {Function} callback Callback to invoke when all objects are created
+ * called after each fabric object created.
+ */
+ enlivenPatterns: function(patterns, callback) {
+ patterns = patterns || [];
+
+ function onLoaded() {
+ if (++numLoadedPatterns === numPatterns) {
+ callback && callback(enlivenedPatterns);
}
- return this;
+ }
+
+ var enlivenedPatterns = [],
+ numLoadedPatterns = 0,
+ numPatterns = patterns.length;
+
+ if (!numPatterns) {
+ callback && callback(enlivenedPatterns);
+ return;
+ }
+
+ patterns.forEach(function (p, index) {
+ if (p && p.source) {
+ new fabric.Pattern(p, function(pattern) {
+ enlivenedPatterns[index] = pattern;
+ onLoaded();
+ });
+ }
+ else {
+ enlivenedPatterns[index] = p;
+ onLoaded();
+ }
+ });
},
- _set: function(key, value) {
- this[key] = value;
+
+ /**
+ * 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.Group}
+ */
+ groupSVGElements: function(elements, options, path) {
+ var object;
+ if (elements.length === 1) {
+ return elements[0];
+ }
+ if (options) {
+ if (options.width && options.height) {
+ options.centerPoint = {
+ x: options.width / 2,
+ y: options.height / 2
+ };
+ }
+ else {
+ delete options.width;
+ delete options.height;
+ }
+ }
+ object = new fabric.Group(elements, options);
+ if (typeof path !== 'undefined') {
+ object.sourcePath = path;
+ }
+ return object;
},
- toggle: function(property) {
- var value = this.get(property);
- if (typeof value === "boolean") {
- this.set(property, !value);
- }
- return this;
+
+ /**
+ * 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 Properties 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]];
+ }
+ }
+ }
},
- get: function(property) {
- return this[property];
- }
-};
-(function(global) {
- var sqrt = Math.sqrt, atan2 = Math.atan2, pow = Math.pow, abs = Math.abs, PiBy180 = Math.PI / 180, PiBy2 = Math.PI / 2;
- fabric.util = {
- cos: function(angle) {
- if (angle === 0) {
- return 1;
- }
- if (angle < 0) {
- angle = -angle;
- }
- var angleSlice = angle / PiBy2;
- switch (angleSlice) {
- case 1:
- case 3:
- return 0;
-
- case 2:
- return -1;
- }
- return Math.cos(angle);
- },
- sin: function(angle) {
- if (angle === 0) {
- return 0;
- }
- var angleSlice = angle / PiBy2, sign = 1;
- if (angle < 0) {
- sign = -1;
- }
- switch (angleSlice) {
- case 1:
- return sign;
+ /**
+ * Draws a dashed line between two points
+ *
+ * This method is used to draw dashed line around selection area.
+ * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
+ *
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x start x coordinate
+ * @param {Number} y start y coordinate
+ * @param {Number} x2 end x coordinate
+ * @param {Number} y2 end y coordinate
+ * @param {Array} da dash array pattern
+ */
+ drawDashedLine: function(ctx, x, y, x2, y2, da) {
+ var dx = x2 - x,
+ dy = y2 - y,
+ len = sqrt(dx * dx + dy * dy),
+ rot = atan2(dy, dx),
+ dc = da.length,
+ di = 0,
+ draw = true;
- case 2:
- return 0;
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.moveTo(0, 0);
+ ctx.rotate(rot);
- case 3:
- return -sign;
- }
- return Math.sin(angle);
- },
- removeFromArray: function(array, value) {
- var idx = array.indexOf(value);
- if (idx !== -1) {
- array.splice(idx, 1);
- }
- return array;
- },
- getRandomInt: function(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- },
- degreesToRadians: function(degrees) {
- return degrees * PiBy180;
- },
- radiansToDegrees: function(radians) {
- return radians / PiBy180;
- },
- 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);
- },
- rotateVector: function(vector, radians) {
- var sin = fabric.util.sin(radians), cos = fabric.util.cos(radians), rx = vector.x * cos - vector.y * sin, ry = vector.x * sin + vector.y * cos;
- return {
- x: rx,
- y: ry
- };
- },
- 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]);
- },
- 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 = maxX - minX, 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 = maxY - minY;
- return {
- left: minX,
- top: minY,
- width: width,
- height: height
- };
- },
- 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;
- },
- toFixed: function(number, fractionDigits) {
- return parseFloat(Number(number).toFixed(fractionDigits));
- },
- 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;
+ x = 0;
+ while (len > x) {
+ x += da[di++ % dc];
+ if (x > len) {
+ x = len;
+ }
+ ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
+ draw = !draw;
+ }
- case "cm":
- return number * fabric.DPI / 2.54;
+ ctx.restore();
+ },
- case "in":
- return number * fabric.DPI;
+ /**
+ * Creates canvas element
+ * @static
+ * @memberOf fabric.util
+ * @return {CanvasElement} initialized canvas element
+ */
+ createCanvasElement: function() {
+ return fabric.document.createElement('canvas');
+ },
- case "pt":
- return number * fabric.DPI / 72;
+ /**
+ * Creates image element (works on client and node)
+ * @static
+ * @memberOf fabric.util
+ * @return {HTMLImageElement} HTML image element
+ */
+ createImage: function() {
+ return fabric.document.createElement('img');
+ },
- case "pc":
- return number * fabric.DPI / 72 * 12;
+ /**
+ * @static
+ * @memberOf fabric.util
+ * @deprecated since 2.0.0
+ * @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();
+ },
- case "em":
- return number * fontSize;
+ /**
+ * 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]
+ ];
+ },
- default:
- return number;
- }
- },
- falseFunction: function() {
- return false;
- },
- getKlass: function(type, namespace) {
- type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
- return fabric.util.resolveNamespace(namespace)[type];
- },
- getSvgAttributes: function(type) {
- var attributes = [ "instantiated_by_use", "style", "id", "class" ];
- switch (type) {
- case "linearGradient":
- attributes = attributes.concat([ "x1", "y1", "x2", "y2", "gradientUnits", "gradientTransform" ]);
- break;
-
- case "radialGradient":
- attributes = attributes.concat([ "gradientUnits", "gradientTransform", "cx", "cy", "r", "fx", "fy", "fr" ]);
- break;
-
- case "stop":
- attributes = attributes.concat([ "offset", "stop-color", "stop-opacity" ]);
- break;
- }
- return attributes;
- },
- 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;
- },
- loadImage: function(url, callback, context, crossOrigin) {
- if (!url) {
- callback && callback.call(context, url);
- return;
- }
- var img = fabric.util.createImage();
- var onLoadCallback = function() {
- callback && callback.call(context, img);
- img = img.onload = img.onerror = null;
- };
- img.onload = onLoadCallback;
- img.onerror = function() {
- fabric.log("Error loading " + img.src);
- callback && callback.call(context, null, true);
- img = img.onload = img.onerror = null;
- };
- if (url.indexOf("data") !== 0 && crossOrigin) {
- img.crossOrigin = crossOrigin;
- }
- if (url.substring(0, 14) === "data:image/svg") {
- img.onload = null;
- fabric.util.loadImageInDom(img, onLoadCallback);
- }
- img.src = url;
- },
- loadImageInDom: function(img, onLoadCallback) {
- var div = fabric.document.createElement("div");
- div.style.width = div.style.height = "1px";
- div.style.left = div.style.top = "-100%";
- div.style.position = "absolute";
- div.appendChild(img);
- fabric.document.querySelector("body").appendChild(div);
- img.onload = function() {
- onLoadCallback();
- div.parentNode.removeChild(div);
- div = null;
- };
- },
- 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 (!o || !o.type) {
- onLoaded();
- return;
- }
- var klass = fabric.util.getKlass(o.type, namespace);
- klass.fromObject(o, function(obj, error) {
- error || (enlivenedObjects[index] = obj);
- reviver && reviver(o, obj, error);
- onLoaded();
- });
- });
- },
- enlivenPatterns: function(patterns, callback) {
- patterns = patterns || [];
- function onLoaded() {
- if (++numLoadedPatterns === numPatterns) {
- callback && callback(enlivenedPatterns);
- }
- }
- var enlivenedPatterns = [], numLoadedPatterns = 0, numPatterns = patterns.length;
- if (!numPatterns) {
- callback && callback(enlivenedPatterns);
- return;
- }
- patterns.forEach(function(p, index) {
- if (p && p.source) {
- new fabric.Pattern(p, function(pattern) {
- enlivenedPatterns[index] = pattern;
- onLoaded();
- });
- } else {
- enlivenedPatterns[index] = p;
- onLoaded();
- }
- });
- },
- groupSVGElements: function(elements, options, path) {
- var object;
- if (elements.length === 1) {
- return elements[0];
- }
- if (options) {
- if (options.width && options.height) {
- options.centerPoint = {
- x: options.width / 2,
- y: options.height / 2
- };
- } else {
- delete options.width;
- delete options.height;
- }
- }
- object = new fabric.Group(elements, options);
- if (typeof path !== "undefined") {
- object.sourcePath = path;
- }
- return object;
- },
- 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]];
- }
- }
- }
- },
- 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();
- },
- createCanvasElement: function() {
- return fabric.document.createElement("canvas");
- },
- createImage: function() {
- return fabric.document.createElement("img");
- },
- clipContext: function(receiver, ctx) {
- ctx.save();
- ctx.beginPath();
- receiver.clipTo(ctx);
- ctx.clip();
- },
- multiplyTransformMatrices: function(a, b, is2x2) {
- 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] ];
- },
- 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.rotate(0);
- },
- getFunctionBody: function(fn) {
- return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
- },
- isTransparent: function(ctx, x, y, tolerance) {
- 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;
- for (i = 3; i < l; i += 4) {
- temp = imageData.data[i];
- _isTransparent = temp <= 0;
- if (_isTransparent === false) {
- break;
- }
- }
- imageData = null;
- return _isTransparent;
- },
- 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();
- }
- }
- alignX = align !== "none" ? align.slice(1, 4) : "none";
- alignY = align !== "none" ? align.slice(5, 8) : "none";
- return {
- meetOrSlice: meetOrSlice,
- alignX: alignX,
- alignY: alignY
- };
- },
- clearFabricFontCache: function(fontFamily) {
- if (!fontFamily) {
- fabric.charWidthsCache = {};
- } else if (fabric.charWidthsCache[fontFamily]) {
- delete fabric.charWidthsCache[fontFamily];
- }
- },
- limitDimsByArea: function(ar, maximumArea) {
- var roughWidth = Math.sqrt(maximumArea * ar), perfLimitSizeY = Math.floor(maximumArea / roughWidth);
- return {
- x: Math.floor(roughWidth),
- y: perfLimitSizeY
- };
- },
- capValue: function(min, value, max) {
- return Math.max(min, Math.min(value, max));
- },
- findScaleToFit: function(source, destination) {
- return Math.min(destination.width / source.width, destination.height / source.height);
- },
- findScaleToCover: function(source, destination) {
- return Math.max(destination.width / source.width, destination.height / source.height);
- }
- };
-})(typeof exports !== "undefined" ? exports : this);
+ /**
+ * 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]
+ };
+ },
-(function() {
- var arcToSegmentsCache = {}, segmentToBezierCache = {}, boundsOfCurveCache = {}, _join = Array.prototype.join;
- 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 = fabric.util.sin(th), cosTh = fabric.util.cos(th), fromX = 0, fromY = 0;
- rx = Math.abs(rx);
- ry = Math.abs(ry);
- var px = -cosTh * toX * .5 - sinTh * toY * .5, py = -cosTh * toY * .5 + sinTh * toX * .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 : 1) * Math.sqrt(pl / (rx2 * py2 + ry2 * px2));
- }
- var cx = root * rx * py / ry, cy = -root * ry * px / rx, cx1 = cosTh * cx - sinTh * cy + toX * .5, cy1 = sinTh * cx + cosTh * cy + toY * .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;
- }
- 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 = fabric.util.cos(th2), sinth2 = fabric.util.sin(th2), costh3 = fabric.util.cos(th3), sinth3 = fabric.util.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 [...]
- segmentToBezierCache[argsString2] = [ cp1X, cp1Y, cp2X, cp2Y, toX, toY ];
- return segmentToBezierCache[argsString2];
- }
- 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);
- }
- }
- 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]);
+ 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);
+ },
+
+ /**
+ * reset an object transform state to neutral. Top and left are not accounted for
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Object} target object to transform
+ */
+ resetObjectTransform: function (target) {
+ target.scaleX = 1;
+ target.scaleY = 1;
+ target.skewX = 0;
+ target.skewY = 0;
+ target.flipX = false;
+ target.flipY = false;
+ target.rotate(0);
+ },
+
+ /**
+ * Extract Object transform values
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Object} target object to read from
+ * @return {Object} Components of transform
+ */
+ saveObjectTransform: function (target) {
+ return {
+ scaleX: target.scaleX,
+ scaleY: target.scaleY,
+ skewX: target.skewX,
+ skewY: target.skewY,
+ angle: target.angle,
+ left: target.left,
+ flipX: target.flipX,
+ flipY: target.flipY,
+ top: target.top
+ };
+ },
+
+ /**
+ * 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;
}
- };
- 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;
- };
- 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);
- }
+ else {
+ x = 0;
}
- 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;
-})();
+ 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 the given font family or all the cache if no
+ * fontFamily is specified.
+ * Use it if you know you are loading fonts in a lazy way and you are not waiting
+ * for custom fonts to load properly when adding text objects to the canvas.
+ * If a text object is added when its own font is not loaded yet, you will get wrong
+ * measurement and so wrong bounding boxes.
+ * After the font cache is cleared, either change the textObject text content or call
+ * initDimensions() to trigger a recalculation
+ * @memberOf fabric.util
+ * @param {String} [fontFamily] font family to clear
+ */
+ clearFabricFontCache: function(fontFamily) {
+ fontFamily = (fontFamily || '').toLowerCase();
+ if (!fontFamily) {
+ fabric.charWidthsCache = { };
+ }
+ else if (fabric.charWidthsCache[fontFamily]) {
+ delete fabric.charWidthsCache[fontFamily];
+ }
+ },
+
+ /**
+ * Given current aspect ratio, determines the max width and height that can
+ * respect the total allowed area for the cache.
+ * @memberOf fabric.util
+ * @param {Number} ar aspect ratio
+ * @param {Number} maximumArea Maximum area you want to achieve
+ * @return {Object.x} Limited dimensions by X
+ * @return {Object.y} Limited dimensions by Y
+ */
+ limitDimsByArea: function(ar, maximumArea) {
+ var roughWidth = Math.sqrt(maximumArea * ar),
+ perfLimitSizeY = Math.floor(maximumArea / roughWidth);
+ return { x: Math.floor(roughWidth), y: perfLimitSizeY };
+ },
+
+ capValue: function(min, value, max) {
+ return Math.max(min, Math.min(value, max));
+ },
+
+ findScaleToFit: function(source, destination) {
+ return Math.min(destination.width / source.width, destination.height / source.height);
+ },
+
+ findScaleToCover: function(source, destination) {
+ return Math.max(destination.width / source.width, destination.height / source.height);
+ }
+ };
+})(typeof exports !== 'undefined' ? exports : this);
+
(function() {
- var slice = Array.prototype.slice;
- 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;
+
+ var _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 (fabric.arcToSegmentsCache[argsString]) {
+ return fabric.arcToSegmentsCache[argsString];
}
- function max(array, byProperty) {
- return find(array, byProperty, function(value1, value2) {
- return value1 >= value2;
- });
+
+ var PI = Math.PI, th = rotateX * PI / 180,
+ sinTh = fabric.util.sin(th),
+ cosTh = fabric.util.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;
}
- function min(array, byProperty) {
- return find(array, byProperty, function(value1, value2) {
- return value1 < value2;
- });
+ else {
+ root = (large === sweep ? -1.0 : 1.0) *
+ Math.sqrt( pl / (rx2 * py2 + ry2 * px2));
}
- function fill(array, value) {
- var k = array.length;
- while (k--) {
- array[k] = value;
- }
- return array;
+
+ 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;
}
- 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;
+ else if (sweep === 1 && dtheta < 0) {
+ dtheta += 2 * PI;
}
- fabric.util.array = {
- fill: fill,
- invoke: invoke,
- min: min,
- max: max
- };
-})();
-(function() {
- function extend(destination, source, deep) {
- if (deep) {
- if (!fabric.isLikelyNode && source instanceof Element) {
- destination = source;
- } else if (source instanceof Array) {
- destination = [];
- for (var i = 0, len = source.length; i < len; i++) {
- destination[i] = extend({}, source[i], deep);
- }
- } else if (source && typeof source === "object") {
- for (var property in source) {
- if (source.hasOwnProperty(property)) {
- destination[property] = extend({}, source[property], deep);
- }
- }
- } else {
- destination = source;
- }
- } else {
- for (var property in source) {
- destination[property] = source[property];
- }
- }
- return destination;
+ // 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;
+ }
+ fabric.arcToSegmentsCache[argsString] = result;
+ return result;
+ }
+
+ function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
+ var costh2 = fabric.util.cos(th2),
+ sinth2 = fabric.util.sin(th2),
+ costh3 = fabric.util.cos(th3),
+ sinth3 = fabric.util.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);
+
+ return [
+ cp1X, cp1Y,
+ cp2X, cp2Y,
+ toX, toY
+ ];
+ }
+
+ /*
+ * Private
+ */
+ function calcVectorAngle(ux, uy, vx, vy) {
+ var ta = Math.atan2(uy, ux),
+ tb = Math.atan2(vy, vx);
+ if (tb >= ta) {
+ return tb - ta;
}
- function clone(object, deep) {
- return extend({}, object, deep);
+ else {
+ return 2 * Math.PI - (ta - tb);
}
- fabric.util.object = {
- extend: extend,
- clone: clone
- };
- fabric.util.object.extend(fabric.util, fabric.Observable);
+ }
+
+ /**
+ * 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;
+ if (fabric.cachesBoundsOfCurve) {
+ argsString = _join.call(arguments);
+ if (fabric.boundsOfCurveCache[argsString]) {
+ return fabric.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])
+ }
+ ];
+ if (fabric.cachesBoundsOfCurve) {
+ fabric.boundsOfCurveCache[argsString] = result;
+ }
+ return result;
+ }
+
+ fabric.util.getBoundsOfCurve = getBoundsOfCurve;
+
})();
+
(function() {
- function camelize(string) {
- return string.replace(/-+(.)?/g, function(match, character) {
- return character ? character.toUpperCase() : "";
- });
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * 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]);
}
- function capitalize(string, firstLetterOnly) {
- return string.charAt(0).toUpperCase() + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
+ 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;
}
- function escapeXml(string) {
- return string.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
+ return array;
+ }
+
+ /**
+ * @private
+ */
+ function find(array, byProperty, condition) {
+ if (!array || array.length === 0) {
+ return;
}
- function graphemeSplit(textstring) {
- var i = 0, chr, graphemes = [];
- for (i = 0, chr; i < textstring.length; i++) {
- if ((chr = getWholeChar(textstring, i)) === false) {
- continue;
- }
- graphemes.push(chr);
- }
- return graphemes;
+
+ 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];
+ }
+ }
}
- function getWholeChar(str, i) {
- var code = str.charCodeAt(i);
- if (isNaN(code)) {
- return "";
- }
- if (code < 55296 || code > 57343) {
- return str.charAt(i);
- }
- if (55296 <= code && code <= 56319) {
- if (str.length <= i + 1) {
- throw "High surrogate without following low surrogate";
- }
- var next = str.charCodeAt(i + 1);
- if (56320 > next || next > 57343) {
- throw "High surrogate without following low surrogate";
- }
- return str.charAt(i) + str.charAt(i + 1);
- }
- if (i === 0) {
- throw "Low surrogate without preceding high surrogate";
- }
- var prev = str.charCodeAt(i - 1);
- if (55296 > prev || prev > 56319) {
- throw "Low surrogate without preceding high surrogate";
+ else {
+ while (i--) {
+ if (condition(array[i], result)) {
+ result = array[i];
}
- return false;
+ }
}
- fabric.util.string = {
- camelize: camelize,
- capitalize: capitalize,
- escapeXml: escapeXml,
- graphemeSplit: graphemeSplit
- };
+ return result;
+ }
+
+ /**
+ * @namespace fabric.util.array
+ */
+ fabric.util.array = {
+ fill: fill,
+ invoke: invoke,
+ min: min,
+ max: max
+ };
+
})();
+
(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;
- }(), addMethods = function(klass, source, parent) {
+ /**
+ * Copies all enumerable properties of one js object to another
+ * Does not clone or extend fabric.Object subclasses.
+ * @memberOf fabric.util.object
+ * @param {Object} destination Where to copy to
+ * @param {Object} source Where to copy from
+ * @return {Object}
+ */
+
+ function extend(destination, source, deep) {
+ // JScript DontEnum bug is not taken care of
+ // the deep clone is for internal use, is not meant to avoid
+ // javascript traps or cloning html element or self referenced objects.
+ if (deep) {
+ if (!fabric.isLikelyNode && source instanceof Element) {
+ // avoid cloning deep images, canvases,
+ destination = source;
+ }
+ else if (source instanceof Array) {
+ destination = [];
+ for (var i = 0, len = source.length; i < len; i++) {
+ destination[i] = extend({ }, source[i], deep);
+ }
+ }
+ else if (source && typeof source === 'object') {
for (var property in source) {
- if (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 parentMethod = null, _this = this;
- while (_this.constructor.superclass) {
- var superClassMethod = _this.constructor.superclass.prototype[methodName];
- if (_this[methodName] !== superClassMethod) {
- parentMethod = superClassMethod;
- break;
- }
- _this = _this.constructor.superclass.prototype;
- }
- if (!parentMethod) {
- return console.log("tried to callSuper " + methodName + ", method not found in prototype chain", this);
- }
- return arguments.length > 1 ? parentMethod.apply(this, slice.call(arguments, 1)) : parentMethod.call(this);
+ if (source.hasOwnProperty(property)) {
+ destination[property] = extend({ }, source[property], deep);
+ }
+ }
+ }
+ else {
+ // this sounds odd for an extend but is ok for recursive use
+ destination = source;
+ }
}
- 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;
+ else {
+ for (var property in source) {
+ destination[property] = source[property];
+ }
}
- fabric.util.createClass = createClass;
+ return destination;
+ }
+
+ /**
+ * Creates an empty object and copies all enumerable properties of another object to it
+ * @memberOf fabric.util.object
+ * TODO: this function return an empty object if you try to clone null
+ * @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
+ };
+ fabric.util.object.extend(fabric.util, fabric.Observable);
})();
+
(function() {
- var unknown = "unknown";
- 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;
- }
- var getElement, setElement, getUniqueId = function() {
- var uid = 0;
- return function(element) {
- return element.__uniqueID || (element.__uniqueID = "uniqueID__" + uid++);
- };
- }();
- (function() {
- var elements = {};
- getElement = function(uid) {
- return elements[uid];
- };
- 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);
- };
+
+ /**
+ * Camelizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to camelize
+ * @return {String} Camelized version of a string
+ */
+ function camelize(string) {
+ return string.replace(/-+(.)?/g, function(match, character) {
+ return character ? character.toUpperCase() : '';
+ });
+ }
+
+ /**
+ * Capitalizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to capitalize
+ * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
+ * and other letters stay untouched, if false first letter is capitalized
+ * and other letters are converted to lowercase.
+ * @return {String} Capitalized version of a string
+ */
+ function capitalize(string, firstLetterOnly) {
+ return string.charAt(0).toUpperCase() +
+ (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
+ }
+
+ /**
+ * Escapes XML in a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to escape
+ * @return {String} Escaped version of a string
+ */
+ function escapeXml(string) {
+ return string.replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+ }
+
+ /**
+ * Divide a string in the user perceived single units
+ * @memberOf fabric.util.string
+ * @param {String} textstring String to escape
+ * @return {Array} array containing the graphemes
+ */
+ function graphemeSplit(textstring) {
+ var i = 0, chr, graphemes = [];
+ for (i = 0, chr; i < textstring.length; i++) {
+ if ((chr = getWholeChar(textstring, i)) === false) {
+ continue;
+ }
+ graphemes.push(chr);
}
- 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);
- }
- }
- };
+ return graphemes;
+ }
+
+ // taken from mdn in the charAt doc page.
+ function getWholeChar(str, i) {
+ var code = str.charCodeAt(i);
+
+ if (isNaN(code)) {
+ return ''; // Position not found
}
- 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"), listeners = {}, handlers = {}, addListener, removeListener;
- if (shouldUseAddListenerRemoveListener) {
- addListener = function(element, eventName, handler, options) {
- element && element.addEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
- };
- removeListener = function(element, eventName, handler, options) {
- element && element.removeEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
- };
- } else if (shouldUseAttachEventDetachEvent) {
- addListener = function(element, eventName, handler) {
- if (!element) {
- return;
- }
- 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);
- };
- removeListener = function(element, eventName, handler) {
- if (!element) {
- return;
- }
- 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 {
- addListener = function(element, eventName, handler) {
- if (!element) {
- return;
- }
- 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);
- };
- removeListener = function(element, eventName, handler) {
- if (!element) {
- return;
- }
- 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);
- }
- }
- }
- };
+ if (code < 0xD800 || code > 0xDFFF) {
+ return str.charAt(i);
}
- fabric.util.addListener = addListener;
- fabric.util.removeListener = removeListener;
- 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
- };
+
+ // High surrogate (could change last hex to 0xDB7F to treat high private
+ // surrogates as single characters)
+ if (0xD800 <= code && code <= 0xDBFF) {
+ if (str.length <= (i + 1)) {
+ throw 'High surrogate without following low surrogate';
+ }
+ var next = str.charCodeAt(i + 1);
+ if (0xDC00 > next || next > 0xDFFF) {
+ throw 'High surrogate without following low surrogate';
+ }
+ return str.charAt(i) + str.charAt(i + 1);
}
- var pointerX = function(event) {
- return event.clientX;
- }, pointerY = function(event) {
- return event.clientY;
- };
- function _getPointer(event, pageProp, clientProp) {
- var touchProp = event.type === "touchend" ? "changedTouches" : "touches";
- var pointer, eventTouchProp = event[touchProp];
- if (eventTouchProp && eventTouchProp[0]) {
- pointer = eventTouchProp[0][clientProp];
- }
- if (typeof pointer === "undefined") {
- pointer = event[clientProp];
- }
- return pointer;
+ // Low surrogate (0xDC00 <= code && code <= 0xDFFF)
+ if (i === 0) {
+ throw 'Low surrogate without preceding high surrogate';
}
- if (fabric.isTouchSupported) {
- pointerX = function(event) {
- return _getPointer(event, "pageX", "clientX");
- };
- pointerY = function(event) {
- return _getPointer(event, "pageY", "clientY");
- };
+ var prev = str.charCodeAt(i - 1);
+
+ // (could change last hex to 0xDB7F to treat high private
+ // surrogates as single characters)
+ if (0xD800 > prev || prev > 0xDBFF) {
+ throw 'Low surrogate without preceding high surrogate';
}
- fabric.util.getPointer = getPointer;
+ // We can pass over low surrogates now as the second component
+ // in a pair which we have already processed
+ return false;
+ }
+
+
+ /**
+ * String utilities
+ * @namespace fabric.util.string
+ */
+ fabric.util.string = {
+ camelize: camelize,
+ capitalize: capitalize,
+ escapeXml: escapeXml,
+ graphemeSplit: graphemeSplit
+ };
})();
+
(function() {
- 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];
- }
+
+ var slice = Array.prototype.slice, emptyFunction = function() { },
+
+ IS_DONTENUM_BUGGY = (function() {
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') {
+ return false;
+ }
}
- 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*([^\)]+)\)/, setOpacity = function(element) {
- return element;
- };
- if (supportsOpacity) {
- setOpacity = function(element, value) {
- element.style.opacity = value;
- return element;
- };
- } else if (supportsFilters) {
- setOpacity = function(element, value) {
- var es = element.style;
- if (element.currentStyle && !element.currentStyle.hasLayout) {
- es.zoom = 1;
+ 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 (reOpacity.test(es.filter)) {
- value = value >= .9999 ? "" : "alpha(opacity=" + value * 100 + ")";
- es.filter = es.filter.replace(reOpacity, value);
- } else {
- es.filter += " alpha(opacity=" + value * 100 + ")";
+ if (source.valueOf !== Object.prototype.valueOf) {
+ klass.prototype.valueOf = source.valueOf;
}
- return element;
- };
+ }
+ }
+ };
+
+ function Subclass() { }
+
+ function callSuper(methodName) {
+ var parentMethod = null,
+ _this = this;
+
+ // climb prototype chain to find method not equal to callee's method
+ while (_this.constructor.superclass) {
+ var superClassMethod = _this.constructor.superclass.prototype[methodName];
+ if (_this[methodName] !== superClassMethod) {
+ parentMethod = superClassMethod;
+ break;
+ }
+ // eslint-disable-next-line
+ _this = _this.constructor.superclass.prototype;
}
- fabric.util.setStyle = setStyle;
-})();
-(function() {
- var _slice = Array.prototype.slice;
- function getById(id) {
- return typeof id === "string" ? fabric.document.getElementById(id) : id;
+ if (!parentMethod) {
+ return console.log('tried to callSuper ' + methodName + ', method not found in prototype chain', this);
}
- var sliceCanConvertNodelists, 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;
- };
+
+ return (arguments.length > 1)
+ ? parentMethod.apply(this, slice.call(arguments, 1))
+ : parentMethod.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 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;
+ function klass() {
+ this.initialize.apply(this, arguments);
}
- function addClass(element, className) {
- if (element && (" " + element.className + " ").indexOf(" " + className + " ") === -1) {
- element.className += (element.className ? " " : "") + className;
- }
+
+ klass.superclass = parent;
+ klass.subclasses = [];
+
+ if (parent) {
+ Subclass.prototype = parent.prototype;
+ klass.prototype = new Subclass();
+ parent.subclasses.push(klass);
}
- 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;
+ for (var i = 0, length = properties.length; i < length; i++) {
+ addMethods(klass, properties[i], parent);
}
- function getScrollLeftTop(element) {
- var left = 0, top = 0, docElement = fabric.document.documentElement, body = fabric.document.body || {
- scrollLeft: 0,
- scrollTop: 0
- };
- while (element && (element.parentNode || element.host)) {
- 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 && element.style.position === "fixed") {
- break;
- }
- }
- return {
- left: left,
- top: top
- };
+ if (!klass.prototype.initialize) {
+ klass.prototype.initialize = emptyFunction;
}
- 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
- };
+ 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;
+ }
}
- 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;
+ return true;
+ }
+
+ /** @ignore */
+ var getElement,
+ setElement,
+ getUniqueId = (function () {
+ var uid = 0;
+ return function (element) {
+ return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
};
+ })();
+
+ (function () {
+ var elements = { };
+ /** @ignore */
+ getElement = function (uid) {
+ return elements[uid];
+ };
+ /** @ignore */
+ setElement = function (uid, element) {
+ elements[uid] = element;
+ };
+ })();
+
+ function createListener(uid, handler) {
+ return {
+ handler: handler,
+ wrappedHandler: createWrappedHandler(uid, handler)
+ };
+ }
+
+ function createWrappedHandler(uid, handler) {
+ return function (e) {
+ handler.call(getElement(uid), e || fabric.window.event);
+ };
+ }
+
+ function createDispatcher(uid, eventName) {
+ return function (e) {
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ handlersForEvent[i].call(this, e || fabric.window.event);
+ }
+ }
+ };
+ }
+
+ var shouldUseAddListenerRemoveListener = (
+ areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
+ areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
+
+ shouldUseAttachEventDetachEvent = (
+ areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
+ areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
+
+ // IE branch
+ listeners = { },
+
+ // DOM L0 branch
+ handlers = { },
+
+ addListener, removeListener;
+
+ if (shouldUseAddListenerRemoveListener) {
+ /** @ignore */
+ addListener = function (element, eventName, handler, options) {
+ // since ie10 or ie9 can use addEventListener but they do not support options, i need to check
+ element && element.addEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler, options) {
+ element && element.removeEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
+ };
+ }
+
+ else if (shouldUseAttachEventDetachEvent) {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ if (!element) {
+ return;
+ }
+ 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) {
+ if (!element) {
+ return;
+ }
+ 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) {
+ if (!element) {
+ return;
+ }
+ 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) {
+ if (!element) {
+ return;
+ }
+ 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) {
+ return event.clientX;
+ },
+
+ pointerY = function(event) {
+ return event.clientY;
+ };
+
+ function _getPointer(event, pageProp, clientProp) {
+ var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
+ var pointer, eventTouchProp = event[touchProp];
+
+ if (eventTouchProp && eventTouchProp[0]) {
+ pointer = eventTouchProp[0][clientProp];
}
- (function() {
- var style = fabric.document.documentElement.style, selectProp = "userSelect" in style ? "userSelect" : "MozUserSelect" in style ? "MozUserSelect" : "WebkitUserSelect" in style ? "WebkitUserSelect" : "KhtmlUserSelect" in style ? "KhtmlUserSelect" : "";
- 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;
- }
- 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() {
- function getScript(url, callback) {
- var headEl = fabric.document.getElementsByTagName("head")[0], scriptEl = fabric.document.createElement("script"), loading = true;
- scriptEl.onload = 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);
- }
- fabric.util.getScript = getScript;
- })();
- function getNodeCanvas(element) {
- var impl = fabric.jsdomImplForWrapper(element);
- return impl._canvas || impl._image;
- }
- 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;
- fabric.util.getNodeCanvas = getNodeCanvas;
+
+ if (typeof pointer === 'undefined') {
+ pointer = event[clientProp];
+ }
+
+ return pointer;
+ }
+
+ if (fabric.isTouchSupported) {
+ pointerX = function(event) {
+ return _getPointer(event, 'pageX', 'clientX');
+ };
+ pointerY = function(event) {
+ return _getPointer(event, 'pageY', 'clientY');
+ };
+ }
+
+ fabric.util.getPointer = getPointer;
+
+})();
+
+
+(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() {
- 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() {}
- 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;
- xhr.onreadystatechange = function() {
- if (xhr.readyState === 4) {
- onComplete(xhr);
- xhr.onreadystatechange = emptyFn;
- }
+
+ 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
};
- 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;
+
+ // 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 && element.style.position === 'fixed') {
+ break;
+ }
}
- fabric.util.request = request;
-})();
-fabric.log = function() {};
+ return { left: left, top: top };
+ }
-fabric.warn = function() {};
+ /**
+ * 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 (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);
- };
- }
- });
-}
+ if (!doc) {
+ return offset;
+ }
-(function() {
- function noop() {
- return false;
+ for (var attr in offsetAttributes) {
+ offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
}
- function animate(options) {
- requestAnimFrame(function(timestamp) {
- options || (options = {});
- var start = timestamp || +new Date(), duration = options.duration || 500, finish = start + duration, time, onChange = options.onChange || noop, abort = options.abort || noop, onComplete = options.onComplete || noop, 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) {
- if (abort()) {
- onComplete(endValue, 1, 1);
- return;
- }
- time = ticktime || +new Date();
- var currentTime = time > finish ? duration : time - start, timePerc = currentTime / duration, current = easing(currentTime, startValue, byValue, duration), valuePerc = Math.abs((current - startValue) / byValue);
- onChange(current, valuePerc, timePerc);
- if (time > finish) {
- options.onComplete && options.onComplete();
- return;
- }
- requestAnimFrame(tick);
- })(start);
- });
+
+ docElem = doc.documentElement;
+ if ( typeof element.getBoundingClientRect !== 'undefined' ) {
+ box = element.getBoundingClientRect();
}
- var _requestAnimFrame = fabric.window.requestAnimationFrame || fabric.window.webkitRequestAnimationFrame || fabric.window.mozRequestAnimationFrame || fabric.window.oRequestAnimationFrame || fabric.window.msRequestAnimationFrame || function(callback) {
- return fabric.window.setTimeout(callback, 1e3 / 60);
+
+ 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;
};
- var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout;
- function requestAnimFrame() {
- return _requestAnimFrame.apply(fabric.window, arguments);
+ }
+ 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;
+ })();
+
+ function getNodeCanvas(element) {
+ var impl = fabric.jsdomImplForWrapper(element);
+ return impl._canvas || impl._image;
+ };
+
+ function cleanUpJsdomNode(element) {
+ if (!fabric.isLikelyNode) {
+ return;
}
- function cancelAnimFrame() {
- return _cancelAnimFrame.apply(fabric.window, arguments);
+ var impl = fabric.jsdomImplForWrapper(element);
+ if (impl) {
+ impl._image = null;
+ impl._canvas = null;
+ // unsure if necessary
+ impl._currentSrc = null;
+ impl._attributes = null;
+ impl._classList = null;
}
- fabric.util.animate = animate;
- fabric.util.requestAnimFrame = requestAnimFrame;
- fabric.util.cancelAnimFrame = cancelAnimFrame;
+ }
+
+ 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;
+ fabric.util.getNodeCanvas = getNodeCanvas;
+ fabric.util.cleanUpJsdomNode = cleanUpJsdomNode;
+
})();
+
(function() {
- function calculateColor(begin, end, pos) {
- var color = "rgba(" + parseInt(begin[0] + pos * (end[0] - begin[0]), 10) + "," + parseInt(begin[1] + pos * (end[1] - begin[1]), 10) + "," + parseInt(begin[2] + pos * (end[2] - begin[2]), 10);
- color += "," + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
- color += ")";
- return color;
- }
- function animateColor(fromColor, toColor, duration, options) {
- var startColor = new fabric.Color(fromColor).getSource(), endColor = new fabric.Color(toColor).getSource();
- options = options || {};
- fabric.util.animate(fabric.util.object.extend(options, {
- duration: duration || 500,
- startValue: startColor,
- endValue: endColor,
- byValue: endColor,
- easing: function(currentTime, startValue, byValue, duration) {
- var posValue = options.colorEasing ? options.colorEasing(currentTime, duration) : 1 - Math.cos(currentTime / duration * (Math.PI / 2));
- return calculateColor(startValue, byValue, posValue);
- }
- }));
+
+ function addParamToUrl(url, param) {
+ return url + (/\?/.test(url) ? '&' : '?') + param;
+ }
+
+ var makeXHR = (function() {
+ var factories = [
+ function() { return new fabric.window.XMLHttpRequest(); },
+ function() { return new ActiveXObject('Microsoft.XMLHTTP'); },
+ function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
+ function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }
+ ];
+ 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);
+ }
}
- fabric.util.animateColor = animateColor;
+
+ 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() {
- function normalize(a, c, p, s) {
- if (a < Math.abs(c)) {
- a = c;
- s = p / 4;
- } else {
- 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 noop() {
+ return false;
+ }
+
+ /**
+ * 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 || noop,
+ abort = options.abort || noop,
+ onComplete = options.onComplete || noop,
+ 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) {
+ if (abort()) {
+ onComplete(endValue, 1, 1);
+ return;
+ }
+ time = ticktime || +new Date();
+ var currentTime = time > finish ? duration : (time - start),
+ timePerc = currentTime / duration,
+ current = easing(currentTime, startValue, byValue, duration),
+ valuePerc = Math.abs((current - startValue) / byValue);
+ onChange(current, valuePerc, timePerc);
+ 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) {
+ return fabric.window.setTimeout(callback, 1000 / 60);
+ };
+
+ var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout;
+
+ /**
+ * 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);
+ }
+
+ function cancelAnimFrame() {
+ return _cancelAnimFrame.apply(fabric.window, arguments);
+ }
+
+ fabric.util.animate = animate;
+ fabric.util.requestAnimFrame = requestAnimFrame;
+ fabric.util.cancelAnimFrame = cancelAnimFrame;
+})();
+
+
+(function() {
+ // Calculate an in-between color. Returns a "rgba()" string.
+ // Credit: Edwin Martin <ed...@bitstorm.org>
+ // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js
+ function calculateColor(begin, end, pos) {
+ var color = 'rgba('
+ + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ','
+ + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ','
+ + parseInt((begin[2] + pos * (end[2] - begin[2])), 10);
+
+ color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
+ color += ')';
+ return color;
+ }
+
+ /**
+ * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed.
+ * @memberOf fabric.util
+ * @param {String} fromColor The starting color in hex or rgb(a) format.
+ * @param {String} toColor The starting color in hex or rgb(a) format.
+ * @param {Number} [duration] Duration of change (in ms).
+ * @param {Object} [options] Animation options
+ * @param {Function} [options.onChange] Callback; invoked on every value change
+ * @param {Function} [options.onComplete] Callback; invoked when value change is completed
+ * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
+ */
+ function animateColor(fromColor, toColor, duration, options) {
+ var startColor = new fabric.Color(fromColor).getSource(),
+ endColor = new fabric.Color(toColor).getSource();
+
+ options = options || {};
+
+ fabric.util.animate(fabric.util.object.extend(options, {
+ duration: duration || 500,
+ startValue: startColor,
+ endValue: endColor,
+ byValue: endColor,
+ easing: function (currentTime, startValue, byValue, duration) {
+ var posValue = options.colorEasing
+ ? options.colorEasing(currentTime, duration)
+ : 1 - Math.cos(currentTime / duration * (Math.PI / 2));
+ return calculateColor(startValue, byValue, posValue);
+ }
+ }));
+ }
+
+ fabric.util.animateColor = animateColor;
+
+})();
+
+
+(function() {
+
+ function normalize(a, c, p, s) {
+ if (a < Math.abs(c)) {
+ a = c;
+ s = p / 4;
}
- 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);
+ 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);
+ }
}
- function easeOutCubic(t, b, c, d) {
- return c * ((t = t / d - 1) * t * t + 1) + b;
+ 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;
}
- 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;
+ 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;
}
- function easeInQuart(t, b, c, d) {
- return c * (t /= d) * 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;
}
- function easeOutQuart(t, b, c, d) {
- return -c * ((t = t / d - 1) * t * t * t - 1) + 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;
}
- 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;
+ if (t === d) {
+ return b + c;
}
- function easeInQuint(t, b, c, d) {
- return c * (t /= d) * t * t * t * t + b;
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
}
- function easeOutQuint(t, b, c, d) {
- return c * ((t = t / d - 1) * t * t * t * 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;
}
- 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;
+ 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;
}
- function easeInSine(t, b, c, d) {
- return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
+ t /= d;
+ if (t === 1) {
+ return b + c;
}
- function easeOutSine(t, b, c, d) {
- return c * Math.sin(t / d * (Math.PI / 2)) + b;
+ if (!p) {
+ p = d * 0.3;
}
- function easeInOutSine(t, b, c, d) {
- return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
+ 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;
}
- function easeInExpo(t, b, c, d) {
- return t === 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
+ t /= d;
+ if (t === 1) {
+ return b + c;
}
- function easeOutExpo(t, b, c, d) {
- return t === d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
+ if (!p) {
+ p = d * 0.3;
}
- 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;
+ 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;
}
- function easeInCirc(t, b, c, d) {
- return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
+ t /= d / 2;
+ if (t === 2) {
+ return b + c;
}
- function easeOutCirc(t, b, c, d) {
- return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
+ if (!p) {
+ p = d * (0.3 * 1.5);
}
- 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;
+ var opts = normalize(a, c, p, s);
+ if (t < 1) {
+ return -0.5 * elastic(opts, t, d) + b;
}
- 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 * .3;
- }
- var opts = normalize(a, c, p, s);
- return -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;
}
- 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 * .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;
+ 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;
}
- 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 * (.3 * 1.5);
- }
- var opts = normalize(a, c, p, s);
- if (t < 1) {
- return -.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) * .5 + opts.c + b;
+ 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;
}
- function easeInBack(t, b, c, d, s) {
- if (s === undefined) {
- s = 1.70158;
- }
- return c * (t /= d) * t * ((s + 1) * t - s) + b;
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
}
- 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;
+ 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;
}
- 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;
+ else if (t < (2 / 2.75)) {
+ return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
}
- function easeInBounce(t, b, c, d) {
- return c - easeOutBounce(d - t, 0, c, d) + b;
+ else if (t < (2.5 / 2.75)) {
+ return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
}
- 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 + .75) + b;
- } else if (t < 2.5 / 2.75) {
- return c * (7.5625 * (t -= 2.25 / 2.75) * t + .9375) + b;
- } else {
- return c * (7.5625 * (t -= 2.625 / 2.75) * t + .984375) + b;
- }
+ else {
+ return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
}
- function easeInOutBounce(t, b, c, d) {
- if (t < d / 2) {
- return easeInBounce(t * 2, 0, c, d) * .5 + b;
- }
- return easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + 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;
}
- fabric.util.ease = {
- easeInQuad: function(t, b, c, d) {
- return c * (t /= d) * t + b;
- },
- easeOutQuad: function(t, b, c, d) {
- return -c * (t /= d) * (t - 2) + b;
- },
- 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;
- },
- 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
- };
+ return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
+ }
+
+ /**
+ * Easing functions
+ * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a>
+ * @namespace fabric.util.ease
+ */
+ fabric.util.ease = {
+
+ /**
+ * Quadratic easing in
+ * @memberOf fabric.util.ease
+ */
+ easeInQuad: function(t, b, c, d) {
+ return c * (t /= d) * t + b;
+ },
+
+ /**
+ * Quadratic easing out
+ * @memberOf fabric.util.ease
+ */
+ easeOutQuad: function(t, b, c, d) {
+ return -c * (t /= d) * (t - 2) + b;
+ },
+
+ /**
+ * Quadratic easing in and out
+ * @memberOf fabric.util.ease
+ */
+ easeInOutQuad: function(t, b, c, d) {
+ t /= (d / 2);
+ if (t < 1) {
+ return c / 2 * t * t + b;
+ }
+ return -c / 2 * ((--t) * (t - 2) - 1) + b;
+ },
+
+ /**
+ * Cubic easing in
+ * @memberOf fabric.util.ease
+ */
+ easeInCubic: function(t, b, c, d) {
+ return c * (t /= d) * t * t + b;
+ },
+
+ easeOutCubic: easeOutCubic,
+ easeInOutCubic: easeInOutCubic,
+ easeInQuart: easeInQuart,
+ easeOutQuart: easeOutQuart,
+ easeInOutQuart: easeInOutQuart,
+ easeInQuint: easeInQuint,
+ easeOutQuint: easeOutQuint,
+ easeInOutQuint: easeInOutQuint,
+ easeInSine: easeInSine,
+ easeOutSine: easeOutSine,
+ easeInOutSine: easeInOutSine,
+ easeInExpo: easeInExpo,
+ easeOutExpo: easeOutExpo,
+ easeInOutExpo: easeInOutExpo,
+ easeInCirc: easeInCirc,
+ easeOutCirc: easeOutCirc,
+ easeInOutCirc: easeInOutCirc,
+ easeInElastic: easeInElastic,
+ easeOutElastic: easeOutElastic,
+ easeInOutElastic: easeInOutElastic,
+ easeInBack: easeInBack,
+ easeOutBack: easeOutBack,
+ easeInOutBack: easeInOutBack,
+ easeInBounce: easeInBounce,
+ easeOutBounce: easeOutBounce,
+ easeInOutBounce: easeInOutBounce
+ };
+
})();
+
(function(global) {
- "use strict";
- var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, parseUnit = fabric.util.parseUnit, multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, svgValidTagNames = [ "path", "circle", "polygon", "polyline", "ellipse", "rect", "line", "image", "text", "linearGradient", "radialGradient", "stop" ], svgViewBoxElements = [ "symbol", "image", "marker", "pattern", "view", "svg" ], s [...]
- 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",
- "letter-spacing": "charSpacing",
- "paint-order": "paintFirst",
- "stroke-dasharray": "strokeDashArray",
- "stroke-linecap": "strokeLineCap",
- "stroke-linejoin": "strokeLineJoin",
- "stroke-miterlimit": "strokeMiterLimit",
- "stroke-opacity": "strokeOpacity",
- "stroke-width": "strokeWidth",
- "text-decoration": "textDecoration",
- "text-anchor": "textAnchor",
- opacity: "opacity"
- }, colorAttributes = {
- stroke: "strokeOpacity",
- fill: "fillOpacity"
- };
- fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames);
- fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements);
- fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors);
- fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents);
- fabric.cssRules = {};
- fabric.gradientDefs = {};
- function normalizeAttr(attr) {
- if (attr in attributesMap) {
- return attributesMap[attr];
- }
- return attr;
- }
- function normalizeValue(attr, value, parentAttributes, fontSize) {
- var isArray = Object.prototype.toString.call(value) === "[object Array]", parsed;
- if ((attr === "fill" || attr === "stroke") && value === "none") {
- value = "";
- } else if (attr === "strokeDashArray") {
- if (value === "none") {
- value = null;
- } else {
- value = value.replace(/,/g, " ").split(/\s+/).map(function(n) {
- return parseFloat(n);
- });
- }
- } else if (attr === "transformMatrix") {
- if (parentAttributes && parentAttributes.transformMatrix) {
- value = multiplyTransformMatrices(parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
- } else {
- value = fabric.parseTransformAttribute(value);
- }
- } else if (attr === "visible") {
- value = value !== "none" && value !== "hidden";
- if (parentAttributes && parentAttributes.visible === false) {
- value = false;
- }
- } else if (attr === "opacity") {
- value = parseFloat(value);
- if (parentAttributes && typeof parentAttributes.opacity !== "undefined") {
- value *= parentAttributes.opacity;
- }
- } else if (attr === "textAnchor") {
- value = value === "start" ? "left" : value === "end" ? "right" : "center";
- } else if (attr === "charSpacing") {
- parsed = parseUnit(value, fontSize) / fontSize * 1e3;
- } else if (attr === "paintFirst") {
- var fillIndex = value.indexOf("fill");
- var strokeIndex = value.indexOf("stroke");
- var value = "fill";
- if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) {
- value = "stroke";
- } else if (fillIndex === -1 && strokeIndex > -1) {
- value = "stroke";
- }
- } else {
- parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
- }
- return !isArray && isNaN(parsed) ? value : parsed;
+
+ 'use strict';
+
+ /**
+ * @name fabric
+ * @namespace
+ */
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ clone = fabric.util.object.clone,
+ toFixed = fabric.util.toFixed,
+ parseUnit = fabric.util.parseUnit,
+ multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
+
+ svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line',
+ 'image', 'text', 'linearGradient', 'radialGradient', 'stop'],
+ svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'],
+ svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'],
+ svgValidParents = ['symbol', 'g', 'a', 'svg'],
+
+ 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',
+ 'letter-spacing': 'charSpacing',
+ 'paint-order': 'paintFirst',
+ 'stroke-dasharray': 'strokeDashArray',
+ 'stroke-linecap': 'strokeLineCap',
+ 'stroke-linejoin': 'strokeLineJoin',
+ 'stroke-miterlimit': 'strokeMiterLimit',
+ 'stroke-opacity': 'strokeOpacity',
+ 'stroke-width': 'strokeWidth',
+ 'text-decoration': 'textDecoration',
+ 'text-anchor': 'textAnchor',
+ opacity: 'opacity'
+ },
+
+ colorAttributes = {
+ stroke: 'strokeOpacity',
+ fill: 'fillOpacity'
+ };
+
+ fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames);
+ fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements);
+ fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors);
+ fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents);
+
+ fabric.cssRules = { };
+ fabric.gradientDefs = { };
+
+ function normalizeAttr(attr) {
+ // transform attribute names
+ if (attr in attributesMap) {
+ return attributesMap[attr];
}
- function getSvgRegex(arr) {
- return new RegExp("^(" + arr.join("|") + ")\\b", "i");
+ 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 = '';
}
- 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;
- }
- function _getMultipleNodes(doc, nodeNames) {
- var nodeName, nodeArray = [], nodeList, i, len;
- for (i = 0, len = nodeNames.length; i < len; i++) {
- nodeName = nodeNames[i];
- nodeList = doc.getElementsByTagName(nodeName);
- nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
- }
- return nodeArray;
- }
- fabric.parseTransformAttribute = function() {
- function rotateMatrix(matrix, args) {
- var cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]), x = 0, y = 0;
- if (args.length === 3) {
- x = args[1];
- y = args[2];
- }
- matrix[0] = cos;
- matrix[1] = sin;
- matrix[2] = -sin;
- matrix[3] = cos;
- matrix[4] = x - (cos * x - sin * y);
- matrix[5] = y - (sin * x + cos * y);
- }
- function scaleMatrix(matrix, args) {
- var multiplierX = args[0], multiplierY = args.length === 2 ? args[1] : args[0];
- matrix[0] = multiplierX;
- matrix[3] = multiplierY;
- }
- function skewMatrix(matrix, args, pos) {
- matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0]));
- }
- function translateMatrix(matrix, args) {
- matrix[4] = args[0];
- if (args.length === 2) {
- matrix[5] = args[1];
- }
- }
- var iMatrix = [ 1, 0, 0, 1, 0, 0 ], 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 + ") [...]
- return function(attributeValue) {
- var matrix = iMatrix.concat(), matrices = [];
- if (!attributeValue || attributeValue && !reTransformList.test(attributeValue)) {
- return matrix;
- }
- attributeValue.replace(reTransform, function(match) {
- var m = new RegExp(transform).exec(match).filter(function(match) {
- return !!match;
- }), 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":
- skewMatrix(matrix, args, 2);
- break;
-
- case "skewY":
- skewMatrix(matrix, args, 1);
- break;
-
- case "matrix":
- matrix = args;
- break;
- }
- matrices.push(matrix.concat());
- matrix = iMatrix.concat();
- });
- var combinedMatrix = matrices[0];
- while (matrices.length > 1) {
- matrices.shift();
- combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
- }
- return combinedMatrix;
- };
- }();
- function parseStyleString(style, oStyle) {
- var attr, value;
- style.replace(/;\s*$/, "").split(";").forEach(function(chunk) {
- var pair = chunk.split(":");
- attr = pair[0].trim().toLowerCase();
- value = pair[1].trim();
- oStyle[attr] = value;
+ else if (attr === 'strokeDashArray') {
+ if (value === 'none') {
+ value = null;
+ }
+ else {
+ value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
+ return parseFloat(n);
});
+ }
}
- function parseStyleObject(style, oStyle) {
- var attr, value;
- for (var prop in style) {
- if (typeof style[prop] === "undefined") {
- continue;
- }
- attr = prop.toLowerCase();
- value = style[prop];
- oStyle[attr] = value;
- }
+ else if (attr === 'transformMatrix') {
+ if (parentAttributes && parentAttributes.transformMatrix) {
+ value = multiplyTransformMatrices(
+ parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
+ }
+ else {
+ value = fabric.parseTransformAttribute(value);
+ }
}
- 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;
+ else if (attr === 'visible') {
+ value = value !== 'none' && value !== 'hidden';
+ // display=none on parent element always takes precedence over child element
+ if (parentAttributes && parentAttributes.visible === false) {
+ value = false;
+ }
}
- function elementMatchesRule(element, selectors) {
- var firstMatching, parentMatching = true;
- firstMatching = selectorMatches(element, selectors.pop());
- if (firstMatching && selectors.length) {
- parentMatching = doesSomeParentMatch(element, selectors);
- }
- return firstMatching && parentMatching && selectors.length === 0;
+ else if (attr === 'opacity') {
+ value = parseFloat(value);
+ if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') {
+ value *= parentAttributes.opacity;
+ }
}
- 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;
- }
- function selectorMatches(element, selector) {
- var nodeName = element.nodeName, classNames = element.getAttribute("class"), id = element.getAttribute("id"), matcher, i;
- 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 (i = classNames.length; i--; ) {
- matcher = new RegExp("\\." + classNames[i] + "(?![a-zA-Z\\-]+)", "i");
- selector = selector.replace(matcher, "");
- }
- }
- return selector.length === 0;
+ else if (attr === 'textAnchor' /* text-anchor */) {
+ value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
}
- function elementById(doc, id) {
- var el;
- doc.getElementById && (el = doc.getElementById(id));
- if (el) {
- return el;
- }
- var node, i, len, nodelist = doc.getElementsByTagName("*");
- for (i = 0, len = nodelist.length; i < len; i++) {
- node = nodelist[i];
- if (id === node.getAttribute("id")) {
- return node;
- }
- }
+ else if (attr === 'charSpacing') {
+ // parseUnit returns px and we convert it to em
+ parsed = parseUnit(value, fontSize) / fontSize * 1000;
}
- 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, len;
- applyViewboxTransform(el2);
- if (/^svg$/i.test(el2.nodeName)) {
- var el3 = el2.ownerDocument.createElement("g");
- for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) {
- attr = attrs.item(j);
- el3.setAttribute(attr.nodeName, attr.nodeValue);
- }
- while (el2.firstChild) {
- el3.appendChild(el2.firstChild);
- }
- el2 = el3;
- }
- for (j = 0, attrs = el.attributes, len = attrs.length; j < len; 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);
- if (nodelist.length === oldLength) {
- i++;
- }
- }
+ else if (attr === 'paintFirst') {
+ var fillIndex = value.indexOf('fill');
+ var strokeIndex = value.indexOf('stroke');
+ var value = 'fill';
+ if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) {
+ value = 'stroke';
+ }
+ else if (fillIndex === -1 && strokeIndex > -1) {
+ value = 'stroke';
+ }
}
- var reViewBoxAttrValue = new RegExp("^" + "\\s*(" + fabric.reNum + "+)\\s*,?" + "\\s*(" + fabric.reNum + "+)\\s*,?" + "\\s*(" + fabric.reNum + "+)\\s*,?" + "\\s*(" + fabric.reNum + "+)\\s*" + "$");
- 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 || !fabric.svgViewBoxElementsRegEx.test(element.nodeName) || !(viewBoxAttr = viewBoxA [...]
- 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;
- }
- preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
- if (preserveAspectRatio.alignX !== "none") {
- 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");
- while (element.firstChild) {
- el.appendChild(element.firstChild);
- }
- element.appendChild(el);
- } else {
- el = element;
- matrix = el.getAttribute("transform") + matrix;
- }
- el.setAttribute("transform", matrix);
- return parsedDim;
- }
- 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;
+ else {
+ parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
}
- fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
- if (!doc) {
- return;
- }
- parseUseDirectives(doc);
- var svgUid = fabric.Object.__uid++, i, len, options = applyViewboxTransform(doc), descendants = fabric.util.toArray(doc.getElementsByTagName("*"));
- options.crossOrigin = parsingOptions && parsingOptions.crossOrigin;
- options.svgUid = svgUid;
- if (descendants.length === 0 && fabric.isLikelyNode) {
- descendants = doc.selectNodes('//*[name(.)!="svg"]');
- var arr = [];
- for (i = 0, len = descendants.length; i < len; i++) {
- arr[i] = descendants[i];
- }
- descendants = arr;
- }
- var elements = descendants.filter(function(el) {
- applyViewboxTransform(el);
- return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace("svg:", "")) && !hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx);
- });
- if (!elements || elements && !elements.length) {
- callback && callback([], {});
- return;
- }
- fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
- fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
- fabric.parseElements(elements, function(instances, elements) {
- if (callback) {
- callback(instances, options, elements, descendants);
- }
- }, clone(options), reviver, parsingOptions);
- };
- 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, {
- parseFontDeclaration: function(value, oStyle) {
- var match = value.match(reFontDeclaration);
- if (!match) {
- return;
- }
- var fontStyle = match[1], 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;
- }
- },
- 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;
- },
- parseAttributes: function(element, attributes, svgUid) {
- if (!element) {
- return;
- }
- var value, parentAttributes = {}, fontSize;
- if (typeof svgUid === "undefined") {
- svgUid = element.getAttribute("svgUid");
- }
- if (element.parentNode && fabric.svgValidParentsRegEx.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) {
- memo[attr] = value;
- }
- return memo;
- }, {});
- ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
- var normalizedAttr, normalizedValue, normalizedStyle = {};
- for (var attr in ownAttributes) {
- normalizedAttr = normalizeAttr(attr);
- normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize);
- normalizedStyle[normalizedAttr] = normalizedValue;
- }
- if (normalizedStyle && normalizedStyle.font) {
- fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle);
- }
- var mergedAttrs = extend(parentAttributes, normalizedStyle);
- return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs);
- },
- parseElements: function(elements, callback, options, reviver, parsingOptions) {
- new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse();
- },
- 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;
- },
- parsePointsAttribute: function(points) {
- if (!points) {
- return null;
- }
- points = points.replace(/,/g, " ").trim();
- points = points.split(/\s+/);
- var parsedPoints = [], i, len;
- for (i = 0, len = points.length; i < len; i += 2) {
- parsedPoints.push({
- x: parseFloat(points[i]),
- y: parseFloat(points[i + 1])
- });
- }
- return parsedPoints;
- },
- getCSSRules: function(doc) {
- var styles = doc.getElementsByTagName("style"), i, len, allRules = {}, rules;
- for (i = 0, len = styles.length; i < len; i++) {
- var styleContents = styles[i].textContent || styles[i].text;
- 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 (i = 0, len = propertyValuePairs.length; i < len; i++) {
- var pair = propertyValuePairs[i].split(/\s*:\s*/), property = pair[0], value = pair[1];
- 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;
- },
- loadSVGFromURL: function(url, callback, reviver, options) {
- url = url.replace(/^\n\s*/, "").trim();
- 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";
- xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ""));
- }
- if (!xml || !xml.documentElement) {
- callback && callback(null);
- }
- fabric.parseSVGDocument(xml.documentElement, function(results, _options, elements, allElements) {
- callback && callback(results, _options, elements, allElements);
- }, reviver, options);
- }
- },
- loadSVGFromString: function(string, callback, reviver, options) {
- 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";
- doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ""));
- }
- fabric.parseSVGDocument(doc.documentElement, function(results, _options, elements, allElements) {
- callback(results, _options, elements, allElements);
- }, reviver, options);
+
+ return (!isArray && isNaN(parsed) ? value : parsed);
+ }
+
+ /**
+ * @private
+ */
+ function getSvgRegex(arr) {
+ return new RegExp('^(' + arr.join('|') + ')\\b', 'i');
+ }
+
+ /**
+ * @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;
}
- });
-})(typeof exports !== "undefined" ? exports : this);
+ attributes[attr] = fabric.Object.prototype[attr];
+ }
-fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions) {
- this.elements = elements;
- this.callback = callback;
- this.options = options;
- this.reviver = reviver;
- this.svgUid = options && options.svgUid || 0;
- this.parsingOptions = parsingOptions;
- this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g;
-};
+ if (attributes[attr].indexOf('url(') === 0) {
+ continue;
+ }
-fabric.ElementsParser.prototype.parse = function() {
- this.instances = new Array(this.elements.length);
- this.numElements = this.elements.length;
- this.createObjects();
-};
+ var color = new fabric.Color(attributes[attr]);
+ attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
+ }
+ return attributes;
+ }
-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);
+ /**
+ * @private
+ */
+ function _getMultipleNodes(doc, nodeNames) {
+ var nodeName, nodeArray = [], nodeList, i, len;
+ for (i = 0, len = nodeNames.length; i < len; i++) {
+ nodeName = nodeNames[i];
+ nodeList = doc.getElementsByTagName(nodeName);
+ nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
}
-};
+ return nodeArray;
+ }
-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();
+ /**
+ * 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 cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]),
+ x = 0, y = 0;
+ if (args.length === 3) {
+ x = args[1];
+ y = args[2];
+ }
+
+ matrix[0] = cos;
+ matrix[1] = sin;
+ matrix[2] = -sin;
+ matrix[3] = cos;
+ matrix[4] = x - (cos * x - sin * y);
+ matrix[5] = y - (sin * x + cos * y);
}
-};
-fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
- klass.fromElement(el, this.createCallback(index, el), this.options);
-};
+ function scaleMatrix(matrix, args) {
+ var multiplierX = args[0],
+ multiplierY = (args.length === 2) ? args[1] : args[0];
-fabric.ElementsParser.prototype.createCallback = function(index, el) {
- var _this = this;
- return function(obj) {
- var _options;
- _this.resolveGradient(obj, "fill");
- _this.resolveGradient(obj, "stroke");
- if (obj instanceof fabric.Image) {
- _options = obj.parsePreserveAspectRatioAttribute(el);
- }
- obj._removeTransformMatrix(_options);
- _this.reviver && _this.reviver(el, obj);
- _this.instances[index] = obj;
- _this.checkIfDone();
+ matrix[0] = multiplierX;
+ matrix[3] = multiplierY;
+ }
+
+ function skewMatrix(matrix, args, pos) {
+ matrix[pos] = 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':
+ skewMatrix(matrix, args, 2);
+ break;
+ case 'skewY':
+ skewMatrix(matrix, args, 1);
+ 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;
};
-};
+ })();
-fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
- var instanceFillValue = obj[property];
- if (!/^url\(/.test(instanceFillValue)) {
- return;
+ /**
+ * @private
+ */
+ function parseStyleString(style, oStyle) {
+ var attr, value;
+ style.replace(/;\s*$/, '').split(';').forEach(function (chunk) {
+ var pair = chunk.split(':');
+
+ attr = pair[0].trim().toLowerCase();
+ value = 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 = prop.toLowerCase();
+ value = style[prop];
+
+ oStyle[attr] = value;
}
- var gradientId = this.regexUrl.exec(instanceFillValue)[1];
- this.regexUrl.lastIndex = 0;
- if (fabric.gradientDefs[this.svgUid][gradientId]) {
- obj.set(property, fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
+ }
+
+ /**
+ * @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;
+ }
-fabric.ElementsParser.prototype.checkIfDone = function() {
- if (--this.numElements === 0) {
- this.instances = this.instances.filter(function(el) {
- return el != null;
- });
- this.callback(this.instances, this.elements);
+ /**
+ * @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(global) {
- "use strict";
- var fabric = global.fabric || (global.fabric = {});
- if (fabric.Point) {
- fabric.warn("fabric.Point is already defined");
- return;
+ 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);
}
- fabric.Point = Point;
- function Point(x, y) {
- this.x = x;
- this.y = y;
+ return selectors.length === 0;
+ }
+
+ /**
+ * @private
+ */
+ function selectorMatches(element, selector) {
+ var nodeName = element.nodeName,
+ classNames = element.getAttribute('class'),
+ id = element.getAttribute('id'), matcher, i;
+ // 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, '');
}
- Point.prototype = {
- type: "point",
- constructor: Point,
- add: function(that) {
- return new Point(this.x + that.x, this.y + that.y);
- },
- addEquals: function(that) {
- this.x += that.x;
- this.y += that.y;
- return this;
- },
- scalarAdd: function(scalar) {
- return new Point(this.x + scalar, this.y + scalar);
- },
- scalarAddEquals: function(scalar) {
- this.x += scalar;
- this.y += scalar;
- return this;
- },
- subtract: function(that) {
- return new Point(this.x - that.x, this.y - that.y);
- },
- subtractEquals: function(that) {
- this.x -= that.x;
- this.y -= that.y;
- return this;
- },
- scalarSubtract: function(scalar) {
- return new Point(this.x - scalar, this.y - scalar);
- },
- scalarSubtractEquals: function(scalar) {
- this.x -= scalar;
- this.y -= scalar;
- return this;
- },
- multiply: function(scalar) {
- return new Point(this.x * scalar, this.y * scalar);
- },
- multiplyEquals: function(scalar) {
- this.x *= scalar;
- this.y *= scalar;
- return this;
- },
- divide: function(scalar) {
- return new Point(this.x / scalar, this.y / scalar);
- },
- divideEquals: function(scalar) {
- this.x /= scalar;
- this.y /= scalar;
- return this;
- },
- eq: function(that) {
- return this.x === that.x && this.y === that.y;
- },
- lt: function(that) {
- return this.x < that.x && this.y < that.y;
- },
- lte: function(that) {
- return this.x <= that.x && this.y <= that.y;
- },
- gt: function(that) {
- return this.x > that.x && this.y > that.y;
- },
- gte: function(that) {
- return this.x >= that.x && this.y >= that.y;
- },
- lerp: function(that, t) {
- if (typeof t === "undefined") {
- t = .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);
- },
- distanceFrom: function(that) {
- var dx = this.x - that.x, dy = this.y - that.y;
- return Math.sqrt(dx * dx + dy * dy);
- },
- midPointFrom: function(that) {
- return this.lerp(that);
- },
- min: function(that) {
- return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
- },
- max: function(that) {
- return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
- },
- toString: function() {
- return this.x + "," + this.y;
- },
- setXY: function(x, y) {
- this.x = x;
- this.y = y;
- return this;
- },
- setX: function(x) {
- this.x = x;
- return this;
- },
- setY: function(y) {
- this.y = y;
- return this;
- },
- setFromPoint: function(that) {
- this.x = that.x;
- this.y = that.y;
- return this;
- },
- swap: function(that) {
- var x = this.x, y = this.y;
- this.x = that.x;
- this.y = that.y;
- that.x = x;
- that.y = y;
- },
- clone: function() {
- return new Point(this.x, this.y);
- }
- };
-})(typeof exports !== "undefined" ? exports : this);
+ if (classNames && selector.length) {
+ classNames = classNames.split(' ');
+ for (i = classNames.length; i--;) {
+ matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
+ selector = selector.replace(matcher, '');
+ }
+ }
+ return selector.length === 0;
+ }
-(function(global) {
- "use strict";
- var fabric = global.fabric || (global.fabric = {});
- if (fabric.Intersection) {
- fabric.warn("fabric.Intersection is already defined");
- return;
+ /**
+ * @private
+ * to support IE8 missing getElementById on SVGdocument
+ */
+ function elementById(doc, id) {
+ var el;
+ doc.getElementById && (el = doc.getElementById(id));
+ if (el) {
+ return el;
}
- function Intersection(status) {
- this.status = status;
- this.points = [];
+ var node, i, len, nodelist = doc.getElementsByTagName('*');
+ for (i = 0, len = nodelist.length; i < len; i++) {
+ node = nodelist[i];
+ if (id === node.getAttribute('id')) {
+ return node;
+ }
}
- fabric.Intersection = Intersection;
- fabric.Intersection.prototype = {
- constructor: Intersection,
- appendPoint: function(point) {
- this.points.push(point);
- return this;
- },
- appendPoints: function(points) {
- this.points = this.points.concat(points);
- return this;
- }
- };
- 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");
- }
+ }
+
+ /**
+ * @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, len;
+
+ applyViewboxTransform(el2);
+ if (/^svg$/i.test(el2.nodeName)) {
+ var el3 = el2.ownerDocument.createElement('g');
+ for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; 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, len = attrs.length; j < len; j++) {
+ attr = attrs.item(j);
+ if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
+ continue;
}
- return result;
- };
- fabric.Intersection.intersectLinePolygon = function(a1, a2, points) {
- var result = new Intersection(), length = points.length, b1, b2, inter, i;
- for (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;
- };
- fabric.Intersection.intersectPolygonPolygon = function(points1, points2) {
- var result = new Intersection(), length = points1.length, i;
- for (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 (attr.nodeName === 'transform') {
+ currentTrans = attr.nodeValue + ' ' + currentTrans;
}
- if (result.points.length > 0) {
- result.status = "Intersection";
+ else {
+ el2.setAttribute(attr.nodeName, attr.nodeValue);
}
- return result;
- };
- 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;
- }
- function Color(color) {
- if (!color) {
- this.setSource([ 0, 0, 0, 1 ]);
- } else {
- this._tryParsingColor(color);
- }
+ 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++;
+ }
}
- fabric.Color = Color;
- fabric.Color.prototype = {
- _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) {
- source = [ 0, 0, 0, 1 ];
- }
- if (source) {
- this.setSource(source);
- }
- },
- _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;
- } else {
- var d = max - min;
- s = l > .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) ];
- },
- getSource: function() {
- return this._source;
- },
- setSource: function(source) {
- this._source = source;
- },
- toRgb: function() {
- var source = this.getSource();
- return "rgb(" + source[0] + "," + source[1] + "," + source[2] + ")";
- },
- toRgba: function() {
- var source = this.getSource();
- return "rgba(" + source[0] + "," + source[1] + "," + source[2] + "," + source[3] + ")";
- },
- toHsl: function() {
- var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]);
- return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)";
- },
- 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] + ")";
- },
- 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();
- },
- toHexa: function() {
- var source = this.getSource(), a;
- a = Math.round(source[3] * 255);
- a = a.toString(16);
- a = a.length === 1 ? "0" + a : a;
- return this.toHex() + a.toUpperCase();
- },
- getAlpha: function() {
- return this.getSource()[3];
- },
- setAlpha: function(alpha) {
- var source = this.getSource();
- source[3] = alpha;
- this.setSource(source);
- return this;
- },
- toGrayscale: function() {
- var source = this.getSource(), average = parseInt((source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), 10), currentAlpha = source[3];
- this.setSource([ average, average, average, currentAlpha ]);
- return this;
- },
- toBlackWhite: function(threshold) {
- var source = this.getSource(), average = (source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), currentAlpha = source[3];
- threshold = threshold || 127;
- average = Number(average) < Number(threshold) ? 0 : 255;
- this.setSource([ average, average, average, currentAlpha ]);
- return this;
- },
- overlayWith: function(otherColor) {
- if (!(otherColor instanceof Color)) {
- otherColor = new Color(otherColor);
- }
- var result = [], alpha = this.getAlpha(), otherAlpha = .5, source = this.getSource(), otherSource = otherColor.getSource(), i;
- for (i = 0; i < 3; i++) {
- result.push(Math.round(source[i] * (1 - otherAlpha) + otherSource[i] * otherAlpha));
- }
- result[3] = alpha;
- this.setSource(result);
- return this;
- }
- };
- 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*)?\)$/;
- fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
- fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i;
- fabric.Color.colorNameMap = {
- aliceblue: "#F0F8FF",
- antiquewhite: "#FAEBD7",
- aqua: "#00FFFF",
- aquamarine: "#7FFFD4",
- azure: "#F0FFFF",
- beige: "#F5F5DC",
- bisque: "#FFE4C4",
- black: "#000000",
- blanchedalmond: "#FFEBCD",
- blue: "#0000FF",
- blueviolet: "#8A2BE2",
- brown: "#A52A2A",
- burlywood: "#DEB887",
- cadetblue: "#5F9EA0",
- chartreuse: "#7FFF00",
- chocolate: "#D2691E",
- coral: "#FF7F50",
- cornflowerblue: "#6495ED",
- cornsilk: "#FFF8DC",
- crimson: "#DC143C",
- cyan: "#00FFFF",
- darkblue: "#00008B",
- darkcyan: "#008B8B",
- darkgoldenrod: "#B8860B",
- darkgray: "#A9A9A9",
- darkgrey: "#A9A9A9",
- darkgreen: "#006400",
- darkkhaki: "#BDB76B",
- darkmagenta: "#8B008B",
- darkolivegreen: "#556B2F",
- darkorange: "#FF8C00",
- darkorchid: "#9932CC",
- darkred: "#8B0000",
- darksalmon: "#E9967A",
- darkseagreen: "#8FBC8F",
- darkslateblue: "#483D8B",
- darkslategray: "#2F4F4F",
- darkslategrey: "#2F4F4F",
- darkturquoise: "#00CED1",
- darkviolet: "#9400D3",
- deeppink: "#FF1493",
- deepskyblue: "#00BFFF",
- dimgray: "#696969",
- dimgrey: "#696969",
- dodgerblue: "#1E90FF",
- firebrick: "#B22222",
- floralwhite: "#FFFAF0",
- forestgreen: "#228B22",
- fuchsia: "#FF00FF",
- gainsboro: "#DCDCDC",
- ghostwhite: "#F8F8FF",
- gold: "#FFD700",
- goldenrod: "#DAA520",
- gray: "#808080",
- grey: "#808080",
- green: "#008000",
- greenyellow: "#ADFF2F",
- honeydew: "#F0FFF0",
- hotpink: "#FF69B4",
- indianred: "#CD5C5C",
- indigo: "#4B0082",
- ivory: "#FFFFF0",
- khaki: "#F0E68C",
- lavender: "#E6E6FA",
- lavenderblush: "#FFF0F5",
- lawngreen: "#7CFC00",
- lemonchiffon: "#FFFACD",
- lightblue: "#ADD8E6",
- lightcoral: "#F08080",
- lightcyan: "#E0FFFF",
- lightgoldenrodyellow: "#FAFAD2",
- lightgray: "#D3D3D3",
- lightgrey: "#D3D3D3",
- lightgreen: "#90EE90",
- lightpink: "#FFB6C1",
- lightsalmon: "#FFA07A",
- lightseagreen: "#20B2AA",
- lightskyblue: "#87CEFA",
- lightslategray: "#778899",
- lightslategrey: "#778899",
- lightsteelblue: "#B0C4DE",
- lightyellow: "#FFFFE0",
- lime: "#00FF00",
- limegreen: "#32CD32",
- linen: "#FAF0E6",
- magenta: "#FF00FF",
- maroon: "#800000",
- mediumaquamarine: "#66CDAA",
- mediumblue: "#0000CD",
- mediumorchid: "#BA55D3",
- mediumpurple: "#9370DB",
- mediumseagreen: "#3CB371",
- mediumslateblue: "#7B68EE",
- mediumspringgreen: "#00FA9A",
- mediumturquoise: "#48D1CC",
- mediumvioletred: "#C71585",
- midnightblue: "#191970",
- mintcream: "#F5FFFA",
- mistyrose: "#FFE4E1",
- moccasin: "#FFE4B5",
- navajowhite: "#FFDEAD",
- navy: "#000080",
- oldlace: "#FDF5E6",
- olive: "#808000",
- olivedrab: "#6B8E23",
- orange: "#FFA500",
- orangered: "#FF4500",
- orchid: "#DA70D6",
- palegoldenrod: "#EEE8AA",
- palegreen: "#98FB98",
- paleturquoise: "#AFEEEE",
- palevioletred: "#DB7093",
- papayawhip: "#FFEFD5",
- peachpuff: "#FFDAB9",
- peru: "#CD853F",
- pink: "#FFC0CB",
- plum: "#DDA0DD",
- powderblue: "#B0E0E6",
- purple: "#800080",
- rebeccapurple: "#663399",
- red: "#FF0000",
- rosybrown: "#BC8F8F",
- royalblue: "#4169E1",
- saddlebrown: "#8B4513",
- salmon: "#FA8072",
- sandybrown: "#F4A460",
- seagreen: "#2E8B57",
- seashell: "#FFF5EE",
- sienna: "#A0522D",
- silver: "#C0C0C0",
- skyblue: "#87CEEB",
- slateblue: "#6A5ACD",
- slategray: "#708090",
- slategrey: "#708090",
- snow: "#FFFAFA",
- springgreen: "#00FF7F",
- steelblue: "#4682B4",
- tan: "#D2B48C",
- teal: "#008080",
- thistle: "#D8BFD8",
- tomato: "#FF6347",
- turquoise: "#40E0D0",
- violet: "#EE82EE",
- wheat: "#F5DEB3",
- white: "#FFFFFF",
- whitesmoke: "#F5F5F5",
- yellow: "#FFFF00",
- yellowgreen: "#9ACD32"
- };
- 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;
+ }
+
+ // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
+ // matches, e.g.: +14.56e-12, etc.
+ var reViewBoxAttrValue = new RegExp(
+ '^' +
+ '\\s*(' + fabric.reNum + '+)\\s*,?' +
+ '\\s*(' + fabric.reNum + '+)\\s*,?' +
+ '\\s*(' + fabric.reNum + '+)\\s*,?' +
+ '\\s*(' + fabric.reNum + '+)\\s*' +
+ '$'
+ );
+
+ /**
+ * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
+ */
+ function applyViewboxTransform(element) {
+
+ var viewBoxAttr = element.getAttribute('viewBox'),
+ scaleX = 1,
+ scaleY = 1,
+ minX = 0,
+ minY = 0,
+ viewBoxWidth, viewBoxHeight, matrix, el,
+ widthAttr = element.getAttribute('width'),
+ heightAttr = element.getAttribute('height'),
+ x = element.getAttribute('x') || 0,
+ y = element.getAttribute('y') || 0,
+ preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
+ missingViewBox = (!viewBoxAttr || !fabric.svgViewBoxElementsRegEx.test(element.nodeName)
+ || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
+ missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
+ toBeParsed = missingViewBox && missingDimAttr,
+ parsedDim = { }, translateMatrix = '', widthDiff = 0, heightDiff = 0;
+
+ parsedDim.width = 0;
+ parsedDim.height = 0;
+ parsedDim.toBeParsed = toBeParsed;
+
+ if (toBeParsed) {
+ return parsedDim;
}
- fabric.Color.fromRgb = function(color) {
- return Color.fromSource(Color.sourceFromRgb(color));
- };
- 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 ];
- }
- };
- fabric.Color.fromRgba = Color.fromRgb;
- fabric.Color.fromHsl = function(color) {
- return Color.fromSource(Color.sourceFromHsl(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 <= .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 ];
- };
- fabric.Color.fromHsla = Color.fromHsl;
- fabric.Color.fromHex = function(color) {
- return Color.fromSource(Color.sourceFromHex(color));
- };
- 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.subs [...]
- return [ parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), parseFloat((parseInt(a, 16) / 255).toFixed(2)) ];
- }
- };
- fabric.Color.fromSource = function(source) {
- var oColor = new Color();
- oColor.setSource(source);
- return oColor;
- };
-})(typeof exports !== "undefined" ? exports : this);
-(function() {
- function getColorStop(el) {
- var style = el.getAttribute("style"), offset = el.getAttribute("offset") || 0, color, colorAlpha, opacity, i;
- 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 (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
- };
+ if (missingViewBox) {
+ parsedDim.width = parseUnit(widthAttr);
+ parsedDim.height = parseUnit(heightAttr);
+ return parsedDim;
}
- function getLinearCoords(el) {
- return {
- x1: el.getAttribute("x1") || 0,
- y1: el.getAttribute("y1") || 0,
- x2: el.getAttribute("x2") || "100%",
- y2: el.getAttribute("y2") || 0
- };
+
+ 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;
}
- 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%"
- };
+ else {
+ parsedDim.width = viewBoxWidth;
+ parsedDim.height = viewBoxHeight;
}
- var clone = fabric.util.object.clone;
- fabric.Gradient = fabric.util.createClass({
- offsetX: 0,
- offsetY: 0,
- 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;
- },
- addColorStop: function(colorStops) {
- for (var position in colorStops) {
- var color = new fabric.Color(colorStops[position]);
- this.colorStops.push({
- offset: parseFloat(position),
- color: color.toRgb(),
- opacity: color.getAlpha()
- });
- }
- return this;
- },
- toObject: function(propertiesToInclude) {
- var object = {
- type: this.type,
- coords: this.coords,
- colorStops: this.colorStops,
- offsetX: this.offsetX,
- offsetY: this.offsetY,
- gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform
- };
- fabric.util.populateWithProperties(this, object, propertiesToInclude);
- return object;
- },
- toSVG: function(object) {
- var coords = clone(this.coords, true), i, len, markup, commonAttributes, colorStops = clone(this.colorStops, true), needsSwap = coords.r1 > coords.r2, offsetX = object.width / 2, offsetY = object.height / 2;
- colorStops.sort(function(a, b) {
- return a.offset - b.offset;
- });
- if (object.type === "path") {
- offsetX -= object.pathOffset.x;
- offsetY -= object.pathOffset.y;
- }
- for (var prop in coords) {
- if (prop === "x1" || prop === "x2") {
- coords[prop] += this.offsetX - offsetX;
- } else if (prop === "y1" || prop === "y2") {
- coords[prop] += this.offsetY - offsetY;
- }
- }
- commonAttributes = 'id="SVGID_' + this.id + '" gradientUnits="userSpaceOnUse"';
- if (this.gradientTransform) {
- commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(" ") + ')" ';
- }
- if (this.type === "linear") {
- markup = [ "<linearGradient ", commonAttributes, ' x1="', coords.x1, '" y1="', coords.y1, '" x2="', coords.x2, '" y2="', coords.y2, '">\n' ];
- } else if (this.type === "radial") {
- markup = [ "<radialGradient ", commonAttributes, ' cx="', needsSwap ? coords.x1 : coords.x2, '" cy="', needsSwap ? coords.y1 : coords.y2, '" r="', needsSwap ? coords.r1 : coords.r2, '" fx="', needsSwap ? coords.x2 : coords.x1, '" fy="', needsSwap ? coords.y2 : coords.y1, '">\n' ];
- }
- if (this.type === "radial") {
- if (needsSwap) {
- colorStops = colorStops.concat();
- colorStops.reverse();
- for (i = 0, len = colorStops.length; i < len; i++) {
- colorStops[i].offset = 1 - colorStops[i].offset;
- }
- }
- var minRadius = Math.min(coords.r1, coords.r2);
- if (minRadius > 0) {
- var maxRadius = Math.max(coords.r1, coords.r2), percentageShift = minRadius / maxRadius;
- for (i = 0, len = colorStops.length; i < len; i++) {
- colorStops[i].offset += percentageShift * (1 - colorStops[i].offset);
- }
- }
- }
- for (i = 0, len = colorStops.length; i < len; i++) {
- var colorStop = colorStops[i];
- markup.push("<stop ", 'offset="', colorStop.offset * 100 + "%", '" style="stop-color:', colorStop.color, typeof colorStop.opacity !== "undefined" ? ";stop-opacity: " + colorStop.opacity : ";", '"/>\n');
- }
- markup.push(this.type === "linear" ? "</linearGradient>\n" : "</radialGradient>\n");
- return markup.join("");
- },
- toLive: function(ctx) {
- var gradient, coords = fabric.util.object.clone(this.coords), i, len;
- if (!this.type) {
- return;
- }
- 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 (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(offset, color);
- }
- return gradient;
- }
- });
- fabric.util.object.extend(fabric.Gradient, {
- fromElement: function(el, instance) {
- var colorStopEls = el.getElementsByTagName("stop"), type, gradientUnits = el.getAttribute("gradientUnits") || "objectBoundingBox", gradientTransform = el.getAttribute("gradientTransform"), colorStops = [], coords, ellipseMatrix, i;
- 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 (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;
- },
- forObject: function(obj, options) {
- options || (options = {});
- _convertPercentUnitsToValues(obj, options.coords, "userSpaceOnUse");
- return new fabric.Gradient(options);
- }
+
+ // 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
+ if (preserveAspectRatio.meetOrSlice === 'meet') {
+ scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
+ // calculate additional translation to move the viewbox
+ }
+ if (preserveAspectRatio.meetOrSlice === 'slice') {
+ scaleY = scaleX = (scaleX > scaleY ? scaleX : scaleY);
+ // calculate additional translation to move the viewbox
+ }
+ widthDiff = parsedDim.width - viewBoxWidth * scaleX;
+ heightDiff = parsedDim.height - viewBoxHeight * scaleX;
+ if (preserveAspectRatio.alignX === 'Mid') {
+ widthDiff /= 2;
+ }
+ if (preserveAspectRatio.alignY === 'Mid') {
+ heightDiff /= 2;
+ }
+ if (preserveAspectRatio.alignX === 'Min') {
+ widthDiff = 0;
+ }
+ if (preserveAspectRatio.alignY === 'Min') {
+ heightDiff = 0;
+ }
+ }
+
+ 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 + widthDiff) + ' ' +
+ (minY * scaleY + heightDiff) + ') ';
+
+ 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * 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.
+ * @param {Object} [parsingOptions] options for parsing document
+ * @param {String} [parsingOptions.crossOrigin] crossOrigin settings
+ */
+ fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
+ if (!doc) {
+ return;
+ }
+
+ parseUseDirectives(doc);
+
+ var svgUid = fabric.Object.__uid++, i, len,
+ options = applyViewboxTransform(doc),
+ descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
+ options.crossOrigin = parsingOptions && parsingOptions.crossOrigin;
+ 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 (i = 0, len = descendants.length; i < len; i++) {
+ arr[i] = descendants[i];
+ }
+ descendants = arr;
+ }
+
+ var elements = descendants.filter(function(el) {
+ applyViewboxTransform(el);
+ return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')) &&
+ !hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx); // http://www.w3.org/TR/SVG/struct.html#DefsElement
});
- 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+\.\d+)%|(\d+)%$/.test(options[prop])) {
- multFactor = .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 (!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, elements) {
+ if (callback) {
+ callback(instances, options, elements, descendants);
+ }
+ }, clone(options), reviver, parsingOptions);
+ };
+
+ 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 && fabric.svgValidParentsRegEx.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) { // eslint-disable-line
+ 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)));
+
+ var normalizedAttr, normalizedValue, normalizedStyle = {};
+ for (var attr in ownAttributes) {
+ normalizedAttr = normalizeAttr(attr);
+ normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize);
+ normalizedStyle[normalizedAttr] = normalizedValue;
+ }
+ if (normalizedStyle && normalizedStyle.font) {
+ fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle);
+ }
+ var mergedAttrs = extend(parentAttributes, normalizedStyle);
+ return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs);
+ },
+
+ /**
+ * 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, parsingOptions) {
+ new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).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;
+
+ for (i = 0, len = points.length; 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'), i, len,
+ allRules = { }, rules;
+
+ // very crude parsing of style contents
+ for (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(); });
+ // eslint-disable-next-line no-loop-func
+ rules.forEach(function(rule) {
+
+ var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
+ ruleObj = { }, declaration = match[2].trim(),
+ propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
+
+ for (i = 0, len = propertyValuePairs.length; i < len; i++) {
+ var pair = propertyValuePairs[i].split(/\s*:\s*/),
+ property = pair[0],
+ value = pair[1];
+ 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.
+ * @param {Object} [options] Object containing options for parsing
+ * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
+ */
+ loadSVGFromURL: function(url, callback, reviver, options) {
+
+ url = url.replace(/^\n\s*/, '').trim();
+ new fabric.util.request(url, {
+ method: 'get',
+ onComplete: onComplete
+ });
+
+ function onComplete(r) {
+
+ var xml = r.responseXML;
+ if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
+ xml = new ActiveXObject('Microsoft.XMLDOM');
+ xml.async = 'false';
+ //IE chokes on DOCTYPE
+ xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
}
- if (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;
- }
+ if (!xml || !xml.documentElement) {
+ callback && callback(null);
}
- return ellipseMatrix;
+
+ fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) {
+ callback && callback(results, _options, elements, allElements);
+ }, reviver, options);
+ }
+ },
+
+ /**
+ * 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.
+ * @param {Object} [options] Object containing options for parsing
+ * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
+ */
+ loadSVGFromString: function(string, callback, reviver, options) {
+ string = string.trim();
+ var doc;
+ if (typeof DOMParser !== 'undefined') {
+ var parser = new DOMParser();
+ if (parser && parser.parseFromString) {
+ doc = parser.parseFromString(string, 'text/xml');
+ }
+ }
+ else if (fabric.window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ // IE chokes on DOCTYPE
+ doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
+ }
+
+ fabric.parseSVGDocument(doc.documentElement, function (results, _options, elements, allElements) {
+ callback(results, _options, elements, allElements);
+ }, reviver, options);
}
-})();
+ });
-(function() {
- "use strict";
- var toFixed = fabric.util.toFixed;
- fabric.Pattern = fabric.util.createClass({
- repeat: "repeat",
- offsetX: 0,
- offsetY: 0,
- crossOrigin: "",
- patternTransform: null,
- initialize: function(options, callback) {
- options || (options = {});
- this.id = fabric.Object.__uid++;
- this.setOptions(options);
- if (!options.source || options.source && typeof options.source !== "string") {
- callback && callback(this);
- return;
- }
- if (typeof fabric.util.getFunctionBody(options.source) !== "undefined") {
- this.source = new Function(fabric.util.getFunctionBody(options.source));
- callback && callback(this);
- } else {
- var _this = this;
- this.source = fabric.util.createImage();
- fabric.util.loadImage(options.source, function(img) {
- _this.source = img;
- callback && callback(_this);
- }, null, this.crossOrigin);
- }
- },
- toObject: function(propertiesToInclude) {
- var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, source, object;
- if (typeof this.source === "function") {
- source = String(this.source);
- } else if (typeof this.source.src === "string") {
- source = this.source.src;
- } else if (typeof this.source === "object" && this.source.toDataURL) {
- source = this.source.toDataURL();
- }
- object = {
- type: "pattern",
- source: source,
- repeat: this.repeat,
- crossOrigin: this.crossOrigin,
- offsetX: toFixed(this.offsetX, NUM_FRACTION_DIGITS),
- offsetY: toFixed(this.offsetY, NUM_FRACTION_DIGITS),
- patternTransform: this.patternTransform ? this.patternTransform.concat() : null
- };
- fabric.util.populateWithProperties(this, object, propertiesToInclude);
- return object;
- },
- toSVG: function(object) {
- var patternSource = typeof this.source === "function" ? this.source() : this.source, patternWidth = patternSource.width / object.width, patternHeight = patternSource.height / object.height, patternOffsetX = this.offsetX / object.width, patternOffsetY = this.offsetY / object.height, patternImgSrc = "";
- if (this.repeat === "repeat-x" || this.repeat === "no-repeat") {
- patternHeight = 1;
- }
- if (this.repeat === "repeat-y" || this.repeat === "no-repeat") {
- patternWidth = 1;
- }
- if (patternSource.src) {
- patternImgSrc = patternSource.src;
- } else if (patternSource.toDataURL) {
- patternImgSrc = patternSource.toDataURL();
- }
- return '<pattern id="SVGID_' + this.id + '" x="' + patternOffsetX + '" y="' + patternOffsetY + '" width="' + patternWidth + '" height="' + patternHeight + '">\n' + '<image x="0" y="0"' + ' width="' + patternSource.width + '" height="' + patternSource.height + '" xlink:href="' + patternImgSrc + '"></image>\n' + "</pattern>\n";
- },
- setOptions: function(options) {
- for (var prop in options) {
- this[prop] = options[prop];
- }
- },
- toLive: function(ctx) {
- var source = typeof this.source === "function" ? this.source() : this.source;
- if (!source) {
- return "";
- }
- if (typeof source.src !== "undefined") {
- if (!source.complete) {
- return "";
- }
- if (source.naturalWidth === 0 || source.naturalHeight === 0) {
- return "";
- }
- }
- return ctx.createPattern(source, this.repeat);
- }
- });
-})();
+})(typeof exports !== 'undefined' ? exports : this);
-(function(global) {
- "use strict";
- var fabric = global.fabric || (global.fabric = {}), toFixed = fabric.util.toFixed;
- if (fabric.Shadow) {
- fabric.warn("fabric.Shadow is already defined.");
- return;
+
+fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions) {
+ this.elements = elements;
+ this.callback = callback;
+ this.options = options;
+ this.reviver = reviver;
+ this.svgUid = (options && options.svgUid) || 0;
+ this.parsingOptions = parsingOptions;
+ this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g;
+};
+
+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);
}
- fabric.Shadow = fabric.util.createClass({
- color: "rgb(0,0,0)",
- blur: 0,
- offsetX: 0,
- offsetY: 0,
- affectStroke: false,
- includeDefaultValues: true,
- initialize: function(options) {
- if (typeof options === "string") {
- options = this._parseShadow(options);
- }
- for (var prop in options) {
- this[prop] = options[prop];
- }
- this.id = fabric.Object.__uid++;
- },
- _parseShadow: function(shadow) {
- var shadowStr = shadow.trim(), offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, "") || "rgb(0,0,0)";
- return {
- color: color.trim(),
- offsetX: parseInt(offsetsAndBlur[1], 10) || 0,
- offsetY: parseInt(offsetsAndBlur[2], 10) || 0,
- blur: parseInt(offsetsAndBlur[3], 10) || 0
- };
- },
- toString: function() {
- return [ this.offsetX, this.offsetY, this.blur, this.color ].join("px ");
- },
- toSVG: function(object) {
- var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, offset = fabric.util.rotateVector({
- x: this.offsetX,
- y: this.offsetY
- }, fabric.util.degreesToRadians(-object.angle)), BLUR_BOX = 20, color = new fabric.Color(this.color);
- if (object.width && object.height) {
- fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
- fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
- }
- if (object.flipX) {
- offset.x *= -1;
- }
- if (object.flipY) {
- offset.y *= -1;
- }
- return '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' + 'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + ">\n" + '\t<feGaussianBlur in="SourceAlpha" stdDeviation="' + toFixed(this.blur ? this.blur / 2 : 0, NUM_FRACTION_DIGITS) + '"></feGaussianBlur>\n' + '\t<feOffset dx="' + toFixed(offset.x, NUM_FRACTION_DIGITS) + '" dy="' + toFixed(offset.y, NUM_FRACTION_DIGITS) + '" result="oBlur" ></feOffset>\n' + '\t<feFlood flood [...]
- },
- toObject: function() {
- if (this.includeDefaultValues) {
- return {
- color: this.color,
- blur: this.blur,
- offsetX: this.offsetX,
- offsetY: this.offsetY,
- affectStroke: this.affectStroke
- };
- }
- var obj = {}, proto = fabric.Shadow.prototype;
- [ "color", "blur", "offsetX", "offsetY", "affectStroke" ].forEach(function(prop) {
- if (this[prop] !== proto[prop]) {
- obj[prop] = this[prop];
- }
- }, this);
- return obj;
- }
- });
- fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/;
-})(typeof exports !== "undefined" ? exports : this);
+ catch (err) {
+ fabric.log(err);
+ }
+ }
+ else {
+ this.checkIfDone();
+ }
+};
-(function() {
- "use strict";
- if (fabric.StaticCanvas) {
- fabric.warn("fabric.StaticCanvas is already defined.");
- return;
+fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
+ klass.fromElement(el, this.createCallback(index, el), this.options);
+};
+
+fabric.ElementsParser.prototype.createCallback = function(index, el) {
+ var _this = this;
+ return function(obj) {
+ var _options;
+ _this.resolveGradient(obj, 'fill');
+ _this.resolveGradient(obj, 'stroke');
+ if (obj instanceof fabric.Image) {
+ _options = obj.parsePreserveAspectRatioAttribute(el);
}
- var extend = fabric.util.object.extend, getElementOffset = fabric.util.getElementOffset, removeFromArray = fabric.util.removeFromArray, toFixed = fabric.util.toFixed, transformPoint = fabric.util.transformPoint, invertTransform = fabric.util.invertTransform, CANVAS_INIT_ERROR = new Error("Could not initialize `canvas` element");
- fabric.StaticCanvas = fabric.util.createClass(fabric.CommonMethods, {
- initialize: function(el, options) {
- options || (options = {});
- this.renderAndResetBound = this.renderAndReset.bind(this);
- this.requestRenderAllBound = this.requestRenderAll.bind(this);
- this._initStatic(el, options);
- },
- backgroundColor: "",
- backgroundImage: null,
- overlayColor: "",
- overlayImage: null,
- includeDefaultValues: true,
- stateful: false,
- renderOnAddRemove: true,
- clipTo: null,
- controlsAboveOverlay: false,
- allowTouchScrolling: false,
- imageSmoothingEnabled: true,
- viewportTransform: fabric.iMatrix.concat(),
- backgroundVpt: true,
- overlayVpt: true,
- onBeforeScaleRotate: function() {},
- enableRetinaScaling: true,
- vptCoords: {},
- skipOffscreen: true,
- _initStatic: function(el, options) {
- var cb = this.requestRenderAllBound;
- this._objects = [];
- this._createLowerCanvas(el);
- this._initOptions(options);
- this._setImageSmoothing();
- if (!this.interactive) {
- this._initRetinaScaling();
- }
- if (options.overlayImage) {
- this.setOverlayImage(options.overlayImage, cb);
- }
- if (options.backgroundImage) {
- this.setBackgroundImage(options.backgroundImage, cb);
- }
- if (options.backgroundColor) {
- this.setBackgroundColor(options.backgroundColor, cb);
- }
- if (options.overlayColor) {
- this.setOverlayColor(options.overlayColor, cb);
- }
- this.calcOffset();
- },
- _isRetinaScaling: function() {
- return fabric.devicePixelRatio !== 1 && this.enableRetinaScaling;
- },
- getRetinaScaling: function() {
- return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
- },
- _initRetinaScaling: function() {
- if (!this._isRetinaScaling()) {
- return;
- }
- this.lowerCanvasEl.setAttribute("width", this.width * fabric.devicePixelRatio);
- this.lowerCanvasEl.setAttribute("height", this.height * fabric.devicePixelRatio);
- this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
- },
- calcOffset: function() {
- this._offset = getElementOffset(this.lowerCanvasEl);
- return this;
- },
- setOverlayImage: function(image, callback, options) {
- return this.__setBgOverlayImage("overlayImage", image, callback, options);
- },
- setBackgroundImage: function(image, callback, options) {
- return this.__setBgOverlayImage("backgroundImage", image, callback, options);
- },
- setOverlayColor: function(overlayColor, callback) {
- return this.__setBgOverlayColor("overlayColor", overlayColor, callback);
- },
- setBackgroundColor: function(backgroundColor, callback) {
- return this.__setBgOverlayColor("backgroundColor", backgroundColor, callback);
- },
- _setImageSmoothing: function() {
- var ctx = this.getContext();
- ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled;
- ctx.imageSmoothingEnabled = this.imageSmoothingEnabled;
- },
- __setBgOverlayImage: function(property, image, callback, options) {
- if (typeof image === "string") {
- fabric.util.loadImage(image, function(img) {
- img && (this[property] = new fabric.Image(img, options));
- callback && callback(img);
- }, this, options && options.crossOrigin);
- } else {
- options && image.setOptions(options);
- this[property] = image;
- callback && callback(image);
- }
- return this;
- },
- __setBgOverlayColor: function(property, color, callback) {
- this[property] = color;
- this._initGradient(color, property);
- this._initPattern(color, property, callback);
- return this;
- },
- _createCanvasElement: function() {
- var element = fabric.util.createCanvasElement();
- if (!element) {
- throw CANVAS_INIT_ERROR;
- }
- if (!element.style) {
- element.style = {};
- }
- if (typeof element.getContext === "undefined") {
- throw CANVAS_INIT_ERROR;
- }
- return element;
- },
- _initOptions: function(options) {
- this._setOptions(options);
- this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;
- this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;
- if (!this.lowerCanvasEl.style) {
- return;
- }
- this.lowerCanvasEl.width = this.width;
- this.lowerCanvasEl.height = this.height;
- this.lowerCanvasEl.style.width = this.width + "px";
- this.lowerCanvasEl.style.height = this.height + "px";
- this.viewportTransform = this.viewportTransform.slice();
- },
- _createLowerCanvas: function(canvasEl) {
- if (canvasEl && canvasEl.getContext) {
- this.lowerCanvasEl = canvasEl;
- } else {
- this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
- }
- fabric.util.addClass(this.lowerCanvasEl, "lower-canvas");
- if (this.interactive) {
- this._applyCanvasStyle(this.lowerCanvasEl);
- }
- this.contextContainer = this.lowerCanvasEl.getContext("2d");
- },
- getWidth: function() {
- return this.width;
- },
- getHeight: function() {
- return this.height;
- },
- setWidth: function(value, options) {
- return this.setDimensions({
- width: value
- }, options);
- },
- setHeight: function(value, options) {
- return this.setDimensions({
- height: value
- }, options);
- },
- setDimensions: function(dimensions, options) {
- var cssValue;
- options = options || {};
- for (var prop in dimensions) {
- cssValue = dimensions[prop];
- if (!options.cssOnly) {
- this._setBackstoreDimension(prop, dimensions[prop]);
- cssValue += "px";
- this.hasLostContext = true;
- }
- if (!options.backstoreOnly) {
- this._setCssDimension(prop, cssValue);
- }
- }
- if (this._isCurrentlyDrawing) {
- this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles();
- }
- this._initRetinaScaling();
- this._setImageSmoothing();
- this.calcOffset();
- if (!options.cssOnly) {
- this.requestRenderAll();
- }
- return this;
- },
- _setBackstoreDimension: function(prop, value) {
- this.lowerCanvasEl[prop] = value;
- if (this.upperCanvasEl) {
- this.upperCanvasEl[prop] = value;
- }
- if (this.cacheCanvasEl) {
- this.cacheCanvasEl[prop] = value;
- }
- this[prop] = value;
- return this;
- },
- _setCssDimension: function(prop, value) {
- this.lowerCanvasEl.style[prop] = value;
- if (this.upperCanvasEl) {
- this.upperCanvasEl.style[prop] = value;
- }
- if (this.wrapperEl) {
- this.wrapperEl.style[prop] = value;
- }
- return this;
- },
- getZoom: function() {
- return this.viewportTransform[0];
- },
- setViewportTransform: function(vpt) {
- var activeObject = this._activeObject, object, ignoreVpt = false, skipAbsolute = true, i, len;
- this.viewportTransform = vpt;
- for (i = 0, len = this._objects.length; i < len; i++) {
- object = this._objects[i];
- object.group || object.setCoords(ignoreVpt, skipAbsolute);
- }
- if (activeObject && activeObject.type === "activeSelection") {
- activeObject.setCoords(ignoreVpt, skipAbsolute);
- }
- this.calcViewportBoundaries();
- this.renderOnAddRemove && this.requestRenderAll();
- return this;
- },
- zoomToPoint: function(point, value) {
- var before = point, vpt = this.viewportTransform.slice(0);
- point = transformPoint(point, invertTransform(this.viewportTransform));
- vpt[0] = value;
- vpt[3] = value;
- var after = transformPoint(point, vpt);
- vpt[4] += before.x - after.x;
- vpt[5] += before.y - after.y;
- return this.setViewportTransform(vpt);
- },
- setZoom: function(value) {
- this.zoomToPoint(new fabric.Point(0, 0), value);
- return this;
- },
- absolutePan: function(point) {
- var vpt = this.viewportTransform.slice(0);
- vpt[4] = -point.x;
- vpt[5] = -point.y;
- return this.setViewportTransform(vpt);
- },
- relativePan: function(point) {
- return this.absolutePan(new fabric.Point(-point.x - this.viewportTransform[4], -point.y - this.viewportTransform[5]));
- },
- getElement: function() {
- return this.lowerCanvasEl;
- },
- _onObjectAdded: function(obj) {
- this.stateful && obj.setupState();
- obj._set("canvas", this);
- obj.setCoords();
- this.fire("object:added", {
- target: obj
- });
- obj.fire("added");
- },
- _onObjectRemoved: function(obj) {
- this.fire("object:removed", {
- target: obj
- });
- obj.fire("removed");
- delete obj.canvas;
- },
- clearContext: function(ctx) {
- ctx.clearRect(0, 0, this.width, this.height);
- return this;
- },
- getContext: function() {
- return this.contextContainer;
- },
- clear: function() {
- this._objects.length = 0;
- this.backgroundImage = null;
- this.overlayImage = null;
- this.backgroundColor = "";
- this.overlayColor = "";
- if (this._hasITextHandlers) {
- this.off("mouse:up", this._mouseUpITextHandler);
- this._iTextInstances = null;
- this._hasITextHandlers = false;
- }
- this.clearContext(this.contextContainer);
- this.fire("canvas:cleared");
- this.renderOnAddRemove && this.requestRenderAll();
- return this;
- },
- renderAll: function() {
- var canvasToDrawOn = this.contextContainer;
- this.renderCanvas(canvasToDrawOn, this._objects);
- return this;
- },
- renderAndReset: function() {
- this.isRendering = 0;
- this.renderAll();
- },
- requestRenderAll: function() {
- if (!this.isRendering) {
- this.isRendering = fabric.util.requestAnimFrame(this.renderAndResetBound);
- }
- return this;
- },
- calcViewportBoundaries: function() {
- var points = {}, width = this.width, height = this.height, iVpt = invertTransform(this.viewportTransform);
- points.tl = transformPoint({
- x: 0,
- y: 0
- }, iVpt);
- points.br = transformPoint({
- x: width,
- y: height
- }, iVpt);
- points.tr = new fabric.Point(points.br.x, points.tl.y);
- points.bl = new fabric.Point(points.tl.x, points.br.y);
- this.vptCoords = points;
- return points;
- },
- renderCanvas: function(ctx, objects) {
- var v = this.viewportTransform;
- if (this.isRendering) {
- fabric.util.cancelAnimFrame(this.isRendering);
- this.isRendering = 0;
- }
- this.calcViewportBoundaries();
- this.clearContext(ctx);
- this.fire("before:render");
- if (this.clipTo) {
- fabric.util.clipContext(this, ctx);
- }
- this._renderBackground(ctx);
- ctx.save();
- ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
- this._renderObjects(ctx, objects);
- ctx.restore();
- if (!this.controlsAboveOverlay && this.interactive) {
- this.drawControls(ctx);
- }
- if (this.clipTo) {
- ctx.restore();
- }
- this._renderOverlay(ctx);
- if (this.controlsAboveOverlay && this.interactive) {
- this.drawControls(ctx);
- }
- this.fire("after:render");
- },
- _renderObjects: function(ctx, objects) {
- var i, len;
- for (i = 0, len = objects.length; i < len; ++i) {
- objects[i] && objects[i].render(ctx);
- }
- },
- _renderBackgroundOrOverlay: function(ctx, property) {
- var object = this[property + "Color"], v;
- if (object) {
- ctx.fillStyle = object.toLive ? object.toLive(ctx, this) : object;
- ctx.fillRect(object.offsetX || 0, object.offsetY || 0, this.width, this.height);
- }
- object = this[property + "Image"];
- if (object) {
- if (this[property + "Vpt"]) {
- v = this.viewportTransform;
- ctx.save();
- ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
- }
- object.render(ctx);
- this[property + "Vpt"] && ctx.restore();
- }
- },
- _renderBackground: function(ctx) {
- this._renderBackgroundOrOverlay(ctx, "background");
- },
- _renderOverlay: function(ctx) {
- this._renderBackgroundOrOverlay(ctx, "overlay");
- },
- getCenter: function() {
- return {
- top: this.height / 2,
- left: this.width / 2
- };
- },
- centerObjectH: function(object) {
- return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
- },
- centerObjectV: function(object) {
- return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
- },
- centerObject: function(object) {
- var center = this.getCenter();
- return this._centerObject(object, new fabric.Point(center.left, center.top));
- },
- viewportCenterObject: function(object) {
- var vpCenter = this.getVpCenter();
- return this._centerObject(object, vpCenter);
- },
- viewportCenterObjectH: function(object) {
- var vpCenter = this.getVpCenter();
- this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y));
- return this;
- },
- viewportCenterObjectV: function(object) {
- var vpCenter = this.getVpCenter();
- return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y));
- },
- getVpCenter: function() {
- var center = this.getCenter(), iVpt = invertTransform(this.viewportTransform);
- return transformPoint({
- x: center.left,
- y: center.top
- }, iVpt);
- },
- _centerObject: function(object, center) {
- object.setPositionByOrigin(center, "center", "center");
- object.setCoords();
- this.renderOnAddRemove && this.requestRenderAll();
- return this;
- },
- toDatalessJSON: function(propertiesToInclude) {
- return this.toDatalessObject(propertiesToInclude);
- },
- toObject: function(propertiesToInclude) {
- return this._toObjectMethod("toObject", propertiesToInclude);
- },
- toDatalessObject: function(propertiesToInclude) {
- return this._toObjectMethod("toDatalessObject", propertiesToInclude);
- },
- _toObjectMethod: function(methodName, propertiesToInclude) {
- var data = {
- version: fabric.version,
- objects: this._toObjects(methodName, propertiesToInclude)
- };
- extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));
- fabric.util.populateWithProperties(this, data, propertiesToInclude);
- return data;
- },
- _toObjects: function(methodName, propertiesToInclude) {
- return this.getObjects().filter(function(object) {
- return !object.excludeFromExport;
- }).map(function(instance) {
- return this._toObject(instance, methodName, propertiesToInclude);
- }, this);
- },
- _toObject: function(instance, methodName, propertiesToInclude) {
- var originalValue;
- if (!this.includeDefaultValues) {
- originalValue = instance.includeDefaultValues;
- instance.includeDefaultValues = false;
- }
- var object = instance[methodName](propertiesToInclude);
- if (!this.includeDefaultValues) {
- instance.includeDefaultValues = originalValue;
- }
- return object;
- },
- __serializeBgOverlay: function(methodName, propertiesToInclude) {
- var data = {}, bgImage = this.backgroundImage, overlay = this.overlayImage;
- if (this.backgroundColor) {
- data.background = this.backgroundColor.toObject ? this.backgroundColor.toObject(propertiesToInclude) : this.backgroundColor;
- }
- if (this.overlayColor) {
- data.overlay = this.overlayColor.toObject ? this.overlayColor.toObject(propertiesToInclude) : this.overlayColor;
- }
- if (bgImage && !bgImage.excludeFromExport) {
- data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude);
- }
- if (overlay && !overlay.excludeFromExport) {
- data.overlayImage = this._toObject(overlay, methodName, propertiesToInclude);
- }
- return data;
- },
- svgViewportTransformation: true,
- toSVG: function(options, reviver) {
- options || (options = {});
- var markup = [];
- this._setSVGPreamble(markup, options);
- this._setSVGHeader(markup, options);
- this._setSVGBgOverlayColor(markup, "backgroundColor");
- this._setSVGBgOverlayImage(markup, "backgroundImage", reviver);
- this._setSVGObjects(markup, reviver);
- this._setSVGBgOverlayColor(markup, "overlayColor");
- this._setSVGBgOverlayImage(markup, "overlayImage", reviver);
- markup.push("</svg>");
- return markup.join("");
- },
- _setSVGPreamble: function(markup, options) {
- if (options.suppressPreamble) {
- return;
- }
- markup.push('<?xml version="1.0" encoding="', options.encoding || "UTF-8", '" standalone="no" ?>\n', '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ', '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n');
- },
- _setSVGHeader: function(markup, options) {
- var width = options.width || this.width, height = options.height || this.height, vpt, viewBox = 'viewBox="0 0 ' + this.width + " " + this.height + '" ', NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
- if (options.viewBox) {
- viewBox = 'viewBox="' + options.viewBox.x + " " + options.viewBox.y + " " + options.viewBox.width + " " + options.viewBox.height + '" ';
- } else {
- if (this.svgViewportTransformation) {
- vpt = this.viewportTransform;
- viewBox = 'viewBox="' + toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + " " + toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + " " + toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + " " + toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" ';
- }
- }
- markup.push("<svg ", 'xmlns="http://www.w3.org/2000/svg" ', 'xmlns:xlink="http://www.w3.org/1999/xlink" ', 'version="1.1" ', 'width="', width, '" ', 'height="', height, '" ', viewBox, 'xml:space="preserve">\n', "<desc>Created with Fabric.js ", fabric.version, "</desc>\n", "<defs>\n", this.createSVGFontFacesMarkup(), this.createSVGRefElementsMarkup(), "</defs>\n");
- },
- createSVGRefElementsMarkup: function() {
- var _this = this, markup = [ "backgroundColor", "overlayColor" ].map(function(prop) {
- var fill = _this[prop];
- if (fill && fill.toLive) {
- return fill.toSVG(_this, false);
- }
- });
- return markup.join("");
- },
- createSVGFontFacesMarkup: function() {
- var markup = "", fontList = {}, obj, fontFamily, style, row, rowIndex, _char, charIndex, i, len, fontPaths = fabric.fontPaths, objects = this.getObjects();
- for (i = 0, len = objects.length; i < len; i++) {
- obj = objects[i];
- fontFamily = obj.fontFamily;
- if (obj.type.indexOf("text") === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) {
- continue;
- }
- fontList[fontFamily] = true;
- if (!obj.styles) {
- continue;
- }
- style = obj.styles;
- for (rowIndex in style) {
- row = style[rowIndex];
- for (charIndex in row) {
- _char = row[charIndex];
- fontFamily = _char.fontFamily;
- if (!fontList[fontFamily] && fontPaths[fontFamily]) {
- fontList[fontFamily] = true;
- }
- }
- }
- }
- for (var j in fontList) {
- markup += [ "\t\t@font-face {\n", "\t\t\tfont-family: '", j, "';\n", "\t\t\tsrc: url('", fontPaths[j], "');\n", "\t\t}\n" ].join("");
- }
- if (markup) {
- markup = [ '\t<style type="text/css">', "<![CDATA[\n", markup, "]]>", "</style>\n" ].join("");
- }
- return markup;
- },
- _setSVGObjects: function(markup, reviver) {
- var instance, i, len, objects = this.getObjects();
- for (i = 0, len = objects.length; i < len; i++) {
- instance = objects[i];
- if (instance.excludeFromExport) {
- continue;
- }
- this._setSVGObject(markup, instance, reviver);
- }
- },
- _setSVGObject: function(markup, instance, reviver) {
- markup.push(instance.toSVG(reviver));
- },
- _setSVGBgOverlayImage: function(markup, property, reviver) {
- if (this[property] && this[property].toSVG) {
- markup.push(this[property].toSVG(reviver));
- }
- },
- _setSVGBgOverlayColor: function(markup, property) {
- var filler = this[property], vpt = this.viewportTransform, finalWidth = this.width / vpt[0], finalHeight = this.height / vpt[3];
- if (!filler) {
- return;
- }
- if (filler.toLive) {
- var repeat = filler.repeat;
- markup.push('<rect transform="translate(', finalWidth / 2, ",", finalHeight / 2, ')"', ' x="', filler.offsetX - finalWidth / 2, '" y="', filler.offsetY - finalHeight / 2, '" ', 'width="', repeat === "repeat-y" || repeat === "no-repeat" ? filler.source.width : finalWidth, '" height="', repeat === "repeat-x" || repeat === "no-repeat" ? filler.source.height : finalHeight, '" fill="url(#SVGID_' + filler.id + ')"', "></rect>\n");
- } else {
- markup.push('<rect x="0" y="0" width="100%" height="100%" ', 'fill="', this[property], '"', "></rect>\n");
- }
- },
- sendToBack: function(object) {
- if (!object) {
- return this;
- }
- var activeSelection = this._activeObject, i, obj, objs;
- if (object === activeSelection && object.type === "activeSelection") {
- objs = activeSelection._objects;
- for (i = objs.length; i--; ) {
- obj = objs[i];
- removeFromArray(this._objects, obj);
- this._objects.unshift(obj);
- }
- } else {
- removeFromArray(this._objects, object);
- this._objects.unshift(object);
- }
- this.renderOnAddRemove && this.requestRenderAll();
- return this;
- },
- bringToFront: function(object) {
- if (!object) {
- return this;
- }
- var activeSelection = this._activeObject, i, obj, objs;
- if (object === activeSelection && object.type === "activeSelection") {
- objs = activeSelection._objects;
- for (i = 0; i < objs.length; i++) {
- obj = objs[i];
- removeFromArray(this._objects, obj);
- this._objects.push(obj);
- }
- } else {
- removeFromArray(this._objects, object);
- this._objects.push(object);
- }
- this.renderOnAddRemove && this.requestRenderAll();
- return this;
- },
- sendBackwards: function(object, intersecting) {
- if (!object) {
- return this;
- }
- var activeSelection = this._activeObject, i, obj, idx, newIdx, objs, objsMoved = 0;
- if (object === activeSelection && object.type === "activeSelection") {
- objs = activeSelection._objects;
- for (i = 0; i < objs.length; i++) {
- obj = objs[i];
- idx = this._objects.indexOf(obj);
- if (idx > 0 + objsMoved) {
- newIdx = idx - 1;
- removeFromArray(this._objects, obj);
- this._objects.splice(newIdx, 0, obj);
- }
- objsMoved++;
- }
- } else {
- idx = this._objects.indexOf(object);
- if (idx !== 0) {
- newIdx = this._findNewLowerIndex(object, idx, intersecting);
- removeFromArray(this._objects, object);
- this._objects.splice(newIdx, 0, object);
- }
- }
- this.renderOnAddRemove && this.requestRenderAll();
- return this;
- },
- _findNewLowerIndex: function(object, idx, intersecting) {
- var newIdx, i;
- if (intersecting) {
- newIdx = idx;
- for (i = idx - 1; i >= 0; --i) {
- var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object);
- if (isIntersecting) {
- newIdx = i;
- break;
- }
- }
- } else {
- newIdx = idx - 1;
- }
- return newIdx;
- },
- bringForward: function(object, intersecting) {
- if (!object) {
- return this;
- }
- var activeSelection = this._activeObject, i, obj, idx, newIdx, objs, objsMoved = 0;
- if (object === activeSelection && object.type === "activeSelection") {
- objs = activeSelection._objects;
- for (i = objs.length; i--; ) {
- obj = objs[i];
- idx = this._objects.indexOf(obj);
- if (idx < this._objects.length - 1 - objsMoved) {
- newIdx = idx + 1;
- removeFromArray(this._objects, obj);
- this._objects.splice(newIdx, 0, obj);
- }
- objsMoved++;
- }
- } else {
- idx = this._objects.indexOf(object);
- if (idx !== this._objects.length - 1) {
- newIdx = this._findNewUpperIndex(object, idx, intersecting);
- removeFromArray(this._objects, object);
- this._objects.splice(newIdx, 0, object);
- }
- }
- this.renderOnAddRemove && this.requestRenderAll();
- return this;
- },
- _findNewUpperIndex: function(object, idx, intersecting) {
- var newIdx, i, len;
- if (intersecting) {
- newIdx = idx;
- for (i = idx + 1, len = this._objects.length; i < len; ++i) {
- var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object);
- if (isIntersecting) {
- newIdx = i;
- break;
- }
- }
- } else {
- newIdx = idx + 1;
- }
- return newIdx;
- },
- moveTo: function(object, index) {
- removeFromArray(this._objects, object);
- this._objects.splice(index, 0, object);
- return this.renderOnAddRemove && this.requestRenderAll();
- },
- dispose: function() {
- if (this.isRendering) {
- fabric.util.cancelAnimFrame(this.isRendering);
- this.isRendering = 0;
- }
- this.forEachObject(function(object) {
- object.dispose && object.dispose();
- });
- this._objects = [];
- this.backgroundImage = null;
- this.overlayImage = null;
- this._iTextInstances = null;
- this.lowerCanvasEl = null;
- this.contextContainer = null;
- return this;
- },
- toString: function() {
- return "#<fabric.Canvas (" + this.complexity() + "): " + "{ objects: " + this.getObjects().length + " }>";
- }
+ obj._removeTransformMatrix(_options);
+ _this.reviver && _this.reviver(el, obj);
+ _this.instances[index] = obj;
+ _this.checkIfDone();
+ };
+};
+
+fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
+
+ var instanceFillValue = obj[property];
+ if (!(/^url\(/).test(instanceFillValue)) {
+ return;
+ }
+ var gradientId = this.regexUrl.exec(instanceFillValue)[1];
+ this.regexUrl.lastIndex = 0;
+ 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;
});
- extend(fabric.StaticCanvas.prototype, fabric.Observable);
- extend(fabric.StaticCanvas.prototype, fabric.Collection);
- extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter);
- extend(fabric.StaticCanvas, {
- EMPTY_JSON: '{"objects": [], "background": "white"}',
- supports: function(methodName) {
- var el = fabric.util.createCanvasElement();
- if (!el || !el.getContext) {
- return null;
- }
- var ctx = el.getContext("2d");
- if (!ctx) {
- return null;
- }
- switch (methodName) {
- case "getImageData":
- return typeof ctx.getImageData !== "undefined";
+ this.callback(this.instances, this.elements);
+ }
+};
- case "setLineDash":
- return typeof ctx.setLineDash !== "undefined";
- case "toDataURL":
- return typeof el.toDataURL !== "undefined";
+(function(global) {
- case "toDataURLWithQuality":
- try {
- el.toDataURL("image/jpeg", 0);
- return true;
- } catch (e) {}
- return false;
+ 'use strict';
- default:
- return null;
- }
- }
- });
- fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
- if (fabric.isLikelyNode) {
- fabric.StaticCanvas.prototype.createPNGStream = function() {
- var impl = fabric.util.getNodeCanvas(this.lowerCanvasEl);
- return impl && impl.createPNGStream();
- };
- fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {
- var impl = fabric.util.getNodeCanvas(this.lowerCanvasEl);
- return impl && impl.createJPEGStream(opts);
- };
- }
-})();
+ /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
-fabric.BaseBrush = fabric.util.createClass({
- color: "rgb(0, 0, 0)",
- width: 1,
- shadow: null,
- strokeLineCap: "round",
- strokeLineJoin: "round",
- strokeMiterLimit: 10,
- strokeDashArray: null,
- setShadow: function(options) {
- this.shadow = new fabric.Shadow(options);
- return this;
+ 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);
},
- _setBrushStyles: function() {
- var ctx = this.canvas.contextTop;
- ctx.strokeStyle = this.color;
- ctx.lineWidth = this.width;
- ctx.lineCap = this.strokeLineCap;
- ctx.miterLimit = this.strokeMiterLimit;
- ctx.lineJoin = this.strokeLineJoin;
- if (fabric.StaticCanvas.supports("setLineDash")) {
- ctx.setLineDash(this.strokeDashArray || []);
- }
+
+ /**
+ * 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;
},
- _saveAndTransform: function(ctx) {
- var v = this.canvas.viewportTransform;
- ctx.save();
- ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
+
+ /**
+ * 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);
},
- _setShadow: function() {
- if (!this.shadow) {
- return;
- }
- var ctx = this.canvas.contextTop, zoom = this.canvas.getZoom();
- ctx.shadowColor = this.shadow.color;
- ctx.shadowBlur = this.shadow.blur * zoom;
- ctx.shadowOffsetX = this.shadow.offsetX * zoom;
- ctx.shadowOffsetY = this.shadow.offsetY * zoom;
+
+ /**
+ * Adds value to this point
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ * @chainable
+ */
+ scalarAddEquals: function (scalar) {
+ this.x += scalar;
+ this.y += scalar;
+ return this;
},
- _resetShadow: function() {
- var ctx = this.canvas.contextTop;
- ctx.shadowColor = "";
- ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
- }
-});
-(function() {
- fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, {
- initialize: function(canvas) {
- this.canvas = canvas;
- this._points = [];
- },
- _drawSegment: function(ctx, p1, p2) {
- var midPoint = p1.midPointFrom(p2);
- ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
- return midPoint;
- },
- onMouseDown: function(pointer) {
- this._prepareForDrawing(pointer);
- this._captureDrawingPath(pointer);
- this._render();
- },
- onMouseMove: function(pointer) {
- if (this._captureDrawingPath(pointer) && this._points.length > 1) {
- if (this.needsFullRender) {
- this.canvas.clearContext(this.canvas.contextTop);
- this._render();
- } else {
- var points = this._points, length = points.length, ctx = this.canvas.contextTop;
- this._saveAndTransform(ctx);
- if (this.oldEnd) {
- ctx.beginPath();
- ctx.moveTo(this.oldEnd.x, this.oldEnd.y);
- }
- this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true);
- ctx.stroke();
- ctx.restore();
- }
- }
- },
- onMouseUp: function() {
- this.oldEnd = undefined;
- this._finalizeAndAddPath();
- },
- _prepareForDrawing: function(pointer) {
- var p = new fabric.Point(pointer.x, pointer.y);
- this._reset();
- this._addPoint(p);
- this.canvas.contextTop.moveTo(p.x, p.y);
- },
- _addPoint: function(point) {
- if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) {
- return false;
- }
- this._points.push(point);
- return true;
- },
- _reset: function() {
- this._points.length = 0;
- this._setBrushStyles();
- var color = new fabric.Color(this.color);
- this.needsFullRender = color.getAlpha() < 1;
- this._setShadow();
- },
- _captureDrawingPath: function(pointer) {
- var pointerPoint = new fabric.Point(pointer.x, pointer.y);
- return this._addPoint(pointerPoint);
- },
- _render: function() {
- var ctx = this.canvas.contextTop, i, len, p1 = this._points[0], p2 = this._points[1];
- this._saveAndTransform(ctx);
- ctx.beginPath();
- if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {
- var width = this.width / 1e3;
- p1 = new fabric.Point(p1.x, p1.y);
- p2 = new fabric.Point(p2.x, p2.y);
- p1.x -= width;
- p2.x += width;
- }
- ctx.moveTo(p1.x, p1.y);
- for (i = 1, len = this._points.length; i < len; i++) {
- this._drawSegment(ctx, p1, p2);
- p1 = this._points[i];
- p2 = this._points[i + 1];
- }
- ctx.lineTo(p1.x, p1.y);
- ctx.stroke();
- ctx.restore();
- },
- convertPointsToSVGPath: function(points) {
- var path = [], i, width = this.width / 1e3, p1 = new fabric.Point(points[0].x, points[0].y), p2 = new fabric.Point(points[1].x, points[1].y), len = points.length, multSignX = 1, multSignY = 1, manyPoints = len > 2;
- if (manyPoints) {
- multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1;
- multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1;
- }
- path.push("M ", p1.x - multSignX * width, " ", p1.y - multSignY * width, " ");
- for (i = 1; i < len; i++) {
- if (!p1.eq(p2)) {
- var midPoint = p1.midPointFrom(p2);
- path.push("Q ", p1.x, " ", p1.y, " ", midPoint.x, " ", midPoint.y, " ");
- }
- p1 = points[i];
- if (i + 1 < points.length) {
- p2 = points[i + 1];
- }
- }
- if (manyPoints) {
- multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1;
- multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1;
- }
- path.push("L ", p1.x + multSignX * width, " ", p1.y + multSignY * width);
- return path;
- },
- createPath: function(pathData) {
- var path = new fabric.Path(pathData, {
- fill: null,
- stroke: this.color,
- strokeWidth: this.width,
- strokeLineCap: this.strokeLineCap,
- strokeMiterLimit: this.strokeMiterLimit,
- strokeLineJoin: this.strokeLineJoin,
- strokeDashArray: this.strokeDashArray
- });
- var position = new fabric.Point(path.left + path.width / 2, path.top + path.height / 2);
- position = path.translateToGivenOrigin(position, "center", "center", path.originX, path.originY);
- path.top = position.y;
- path.left = position.x;
- if (this.shadow) {
- this.shadow.affectStroke = true;
- path.setShadow(this.shadow);
- }
- return path;
- },
- _finalizeAndAddPath: function() {
- var ctx = this.canvas.contextTop;
- ctx.closePath();
- var pathData = this.convertPointsToSVGPath(this._points).join("");
- if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") {
- this.canvas.requestRenderAll();
- return;
- }
- var path = this.createPath(pathData);
- this.canvas.clearContext(this.canvas.contextTop);
- this.canvas.add(path);
- this.canvas.renderAll();
- path.setCoords();
- this._resetShadow();
- this.canvas.fire("path:created", {
- path: path
- });
- }
- });
-})();
+ /**
+ * 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);
+ },
-fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, {
- width: 10,
- initialize: function(canvas) {
- this.canvas = canvas;
- this.points = [];
+ /**
+ * 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;
},
- drawDot: function(pointer) {
- var point = this.addPoint(pointer), ctx = this.canvas.contextTop;
- this._saveAndTransform(ctx);
- ctx.fillStyle = point.fill;
- ctx.beginPath();
- ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
- ctx.closePath();
- ctx.fill();
- ctx.restore();
+
+ /**
+ * 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);
},
- onMouseDown: function(pointer) {
- this.points.length = 0;
- this.canvas.clearContext(this.canvas.contextTop);
- this._setShadow();
- this.drawDot(pointer);
+
+ /**
+ * Subtracts value from this point
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ * @chainable
+ */
+ scalarSubtractEquals: function (scalar) {
+ this.x -= scalar;
+ this.y -= scalar;
+ return this;
},
- _render: function() {
- var ctx = this.canvas.contextTop, i, len, points = this.points, point;
- this._saveAndTransform(ctx);
- for (i = 0, len = points.length; i < len; i++) {
- point = points[i];
- ctx.fillStyle = point.fill;
- ctx.beginPath();
- ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
- ctx.closePath();
- ctx.fill();
- }
- ctx.restore();
+
+ /**
+ * Multiplies 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);
},
- onMouseMove: function(pointer) {
- this.drawDot(pointer);
+
+ /**
+ * Multiplies 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;
},
- onMouseUp: function() {
- var originalRenderOnAddRemove = this.canvas.renderOnAddRemove, i, len;
- this.canvas.renderOnAddRemove = false;
- var circles = [];
- for (i = 0, len = this.points.length; i < len; i++) {
- var point = this.points[i], circle = new fabric.Circle({
- radius: point.radius,
- left: point.x,
- top: point.y,
- originX: "center",
- originY: "center",
- fill: point.fill
- });
- this.shadow && circle.setShadow(this.shadow);
- circles.push(circle);
- }
- var group = new fabric.Group(circles, {
- originX: "center",
- originY: "center"
- });
- group.canvas = this.canvas;
- this.canvas.add(group);
- this.canvas.fire("path:created", {
- path: group
- });
- this.canvas.clearContext(this.canvas.contextTop);
- this._resetShadow();
- this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
- this.canvas.requestRenderAll();
+
+ /**
+ * 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);
},
- addPoint: function(pointer) {
- var pointerPoint = new fabric.Point(pointer.x, pointer.y), circleRadius = fabric.util.getRandomInt(Math.max(0, this.width - 20), this.width + 20) / 2, circleColor = new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0, 100) / 100).toRgba();
- pointerPoint.radius = circleRadius;
- pointerPoint.fill = circleColor;
- this.points.push(pointerPoint);
- return pointerPoint;
- }
-});
-fabric.SprayBrush = fabric.util.createClass(fabric.BaseBrush, {
- width: 10,
- density: 20,
- dotWidth: 1,
- dotWidthVariance: 1,
- randomOpacity: false,
- optimizeOverlapping: true,
- initialize: function(canvas) {
- this.canvas = canvas;
- this.sprayChunks = [];
+ /**
+ * 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;
},
- onMouseDown: function(pointer) {
- this.sprayChunks.length = 0;
- this.canvas.clearContext(this.canvas.contextTop);
- this._setShadow();
- this.addSprayChunk(pointer);
- this.render(this.sprayChunkPoints);
+
+ /**
+ * 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);
},
- onMouseMove: function(pointer) {
- this.addSprayChunk(pointer);
- this.render(this.sprayChunkPoints);
+
+ /**
+ * 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);
},
- onMouseUp: function() {
- var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
- this.canvas.renderOnAddRemove = false;
- var rects = [];
- for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {
- var sprayChunk = this.sprayChunks[i];
- for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) {
- var rect = new fabric.Rect({
- width: sprayChunk[j].width,
- height: sprayChunk[j].width,
- left: sprayChunk[j].x + 1,
- top: sprayChunk[j].y + 1,
- originX: "center",
- originY: "center",
- fill: this.color
- });
- rects.push(rect);
- }
- }
- if (this.optimizeOverlapping) {
- rects = this._getOptimizedRects(rects);
- }
- var group = new fabric.Group(rects, {
- originX: "center",
- originY: "center"
- });
- this.shadow && group.setShadow(this.shadow);
- this.canvas.add(group);
- this.canvas.fire("path:created", {
- path: group
- });
- this.canvas.clearContext(this.canvas.contextTop);
- this._resetShadow();
- this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
- this.canvas.requestRenderAll();
+
+ /**
+ * 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);
},
- _getOptimizedRects: function(rects) {
- var uniqueRects = {}, key, i, len;
- for (i = 0, len = rects.length; i < len; i++) {
- key = rects[i].left + "" + rects[i].top;
- if (!uniqueRects[key]) {
- uniqueRects[key] = rects[i];
- }
- }
- var uniqueRectsArray = [];
- for (key in uniqueRects) {
- uniqueRectsArray.push(uniqueRects[key]);
- }
- return uniqueRectsArray;
+
+ /**
+
+ * 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);
},
- render: function(sprayChunk) {
- var ctx = this.canvas.contextTop, i, len;
- ctx.fillStyle = this.color;
- this._saveAndTransform(ctx);
- for (i = 0, len = sprayChunk.length; i < len; i++) {
- var point = sprayChunk[i];
- if (typeof point.opacity !== "undefined") {
- ctx.globalAlpha = point.opacity;
- }
- ctx.fillRect(point.x, point.y, point.width, point.width);
- }
- ctx.restore();
+
+ /**
+ * 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);
},
- _render: function() {
- var ctx = this.canvas.contextTop, i, ilen;
- ctx.fillStyle = this.color;
- this._saveAndTransform(ctx);
- for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {
- this.render(this.sprayChunks[i]);
- }
- ctx.restore();
+
+ /**
+ * 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);
},
- addSprayChunk: function(pointer) {
- this.sprayChunkPoints = [];
- var x, y, width, radius = this.width / 2, i;
- for (i = 0; i < this.density; i++) {
- x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius);
- y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius);
- if (this.dotWidthVariance) {
- width = fabric.util.getRandomInt(Math.max(1, this.dotWidth - this.dotWidthVariance), this.dotWidth + this.dotWidthVariance);
- } else {
- width = this.dotWidth;
- }
- var point = new fabric.Point(x, y);
- point.width = width;
- if (this.randomOpacity) {
- point.opacity = fabric.util.getRandomInt(0, 100) / 100;
- }
- this.sprayChunkPoints.push(point);
- }
- this.sprayChunks.push(this.sprayChunkPoints);
- }
-});
-fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, {
- getPatternSrc: function() {
- var dotWidth = 20, dotDistance = 5, patternCanvas = fabric.document.createElement("canvas"), patternCtx = patternCanvas.getContext("2d");
- patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;
- patternCtx.fillStyle = this.color;
- patternCtx.beginPath();
- patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false);
- patternCtx.closePath();
- patternCtx.fill();
- return patternCanvas;
+ /**
+ * 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);
},
- getPatternSrcFunction: function() {
- return String(this.getPatternSrc).replace("this.color", '"' + this.color + '"');
+
+ /**
+ * Returns the point between this point and another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point}
+ */
+ midPointFrom: function (that) {
+ return this.lerp(that);
},
- getPattern: function() {
- return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), "repeat");
+
+ /**
+ * 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));
},
- _setBrushStyles: function() {
- this.callSuper("_setBrushStyles");
- this.canvas.contextTop.strokeStyle = this.getPattern();
+
+ /**
+ * 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));
},
- createPath: function(pathData) {
- var path = this.callSuper("createPath", pathData), topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2);
- path.stroke = new fabric.Pattern({
- source: this.source || this.getPatternSrcFunction(),
- offsetX: -topLeft.x,
- offsetY: -topLeft.y
- });
- return path;
- }
-});
-(function() {
- var getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, atan2 = Math.atan2, abs = Math.abs, supportLineDash = fabric.StaticCanvas.supports("setLineDash"), STROKE_OFFSET = .5;
- fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, {
- initialize: function(el, options) {
- options || (options = {});
- this.renderAndResetBound = this.renderAndReset.bind(this);
- this._initStatic(el, options);
- this._initInteractive();
- this._createCacheCanvas();
- },
- uniScaleTransform: false,
- uniScaleKey: "shiftKey",
- centeredScaling: false,
- centeredRotation: false,
- centeredKey: "altKey",
- altActionKey: "shiftKey",
- interactive: true,
- selection: true,
- selectionKey: "shiftKey",
- altSelectionKey: null,
- selectionColor: "rgba(100, 100, 255, 0.3)",
- selectionDashArray: [],
- selectionBorderColor: "rgba(255, 255, 255, 0.3)",
- selectionLineWidth: 1,
- selectionFullyContained: false,
- hoverCursor: "move",
- moveCursor: "move",
- defaultCursor: "default",
- freeDrawingCursor: "crosshair",
- rotationCursor: "crosshair",
- notAllowedCursor: "not-allowed",
- containerClass: "canvas-container",
- perPixelTargetFind: false,
- targetFindTolerance: 0,
- skipTargetFind: false,
- isDrawingMode: false,
- preserveObjectStacking: false,
- snapAngle: 0,
- snapThreshold: null,
- stopContextMenu: false,
- fireRightClick: false,
- fireMiddleClick: false,
- _initInteractive: function() {
- this._currentTransform = null;
- this._groupSelector = null;
- this._initWrapperElement();
- this._createUpperCanvas();
- this._initEventListeners();
- this._initRetinaScaling();
- this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);
- this.calcOffset();
- },
- _chooseObjectsToRender: function() {
- var activeObjects = this.getActiveObjects(), object, objsToRender, activeGroupObjects;
- if (activeObjects.length > 0 && !this.preserveObjectStacking) {
- objsToRender = [];
- activeGroupObjects = [];
- for (var i = 0, length = this._objects.length; i < length; i++) {
- object = this._objects[i];
- if (activeObjects.indexOf(object) === -1) {
- objsToRender.push(object);
- } else {
- activeGroupObjects.push(object);
- }
- }
- if (activeObjects.length > 1) {
- this._activeObject._objects = activeGroupObjects;
- }
- objsToRender.push.apply(objsToRender, activeGroupObjects);
- } else {
- objsToRender = this._objects;
- }
- return objsToRender;
- },
- renderAll: function() {
- if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) {
- this.clearContext(this.contextTop);
- this.contextTopDirty = false;
- }
- if (this.hasLostContext) {
- this.renderTopLayer(this.contextTop);
- }
- var canvasToDrawOn = this.contextContainer;
- this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
- return this;
- },
- renderTopLayer: function(ctx) {
- if (this.isDrawingMode && this._isCurrentlyDrawing) {
- this.freeDrawingBrush && this.freeDrawingBrush._render();
- }
- if (this.selection && this._groupSelector) {
- this._drawSelection(ctx);
- }
- },
- renderTop: function() {
- var ctx = this.contextTop;
- this.clearContext(ctx);
- this.renderTopLayer(ctx);
- this.fire("after:render");
- this.contextTopDirty = true;
- return this;
- },
- _resetCurrentTransform: function() {
- var t = this._currentTransform;
- t.target.set({
- scaleX: t.original.scaleX,
- scaleY: t.original.scaleY,
- skewX: t.original.skewX,
- skewY: t.original.skewY,
- left: t.original.left,
- top: t.original.top
- });
- if (this._shouldCenterTransform(t.target)) {
- if (t.originX !== "center") {
- if (t.originX === "right") {
- t.mouseXSign = -1;
- } else {
- t.mouseXSign = 1;
- }
- }
- if (t.originY !== "center") {
- if (t.originY === "bottom") {
- t.mouseYSign = -1;
- } else {
- t.mouseYSign = 1;
- }
- }
- t.originX = "center";
- t.originY = "center";
- } else {
- t.originX = t.original.originX;
- t.originY = t.original.originY;
- }
- },
- containsPoint: function(e, target, point) {
- var ignoreZoom = true, pointer = point || this.getPointer(e, ignoreZoom), xy;
- if (target.group && target.group === this._activeObject && target.group.type === "activeSelection") {
- xy = this._normalizePointer(target.group, pointer);
- } else {
- xy = {
- x: pointer.x,
- y: pointer.y
- };
- }
- return target.containsPoint(xy) || target._findTargetCorner(pointer);
- },
- _normalizePointer: function(object, pointer) {
- var m = object.calcTransformMatrix(), invertedM = fabric.util.invertTransform(m), vptPointer = this.restorePointerVpt(pointer);
- return fabric.util.transformPoint(vptPointer, invertedM);
- },
- isTargetTransparent: function(target, x, y) {
- var ctx = this.contextCache, originalColor = target.selectionBackgroundColor, v = this.viewportTransform;
- target.selectionBackgroundColor = "";
- this.clearContext(ctx);
- ctx.save();
- ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
- target.render(ctx);
- ctx.restore();
- target === this._activeObject && target._renderControls(ctx, {
- hasBorders: false,
- transparentCorners: false
- }, {
- hasBorders: false
- });
- target.selectionBackgroundColor = originalColor;
- var isTransparent = fabric.util.isTransparent(ctx, x, y, this.targetFindTolerance);
- return isTransparent;
- },
- _isSelectionKeyPressed: function(e) {
- var selectionKeyPressed = false;
- if (Object.prototype.toString.call(this.selectionKey) === "[object Array]") {
- selectionKeyPressed = !!this.selectionKey.find(function(key) {
- return e[key] === true;
- });
- } else {
- selectionKeyPressed = e[this.selectionKey];
- }
- return selectionKeyPressed;
- },
- _shouldClearSelection: function(e, target) {
- var activeObjects = this.getActiveObjects(), activeObject = this._activeObject;
- return !target || target && activeObject && activeObjects.length > 1 && activeObjects.indexOf(target) === -1 && activeObject !== target && !this._isSelectionKeyPressed(e) || target && !target.evented || target && !target.selectable && activeObject && activeObject !== target;
- },
- _shouldCenterTransform: function(target) {
- if (!target) {
- return;
- }
- var t = this._currentTransform, centerTransform;
- if (t.action === "scale" || t.action === "scaleX" || t.action === "scaleY") {
- centerTransform = this.centeredScaling || target.centeredScaling;
- } else if (t.action === "rotate") {
- centerTransform = this.centeredRotation || target.centeredRotation;
- }
- return centerTransform ? !t.altKey : t.altKey;
- },
- _getOriginFromCorner: function(target, corner) {
- var origin = {
- x: target.originX,
- y: target.originY
- };
- if (corner === "ml" || corner === "tl" || corner === "bl") {
- origin.x = "right";
- } else if (corner === "mr" || corner === "tr" || corner === "br") {
- origin.x = "left";
- }
- if (corner === "tl" || corner === "mt" || corner === "tr") {
- origin.y = "bottom";
- } else if (corner === "bl" || corner === "mb" || corner === "br") {
- origin.y = "top";
- }
- return origin;
- },
- _getActionFromCorner: function(target, corner, e) {
- if (!corner) {
- return "drag";
- }
- switch (corner) {
- case "mtr":
- return "rotate";
+ /**
+ * Returns string representation of this point
+ * @return {String}
+ */
+ toString: function () {
+ return this.x + ',' + this.y;
+ },
- case "ml":
- case "mr":
- return e[this.altActionKey] ? "skewY" : "scaleX";
+ /**
+ * 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;
+ },
- case "mt":
- case "mb":
- return e[this.altActionKey] ? "skewX" : "scaleY";
+ /**
+ * Sets x of this point
+ * @param {Number} x
+ * @chainable
+ */
+ setX: function (x) {
+ this.x = x;
+ return this;
+ },
- default:
- return "scale";
- }
- },
- _setupCurrentTransform: function(e, target) {
- if (!target) {
- return;
- }
- var pointer = this.getPointer(e), corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(target, corner, e), origin = this._getOriginFromCorner(target, corner);
- this._currentTransform = {
- target: target,
- action: action,
- corner: corner,
- scaleX: target.scaleX,
- scaleY: target.scaleY,
- skewX: target.skewX,
- skewY: target.skewY,
- offsetX: pointer.x - target.left,
- offsetY: pointer.y - target.top,
- originX: origin.x,
- originY: origin.y,
- ex: pointer.x,
- ey: pointer.y,
- lastX: pointer.x,
- lastY: pointer.y,
- theta: degreesToRadians(target.angle),
- width: target.width * target.scaleX,
- mouseXSign: 1,
- mouseYSign: 1,
- shiftKey: e.shiftKey,
- altKey: e[this.centeredKey]
- };
- this._currentTransform.original = {
- left: target.left,
- top: target.top,
- scaleX: target.scaleX,
- scaleY: target.scaleY,
- skewX: target.skewX,
- skewY: target.skewY,
- originX: origin.x,
- originY: origin.y
- };
- this._resetCurrentTransform();
- },
- _translateObject: function(x, y) {
- var transform = this._currentTransform, target = transform.target, newLeft = x - transform.offsetX, newTop = y - transform.offsetY, moveX = !target.get("lockMovementX") && target.left !== newLeft, moveY = !target.get("lockMovementY") && target.top !== newTop;
- moveX && target.set("left", newLeft);
- moveY && target.set("top", newTop);
- return moveX || moveY;
- },
- _changeSkewTransformOrigin: function(mouseMove, t, by) {
- var property = "originX", origins = {
- 0: "center"
- }, skew = t.target.skewX, originA = "left", originB = "right", corner = t.corner === "mt" || t.corner === "ml" ? 1 : -1, flipSign = 1;
- mouseMove = mouseMove > 0 ? 1 : -1;
- if (by === "y") {
- skew = t.target.skewY;
- originA = "top";
- originB = "bottom";
- property = "originY";
- }
- origins[-1] = originA;
- origins[1] = originB;
- t.target.flipX && (flipSign *= -1);
- t.target.flipY && (flipSign *= -1);
- if (skew === 0) {
- t.skewSign = -corner * mouseMove * flipSign;
- t[property] = origins[-mouseMove];
- } else {
- skew = skew > 0 ? 1 : -1;
- t.skewSign = skew;
- t[property] = origins[skew * corner * flipSign];
- }
- },
- _skewObject: function(x, y, by) {
- var t = this._currentTransform, target = t.target, skewed = false, lockSkewingX = target.get("lockSkewingX"), lockSkewingY = target.get("lockSkewingY");
- if (lockSkewingX && by === "x" || lockSkewingY && by === "y") {
- return false;
- }
- var center = target.getCenterPoint(), actualMouseByCenter = target.toLocalPoint(new fabric.Point(x, y), "center", "center")[by], lastMouseByCenter = target.toLocalPoint(new fabric.Point(t.lastX, t.lastY), "center", "center")[by], actualMouseByOrigin, constraintPosition, dim = target._getTransformedDimensions();
- this._changeSkewTransformOrigin(actualMouseByCenter - lastMouseByCenter, t, by);
- actualMouseByOrigin = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY)[by];
- constraintPosition = target.translateToOriginPoint(center, t.originX, t.originY);
- skewed = this._setObjectSkew(actualMouseByOrigin, t, by, dim);
- t.lastX = x;
- t.lastY = y;
- target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
- return skewed;
- },
- _setObjectSkew: function(localMouse, transform, by, _dim) {
- var target = transform.target, newValue, skewed = false, skewSign = transform.skewSign, newDim, dimNoSkew, otherBy, _otherBy, _by, newDimMouse, skewX, skewY;
- if (by === "x") {
- otherBy = "y";
- _otherBy = "Y";
- _by = "X";
- skewX = 0;
- skewY = target.skewY;
- } else {
- otherBy = "x";
- _otherBy = "X";
- _by = "Y";
- skewX = target.skewX;
- skewY = 0;
- }
- dimNoSkew = target._getTransformedDimensions(skewX, skewY);
- newDimMouse = 2 * Math.abs(localMouse) - dimNoSkew[by];
- if (newDimMouse <= 2) {
- newValue = 0;
- } else {
- newValue = skewSign * Math.atan(newDimMouse / target["scale" + _by] / (dimNoSkew[otherBy] / target["scale" + _otherBy]));
- newValue = fabric.util.radiansToDegrees(newValue);
- }
- skewed = target["skew" + _by] !== newValue;
- target.set("skew" + _by, newValue);
- if (target["skew" + _otherBy] !== 0) {
- newDim = target._getTransformedDimensions();
- newValue = _dim[otherBy] / newDim[otherBy] * target["scale" + _otherBy];
- target.set("scale" + _otherBy, newValue);
- }
- return skewed;
- },
- _scaleObject: function(x, y, by) {
- var t = this._currentTransform, target = t.target, lockScalingX = target.lockScalingX, lockScalingY = target.lockScalingY, lockScalingFlip = target.lockScalingFlip;
- if (lockScalingX && lockScalingY) {
- return false;
- }
- var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY), dim = target._getTransformedDimensions(), scaled = false;
- this._setLocalMouse(localMouse, t);
- scaled = this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip, dim);
- target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
- return scaled;
- },
- _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) {
- var target = transform.target, forbidScalingX = false, forbidScalingY = false, scaled = false, changeX, changeY, scaleX, scaleY;
- scaleX = localMouse.x * target.scaleX / _dim.x;
- scaleY = localMouse.y * target.scaleY / _dim.y;
- changeX = target.scaleX !== scaleX;
- changeY = target.scaleY !== scaleY;
- if (lockScalingFlip && scaleX <= 0 && scaleX < target.scaleX) {
- forbidScalingX = true;
- localMouse.x = 0;
- }
- if (lockScalingFlip && scaleY <= 0 && scaleY < target.scaleY) {
- forbidScalingY = true;
- localMouse.y = 0;
- }
- if (by === "equally" && !lockScalingX && !lockScalingY) {
- scaled = this._scaleObjectEqually(localMouse, target, transform, _dim);
- } else if (!by) {
- forbidScalingX || lockScalingX || target.set("scaleX", scaleX) && (scaled = scaled || changeX);
- forbidScalingY || lockScalingY || target.set("scaleY", scaleY) && (scaled = scaled || changeY);
- } else if (by === "x" && !target.get("lockUniScaling")) {
- forbidScalingX || lockScalingX || target.set("scaleX", scaleX) && (scaled = scaled || changeX);
- } else if (by === "y" && !target.get("lockUniScaling")) {
- forbidScalingY || lockScalingY || target.set("scaleY", scaleY) && (scaled = scaled || changeY);
- }
- transform.newScaleX = scaleX;
- transform.newScaleY = scaleY;
- forbidScalingX || forbidScalingY || this._flipObject(transform, by);
- return scaled;
- },
- _scaleObjectEqually: function(localMouse, target, transform, _dim) {
- var dist = localMouse.y + localMouse.x, lastDist = _dim.y * transform.original.scaleY / target.scaleY + _dim.x * transform.original.scaleX / target.scaleX, scaled, signX = localMouse.x < 0 ? -1 : 1, signY = localMouse.y < 0 ? -1 : 1;
- transform.newScaleX = signX * Math.abs(transform.original.scaleX * dist / lastDist);
- transform.newScaleY = signY * Math.abs(transform.original.scaleY * dist / lastDist);
- scaled = transform.newScaleX !== target.scaleX || transform.newScaleY !== target.scaleY;
- target.set("scaleX", transform.newScaleX);
- target.set("scaleY", transform.newScaleY);
- return scaled;
- },
- _flipObject: function(transform, by) {
- if (transform.newScaleX < 0 && by !== "y") {
- if (transform.originX === "left") {
- transform.originX = "right";
- } else if (transform.originX === "right") {
- transform.originX = "left";
- }
- }
- if (transform.newScaleY < 0 && by !== "x") {
- if (transform.originY === "top") {
- transform.originY = "bottom";
- } else if (transform.originY === "bottom") {
- transform.originY = "top";
- }
- }
- },
- _setLocalMouse: function(localMouse, t) {
- var target = t.target, zoom = this.getZoom(), padding = target.padding / zoom;
- if (t.originX === "right") {
- localMouse.x *= -1;
- } else if (t.originX === "center") {
- localMouse.x *= t.mouseXSign * 2;
- if (localMouse.x < 0) {
- t.mouseXSign = -t.mouseXSign;
- }
- }
- if (t.originY === "bottom") {
- localMouse.y *= -1;
- } else if (t.originY === "center") {
- localMouse.y *= t.mouseYSign * 2;
- if (localMouse.y < 0) {
- t.mouseYSign = -t.mouseYSign;
- }
- }
- if (abs(localMouse.x) > padding) {
- if (localMouse.x < 0) {
- localMouse.x += padding;
- } else {
- localMouse.x -= padding;
- }
- } else {
- localMouse.x = 0;
- }
- if (abs(localMouse.y) > padding) {
- if (localMouse.y < 0) {
- localMouse.y += padding;
- } else {
- localMouse.y -= padding;
- }
- } else {
- localMouse.y = 0;
- }
- },
- _rotateObject: function(x, y) {
- var t = this._currentTransform, target = t.target, constraintPosition, constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY);
- if (target.lockRotation) {
- return false;
- }
- var lastAngle = atan2(t.ey - constraintPosition.y, t.ex - constraintPosition.x), curAngle = atan2(y - constraintPosition.y, x - constraintPosition.x), angle = radiansToDegrees(curAngle - lastAngle + t.theta), hasRotated = true;
- if (target.snapAngle > 0) {
- var snapAngle = target.snapAngle, snapThreshold = target.snapThreshold || snapAngle, rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle, leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle;
- if (Math.abs(angle - leftAngleLocked) < snapThreshold) {
- angle = leftAngleLocked;
- } else if (Math.abs(angle - rightAngleLocked) < snapThreshold) {
- angle = rightAngleLocked;
- }
- }
- if (angle < 0) {
- angle = 360 + angle;
- }
- angle %= 360;
- if (target.angle === angle) {
- hasRotated = false;
- } else {
- target.angle = angle;
- target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
- }
- return hasRotated;
- },
- setCursor: function(value) {
- this.upperCanvasEl.style.cursor = value;
- },
- _resetObjectTransform: function(target) {
- target.scaleX = 1;
- target.scaleY = 1;
- target.skewX = 0;
- target.skewY = 0;
- target.rotate(0);
- },
- _drawSelection: function(ctx) {
- var groupSelector = this._groupSelector, left = groupSelector.left, top = groupSelector.top, aleft = abs(left), atop = abs(top);
- if (this.selectionColor) {
- ctx.fillStyle = this.selectionColor;
- ctx.fillRect(groupSelector.ex - (left > 0 ? 0 : -left), groupSelector.ey - (top > 0 ? 0 : -top), aleft, atop);
- }
- if (!this.selectionLineWidth || !this.selectionBorderColor) {
- return;
- }
- ctx.lineWidth = this.selectionLineWidth;
- ctx.strokeStyle = this.selectionBorderColor;
- if (this.selectionDashArray.length > 1 && !supportLineDash) {
- var px = groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft), py = groupSelector.ey + STROKE_OFFSET - (top > 0 ? 0 : atop);
- ctx.beginPath();
- fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray);
- fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray);
- fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray);
- fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray);
- ctx.closePath();
- ctx.stroke();
- } else {
- fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray);
- ctx.strokeRect(groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft), groupSelector.ey + STROKE_OFFSET - (top > 0 ? 0 : atop), aleft, atop);
- }
- },
- findTarget: function(e, skipGroup) {
- if (this.skipTargetFind) {
- return;
- }
- var ignoreZoom = true, pointer = this.getPointer(e, ignoreZoom), activeObject = this._activeObject, aObjects = this.getActiveObjects(), activeTarget, activeTargetSubs;
- this.targets = [];
- if (aObjects.length > 1 && !skipGroup && activeObject === this._searchPossibleTargets([ activeObject ], pointer)) {
- return activeObject;
- }
- if (aObjects.length === 1 && activeObject._findTargetCorner(pointer)) {
- return activeObject;
- }
- if (aObjects.length === 1 && activeObject === this._searchPossibleTargets([ activeObject ], pointer)) {
- if (!this.preserveObjectStacking) {
- return activeObject;
- } else {
- activeTarget = activeObject;
- activeTargetSubs = this.targets;
- this.targets = [];
- }
- }
- var target = this._searchPossibleTargets(this._objects, pointer);
- if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) {
- target = activeTarget;
- this.targets = activeTargetSubs;
- }
- return target;
- },
- _checkTarget: function(pointer, obj) {
- if (obj && obj.visible && obj.evented && this.containsPoint(null, obj, pointer)) {
- if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
- var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);
- if (!isTransparent) {
- return true;
- }
- } else {
- return true;
- }
- }
- },
- _searchPossibleTargets: function(objects, pointer) {
- var target, i = objects.length, normalizedPointer, subTarget;
- while (i--) {
- if (this._checkTarget(pointer, objects[i])) {
- target = objects[i];
- if (target.subTargetCheck && target instanceof fabric.Group) {
- normalizedPointer = this._normalizePointer(target, pointer);
- subTarget = this._searchPossibleTargets(target._objects, normalizedPointer);
- subTarget && this.targets.push(subTarget);
- }
- break;
- }
- }
- return target;
- },
- restorePointerVpt: function(pointer) {
- return fabric.util.transformPoint(pointer, fabric.util.invertTransform(this.viewportTransform));
- },
- getPointer: function(e, ignoreZoom, upperCanvasEl) {
- if (!upperCanvasEl) {
- upperCanvasEl = this.upperCanvasEl;
- }
- var pointer = getPointer(e), bounds = upperCanvasEl.getBoundingClientRect(), boundsWidth = bounds.width || 0, boundsHeight = bounds.height || 0, cssScale;
- if (!boundsWidth || !boundsHeight) {
- if ("top" in bounds && "bottom" in bounds) {
- boundsHeight = Math.abs(bounds.top - bounds.bottom);
- }
- if ("right" in bounds && "left" in bounds) {
- boundsWidth = Math.abs(bounds.right - bounds.left);
- }
- }
- this.calcOffset();
- pointer.x = pointer.x - this._offset.left;
- pointer.y = pointer.y - this._offset.top;
- if (!ignoreZoom) {
- pointer = this.restorePointerVpt(pointer);
- }
- if (boundsWidth === 0 || boundsHeight === 0) {
- cssScale = {
- width: 1,
- height: 1
- };
- } else {
- cssScale = {
- width: upperCanvasEl.width / boundsWidth,
- height: upperCanvasEl.height / boundsHeight
- };
- }
- return {
- x: pointer.x * cssScale.width,
- y: pointer.y * cssScale.height
- };
- },
- _createUpperCanvas: function() {
- var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, "");
- if (this.upperCanvasEl) {
- this.upperCanvasEl.className = "";
- } else {
- this.upperCanvasEl = this._createCanvasElement();
- }
- fabric.util.addClass(this.upperCanvasEl, "upper-canvas " + lowerCanvasClass);
- this.wrapperEl.appendChild(this.upperCanvasEl);
- this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
- this._applyCanvasStyle(this.upperCanvasEl);
- this.contextTop = this.upperCanvasEl.getContext("2d");
- },
- _createCacheCanvas: function() {
- this.cacheCanvasEl = this._createCanvasElement();
- this.cacheCanvasEl.setAttribute("width", this.width);
- this.cacheCanvasEl.setAttribute("height", this.height);
- this.contextCache = this.cacheCanvasEl.getContext("2d");
- },
- _initWrapperElement: function() {
- this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, "div", {
- class: this.containerClass
- });
- fabric.util.setStyle(this.wrapperEl, {
- width: this.width + "px",
- height: this.height + "px",
- position: "relative"
- });
- fabric.util.makeElementUnselectable(this.wrapperEl);
- },
- _applyCanvasStyle: function(element) {
- var width = this.width || element.width, height = this.height || element.height;
- fabric.util.setStyle(element, {
- position: "absolute",
- width: width + "px",
- height: height + "px",
- left: 0,
- top: 0,
- "touch-action": this.allowTouchScrolling ? "manipulation" : "none"
- });
- element.width = width;
- element.height = height;
- fabric.util.makeElementUnselectable(element);
- },
- _copyCanvasStyle: function(fromEl, toEl) {
- toEl.style.cssText = fromEl.style.cssText;
- },
- getSelectionContext: function() {
- return this.contextTop;
- },
- getSelectionElement: function() {
- return this.upperCanvasEl;
- },
- getActiveObject: function() {
- return this._activeObject;
- },
- getActiveObjects: function() {
- var active = this._activeObject;
- if (active) {
- if (active.type === "activeSelection" && active._objects) {
- return active._objects.slice(0);
- } else {
- return [ active ];
- }
- }
- return [];
- },
- _onObjectRemoved: function(obj) {
- if (obj === this._activeObject) {
- this.fire("before:selection:cleared", {
- target: obj
- });
- this._discardActiveObject();
- this.fire("selection:cleared", {
- target: obj
- });
- obj.fire("deselected");
- }
- if (this._hoveredTarget === obj) {
- this._hoveredTarget = null;
- }
- this.callSuper("_onObjectRemoved", obj);
- },
- _fireSelectionEvents: function(oldObjects, e) {
- var somethingChanged = false, objects = this.getActiveObjects(), added = [], removed = [], opt = {
- e: e
- };
- oldObjects.forEach(function(oldObject) {
- if (objects.indexOf(oldObject) === -1) {
- somethingChanged = true;
- oldObject.fire("deselected", opt);
- removed.push(oldObject);
- }
- });
- objects.forEach(function(object) {
- if (oldObjects.indexOf(object) === -1) {
- somethingChanged = true;
- object.fire("selected", opt);
- added.push(object);
- }
- });
- if (oldObjects.length > 0 && objects.length > 0) {
- opt.selected = added;
- opt.deselected = removed;
- opt.updated = added[0] || removed[0];
- opt.target = this._activeObject;
- somethingChanged && this.fire("selection:updated", opt);
- } else if (objects.length > 0) {
- if (objects.length === 1) {
- opt.target = added[0];
- this.fire("object:selected", opt);
- }
- opt.selected = added;
- opt.target = this._activeObject;
- this.fire("selection:created", opt);
- } else if (oldObjects.length > 0) {
- opt.deselected = removed;
- this.fire("selection:cleared", opt);
- }
- },
- setActiveObject: function(object, e) {
- var currentActives = this.getActiveObjects();
- this._setActiveObject(object, e);
- this._fireSelectionEvents(currentActives, e);
- return this;
- },
- _setActiveObject: function(object, e) {
- if (this._activeObject === object) {
- return false;
- }
- if (!this._discardActiveObject(e, object)) {
- return false;
- }
- if (object.onSelect({
- e: e
- })) {
- return false;
- }
- this._activeObject = object;
- return true;
- },
- _discardActiveObject: function(e, object) {
- var obj = this._activeObject;
- if (obj) {
- if (obj.onDeselect({
- e: e,
- object: object
- })) {
- return false;
- }
- this._activeObject = null;
- }
- return true;
- },
- discardActiveObject: function(e) {
- var currentActives = this.getActiveObjects();
- if (currentActives.length) {
- this.fire("before:selection:cleared", {
- target: currentActives[0],
- e: e
- });
- }
- this._discardActiveObject(e);
- this._fireSelectionEvents(currentActives, e);
- return this;
- },
- dispose: function() {
- var wrapper = this.wrapperEl;
- this.removeListeners();
- wrapper.removeChild(this.upperCanvasEl);
- wrapper.removeChild(this.lowerCanvasEl);
- this.upperCanvasEl = null;
- this.cacheCanvasEl = null;
- this.contextCache = null;
- this.contextTop = null;
- if (wrapper.parentNode) {
- wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl);
- }
- delete this.wrapperEl;
- fabric.StaticCanvas.prototype.dispose.call(this);
- return this;
- },
- clear: function() {
- this.discardActiveObject();
- this.clearContext(this.contextTop);
- return this.callSuper("clear");
- },
- drawControls: function(ctx) {
- var activeObject = this._activeObject;
- if (activeObject) {
- activeObject._renderControls(ctx);
- }
- },
- _toObject: function(instance, methodName, propertiesToInclude) {
- var originalProperties = this._realizeGroupTransformOnObject(instance), object = this.callSuper("_toObject", instance, methodName, propertiesToInclude);
- this._unwindGroupTransformOnObject(instance, originalProperties);
- return object;
- },
- _realizeGroupTransformOnObject: function(instance) {
- if (instance.group && instance.group.type === "activeSelection" && this._activeObject === instance.group) {
- var layoutProps = [ "angle", "flipX", "flipY", "left", "scaleX", "scaleY", "skewX", "skewY", "top" ];
- var originalValues = {};
- layoutProps.forEach(function(prop) {
- originalValues[prop] = instance[prop];
- });
- this._activeObject.realizeTransform(instance);
- return originalValues;
- } else {
- return null;
- }
- },
- _unwindGroupTransformOnObject: function(instance, originalValues) {
- if (originalValues) {
- instance.set(originalValues);
- }
- },
- _setSVGObject: function(markup, instance, reviver) {
- var originalProperties = this._realizeGroupTransformOnObject(instance);
- this.callSuper("_setSVGObject", markup, instance, reviver);
- this._unwindGroupTransformOnObject(instance, originalProperties);
- }
- });
- for (var prop in fabric.StaticCanvas) {
- if (prop !== "prototype") {
- fabric.Canvas[prop] = fabric.StaticCanvas[prop];
- }
+ /**
+ * Sets y of this point
+ * @param {Number} y
... 32370 lines suppressed ...