=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation(),this._currentCursorOpacity=1,e.shiftKey?this.moveCursorRightWithShift(e):this.moveCursorRightWithoutShift(e),this.initDelayedCursor()},moveCursorRightWithShift:function(e){this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd?this._moveRight(e,"selectionStart"):(this._selectionDirection="right",this._moveRight(e,"selectionEnd"),this.text.charAt(this.selectionEnd-1)==="\n"&&this.selectionEnd++,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length))},moveCursorRightWithoutShift:function(e){this._selectionDirection="right",this.selectionStart===this.selectionEnd?(this._moveRight(e,"selectionStart"),this.selectionEnd=this.selectionStart):(this.selectionEnd+=this.getNumNewLinesInSelectedText(),this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length),this.selectionStart=this.selectionEnd)},removeChars:function(e){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(e):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.selectionEnd=this.selectionStart,this._removeExtraneousStyles(),this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("text:changed")},_removeCharsNearCursor:function(e){if(this.selectionStart!==0)if(e.metaKey){var t=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(t,this.selectionStart),this.selectionStart=t}else if(e.altKey){var n=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(n,this.selectionStart),this.selectionStart=n}else{var r=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(r),this.selectionStart--,this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}),fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(e,t,n,r,i,s){this.styles[t]?this._setSVGTextLineChars(e,t,n,r,i,s):this.callSuper("_setSVGTextLineText",e,t,n,r,i)},_setSVGTextLineChars:function(e,t,n,r,i,s){var o=t===0||this.useNative?"y":"dy",u=e.split(""),a=0,f=this._getSVGLineLeftOffset(t),l=this._getSVGLineTopOffset(t),c=this._getHeightOfLine(this.ctx,t);for(var h=0,p=u.length;h'].join("")},_createTextCharSpan:function(e,t,n,r,i,s){var o=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text"},t));return['',fabric.util.string.escapeXml(e)," "].join("")}}),function(){function request(e,t,n){var r=URL.parse(e);r.port||(r.port=r.protocol.indexOf("https:")===0?443:80);var i=r.port===443?HTTPS:HTTP,s=i.request({hostname:r.hostname,port:r.port,path:r.path,method:"GET"},function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})});s.on("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+r.hostname+":"+r.port):fabric.log(e.message)}),s.end()}function request_fs(e,t){var n=require("fs");n.readFile(e,function(e,n){if(e)throw fabric.log(e),e;t(n)})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=(new require("xmldom")).DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){var r=function(r){i.src=new Buffer(r,"binary"),i._src=e,t&&t.call(n,i)},i=new Image;e&&(e instanceof Buffer||e.indexOf("data")===0)?(i.src=i._src=e,t&&t.call(n,i)):e&&e.indexOf("http")!==0?request_fs(e,r):e?request(e,"binary",r):t&&t.call(n,e)},fabric.loadSVGFromURL=function(e,t,n){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),e.indexOf("http")!==0?request_fs(e,function(e){fabric.loadSVGFromString(e,t,n)}):request(e,"",function(e){fabric.loadSVGFromString(e,t,n)})},fabric.loadSVGFromString=function(e,t,n){var r=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(r.documentElement,function(e,n){t&&t(e,n)},n)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){var r=new fabric.Image(n);r._initConfig(e),r._initFilters(e,function(e){r.filters=e||[],t&&t(r)})})},fabric.createCanvasForNode=function(e,t){var n=fabric.document.createElement("canvas"),r=new Canvas(e||600,t||600);n.style={},n.width=r.width,n.height=r.height;var i=fabric.Canvas||fabric.StaticCanvas,s=new i(n);return s.contextContainer=r.getContext("2d"),s.nodeCanvas=r,s.Font=Canvas.Font,s},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(e){return this.nodeCanvas.createJPEGStream(e)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e){return origSetWidth.call(this,e),this.nodeCanvas.width=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth);var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(e){return origSetHeight.call(this,e),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}();
\ No newline at end of file
diff --git a/dist/js/motsmeles.min.js b/dist/js/motsmeles.min.js
new file mode 100644
index 0000000..b391e5c
--- /dev/null
+++ b/dist/js/motsmeles.min.js
@@ -0,0 +1,2 @@
+/*! motsmeles 2015-01-26 */
+var SIZE=30,NUMCASE=10,GRID=[["S","S","S","A","U","B","A","G","N","E"],["E","P","A","A","M","V","E","N","C","E"],["L","U","L","M","I","R","A","M","A","S"],["R","A","O","G","E","X","A","C","O","E"],["A","L","N","E","G","N","A","R","O","R"],["T","O","U","L","O","N","G","L","R","E"],["N","D","S","T","N","U","E","I","E","Y"],["I","N","P","E","E","N","I","L","D","H"],["C","A","S","S","U","J","E","R","F","L"],["E","B","E","L","S","I","S","S","A","C"]],words=["AIX","APT","ARLES","AUBAGNE","AUPS","AVIGNON","BANDOL","CANNES","CASSIS","DIGNE","FREJUS","HYERES","LUNEL","MIRAMAS","NICE","ORANGE","SALON","SORGUES","TOULON","VENCE"],reponse="BA";!function(a,b,c,d,e,f,g){var h,i,j,k,l=new a.Canvas("c",{selection:!1}),m=[],n=[],o=!1;l.setDimensions({width:c*d+d,height:c*d+d});var p=a.util.createClass(a.Rect,{type:"labeledRect",initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("label",a.label||"")},toObject:function(){return a.util.object.extend(this.callSuper("toObject"),{label:this.get("label")})},_render:function(a){this.callSuper("_render",a),a.font="20px Helvetica",a.fillStyle="#333",a.fillText(this.label,-this.width/2+5,-this.height/2+20)}}),q=function(){var a,b,c,d=-1,f=m[0]===n[0],h=m[1]===n[1],i="";if(f){for(b=Math.min(m[1],n[1]),c=Math.max(m[1],n[1]),a=b;c>=a;a++)i+=e[m[0]][a];return Math.max(g.indexOf(i),g.indexOf(i.split("").reverse().join("")))}if(h){for(b=Math.min(m[0],n[0]),c=Math.max(m[0],n[0]),a=b;c>=a;a++)i+=e[a][m[1]];return Math.max(g.indexOf(i),g.indexOf(i.split("").reverse().join("")))}return d},r=function(a){var c=b.getElementById("words").getElementsByTagName("li"),d=c.length;for(j=0;d>j;j++)if(c[j].textContent===a){c[j].className="validate";break}};for(h=0;d>h;h++)for(i=0;d>i;i++){var s=new p({left:h*c,top:i*c,fill:"white",stroke:"black",width:c,height:c,label:e[i][h],selectable:!1});l.add(s)}for(var t=0;t
Mot mélés
-
+
diff --git a/vendor/fabric.1.4.0.js b/vendor/fabric.1.4.0.js
new file mode 100644
index 0000000..dc64c6b
--- /dev/null
+++ b/vendor/fabric.1.4.0.js
@@ -0,0 +1,21142 @@
+/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` */
+/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */
+
+var fabric = fabric || { version: "1.4.0" };
+if (typeof exports !== 'undefined') {
+ exports.fabric = fabric;
+}
+
+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("");
+
+ fabric.window = fabric.document.createWindow();
+}
+
+/**
+ * True when in environment that supports touch events
+ * @type boolean
+ */
+fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
+
+/**
+ * True when in environment that's probably Node.js
+ * @type boolean
+ */
+fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
+ typeof window === 'undefined';
+
+
+/**
+ * Attributes parsed from all SVG elements
+ * @type array
+ */
+fabric.SHARED_ATTRIBUTES = [
+ "transform",
+ "fill", "fill-opacity", "fill-rule",
+ "opacity",
+ "stroke", "stroke-dasharray", "stroke-linecap",
+ "stroke-linejoin", "stroke-miterlimit",
+ "stroke-opacity", "stroke-width"
+];
+
+
+(function(){
+
+ /**
+ * @private
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ function _removeEventListener(eventName, handler) {
+ if (!this.__eventListeners[eventName]) return;
+
+ if (handler) {
+ fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
+ }
+ else {
+ this.__eventListeners[eventName].length = 0;
+ }
+ }
+
+ /**
+ * Observes specified event
+ * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
+ * @memberOf fabric.Observable
+ * @alias on
+ * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
+ * @param {Function} handler Function that receives a notification when an event of the specified type occurs
+ * @return {Self} thisArg
+ * @chainable
+ */
+ function observe(eventName, handler) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = { };
+ }
+ // one object with key/value pairs was passed
+ if (arguments.length === 1) {
+ for (var prop in eventName) {
+ this.on(prop, eventName[prop]);
+ }
+ }
+ else {
+ if (!this.__eventListeners[eventName]) {
+ this.__eventListeners[eventName] = [ ];
+ }
+ this.__eventListeners[eventName].push(handler);
+ }
+ return this;
+ }
+
+ /**
+ * Stops event observing for a particular event handler. Calling this method
+ * without arguments removes all handlers for all events
+ * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
+ * @memberOf fabric.Observable
+ * @alias off
+ * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
+ * @param {Function} handler Function to be deleted from EventListeners
+ * @return {Self} thisArg
+ * @chainable
+ */
+ function stopObserving(eventName, handler) {
+ if (!this.__eventListeners) return;
+
+ // remove all key/value pairs (event name -> event handler)
+ if (arguments.length === 0) {
+ this.__eventListeners = { };
+ }
+ // 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++) {
+ // avoiding try/catch for perf. reasons
+ listenersForEvent[i].call(this, options || { });
+ }
+ 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 = {
+
+ /**
+ * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`)
+ * Objects should be instances of (or inherit from) fabric.Object
+ * @param [...] Zero or more fabric instances
+ * @return {Self} thisArg
+ */
+ add: function () {
+ this._objects.push.apply(this._objects, arguments);
+ for (var i = arguments.length; i--; ) {
+ this._onObjectAdded(arguments[i]);
+ }
+ this.renderOnAddRemove && this.renderAll();
+ return this;
+ },
+
+ /**
+ * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
+ * An object should be an instance of (or inherit from) fabric.Object
+ * @param {Object} object Object to insert
+ * @param {Number} index Index to insert object at
+ * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
+ * @return {Self} thisArg
+ */
+ insertAt: function (object, index, nonSplicing) {
+ var objects = this.getObjects();
+ if (nonSplicing) {
+ objects[index] = object;
+ }
+ else {
+ objects.splice(index, 0, object);
+ }
+ this._onObjectAdded(object);
+ this.renderOnAddRemove && this.renderAll();
+ return this;
+ },
+
+ /**
+ * Removes an object from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
+ * @param {Object} object Object to remove
+ * @return {Self} thisArg
+ */
+ remove: function(object) {
+ var objects = this.getObjects(),
+ index = objects.indexOf(object);
+
+ // only call onObjectRemoved if an object was actually removed
+ if (index !== -1) {
+ objects.splice(index, 1);
+ this._onObjectRemoved(object);
+ }
+
+ this.renderOnAddRemove && this.renderAll();
+ return object;
+ },
+
+ /**
+ * 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.
+ * Iteration happens in reverse order (for performance reasons).
+ * 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
+ */
+ forEachObject: function(callback, context) {
+ var objects = this.getObjects(),
+ i = objects.length;
+ while (i--) {
+ callback.call(context, objects[i], i, objects);
+ }
+ return this;
+ },
+
+ /**
+ * Returns an array of children objects of this instance
+ * Type parameter introduced in 1.3.10
+ * @param {String} [type] When specified, only objects of this type are returned
+ * @return {Array}
+ */
+ getObjects: function(type) {
+ if (typeof type === 'undefined') {
+ return this._objects;
+ }
+ return this._objects.filter(function(o) {
+ return o.type === type;
+ });
+ },
+
+ /**
+ * Returns object at specified index
+ * @param {Number} index
+ * @return {Self} thisArg
+ */
+ item: function (index) {
+ return this.getObjects()[index];
+ },
+
+ /**
+ * Returns true if collection contains no objects
+ * @return {Boolean} true if collection is empty
+ */
+ isEmpty: function () {
+ return this.getObjects().length === 0;
+ },
+
+ /**
+ * Returns a size of a collection (i.e: length of an array containing its objects)
+ * @return {Number} Collection size
+ */
+ size: function() {
+ return this.getObjects().length;
+ },
+
+ /**
+ * Returns true if collection contains an object
+ * @param {Object} object Object to check against
+ * @return {Boolean} `true` if collection contains an object
+ */
+ contains: function(object) {
+ return this.getObjects().indexOf(object) > -1;
+ },
+
+ /**
+ * Returns number representation of a collection complexity
+ * @return {Number} complexity
+ */
+ complexity: function () {
+ return this.getObjects().reduce(function (memo, current) {
+ memo += current.complexity ? current.complexity() : 0;
+ return memo;
+ }, 0);
+ }
+};
+
+
+(function(global) {
+
+ var sqrt = Math.sqrt,
+ atan2 = Math.atan2,
+ PiBy180 = Math.PI / 180;
+
+ /**
+ * @namespace fabric.util
+ */
+ fabric.util = {
+
+ /**
+ * Removes value from an array.
+ * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} array
+ * @param {Any} value
+ * @return {Array} original array
+ */
+ removeFromArray: function(array, value) {
+ var idx = array.indexOf(value);
+ if (idx !== -1) {
+ array.splice(idx, 1);
+ }
+ return array;
+ },
+
+ /**
+ * Returns random number between 2 specified ones.
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} min lower limit
+ * @param {Number} max upper limit
+ * @return {Number} random value (between min and max)
+ */
+ getRandomInt: function(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ },
+
+ /**
+ * Transforms degrees to radians.
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} degrees value in degrees
+ * @return {Number} value in radians
+ */
+ degreesToRadians: function(degrees) {
+ return degrees * PiBy180;
+ },
+
+ /**
+ * Transforms radians to degrees.
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} radians value in radians
+ * @return {Number} value in degrees
+ */
+ radiansToDegrees: function(radians) {
+ return radians / PiBy180;
+ },
+
+ /**
+ * Rotates `point` around `origin` with `radians`
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Point} The point to rotate
+ * @param {fabric.Point} The origin of the rotation
+ * @param {Number} The radians of the angle for the rotation
+ * @return {fabric.Point} The new rotated point
+ */
+ rotatePoint: function(point, origin, radians) {
+ var sin = Math.sin(radians),
+ cos = Math.cos(radians);
+
+ point.subtractEquals(origin);
+
+ var rx = point.x * cos - point.y * sin,
+ ry = point.x * sin + point.y * cos;
+
+ return new fabric.Point(rx, ry).addEquals(origin);
+ },
+
+ /**
+ * 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));
+ },
+
+ /**
+ * Function which always returns `false`.
+ * @static
+ * @memberOf fabric.util
+ * @return {Boolean}
+ */
+ falseFunction: function() {
+ return false;
+ },
+
+ /**
+ * Returns klass "Class" object of given namespace
+ * @memberOf fabric.util
+ * @param {String} type Type of object (eg. 'circle')
+ * @param {String} namespace Namespace to get klass "Class" object from
+ * @return {Object} klass "Class"
+ */
+ getKlass: function(type, namespace) {
+ // capitalize first letter only
+ type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
+ return fabric.util.resolveNamespace(namespace)[type];
+ },
+
+ /**
+ * Returns object of given namespace
+ * @memberOf fabric.util
+ * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
+ * @return {Object} Object for given namespace (default fabric)
+ */
+ resolveNamespace: function(namespace) {
+ if (!namespace) return fabric;
+
+ var parts = namespace.split('.'),
+ len = parts.length,
+ obj = global || fabric.window;
+
+ for (var 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 {Any} [context] Context to invoke callback in
+ * @param {Object} [crossOrigin] crossOrigin value to set image element to
+ */
+ loadImage: function(url, callback, context, crossOrigin) {
+ if (!url) {
+ callback && callback.call(context, url);
+ return;
+ }
+
+ var img = fabric.util.createImage();
+
+ /** @ignore */
+ img.onload = function () {
+ callback && callback.call(context, img);
+ img = img.onload = img.onerror = null;
+ };
+
+ /** @ignore */
+ img.onerror = function() {
+ fabric.log('Error loading ' + img.src);
+ callback && callback.call(context, null, true);
+ img = img.onload = img.onerror = null;
+ };
+
+ // data-urls appear to be buggy with crossOrigin
+ // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
+ // see https://code.google.com/p/chromium/issues/detail?id=315152
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=935069
+ if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') {
+ img.crossOrigin = crossOrigin;
+ }
+
+ img.src = url;
+ },
+
+ /**
+ * Creates corresponding fabric instances from their object representations
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} objects Objects to enliven
+ * @param {Function} callback Callback to invoke when all objects are created
+ * @param {Function} [reviver] Method for further parsing of object elements,
+ * called after each fabric object created.
+ */
+ enlivenObjects: function(objects, callback, namespace, reviver) {
+ objects = objects || [ ];
+
+ function onLoaded() {
+ if (++numLoadedObjects === numTotalObjects) {
+ callback && callback(enlivenedObjects);
+ }
+ }
+
+ var enlivenedObjects = [ ],
+ numLoadedObjects = 0,
+ numTotalObjects = objects.length;
+
+ if (!numTotalObjects) {
+ callback && callback(enlivenedObjects);
+ return;
+ }
+
+ objects.forEach(function (o, index) {
+ // if sparse array
+ if (!o || !o.type) {
+ onLoaded();
+ return;
+ }
+ var klass = fabric.util.getKlass(o.type, namespace);
+ if (klass.async) {
+ klass.fromObject(o, function (obj, error) {
+ if (!error) {
+ enlivenedObjects[index] = obj;
+ reviver && reviver(o, enlivenedObjects[index]);
+ }
+ onLoaded();
+ });
+ }
+ else {
+ enlivenedObjects[index] = klass.fromObject(o);
+ reviver && reviver(o, enlivenedObjects[index]);
+ onLoaded();
+ }
+ });
+ },
+
+ /**
+ * Groups SVG elements (usually those retrieved from SVG document)
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} elements SVG elements to group
+ * @param {Object} [options] Options object
+ * @return {fabric.Object|fabric.PathGroup}
+ */
+ groupSVGElements: function(elements, options, path) {
+ var object;
+
+ if (elements.length > 1) {
+ object = new fabric.PathGroup(elements, options);
+ }
+ else {
+ object = elements[0];
+ }
+
+ if (typeof path !== 'undefined') {
+ object.setSourcePath(path);
+ }
+ return object;
+ },
+
+ /**
+ * Populates an object with properties of another object
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} source Source object
+ * @param {Object} destination Destination object
+ * @return {Array} properties Propertie names to include
+ */
+ populateWithProperties: function(source, destination, properties) {
+ if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
+ for (var i = 0, len = properties.length; i < len; i++) {
+ if (properties[i] in source) {
+ destination[properties[i]] = source[properties[i]];
+ }
+ }
+ }
+ },
+
+ /**
+ * Draws a dashed line between two points
+ *
+ * This method is used to draw dashed line around selection area.
+ * See dotted stroke in canvas
+ *
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x start x coordinate
+ * @param {Number} y start y coordinate
+ * @param {Number} x2 end x coordinate
+ * @param {Number} y2 end y coordinate
+ * @param {Array} da dash array pattern
+ */
+ drawDashedLine: function(ctx, x, y, x2, y2, da) {
+ var dx = x2 - x,
+ dy = y2 - y,
+ len = sqrt(dx*dx + dy*dy),
+ rot = atan2(dy, dx),
+ dc = da.length,
+ di = 0,
+ draw = true;
+
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.moveTo(0, 0);
+ ctx.rotate(rot);
+
+ x = 0;
+ while (len > x) {
+ x += da[di++ % dc];
+ if (x > len) {
+ x = len;
+ }
+ ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
+ draw = !draw;
+ }
+
+ ctx.restore();
+ },
+
+ /**
+ * Creates canvas element and initializes it via excanvas if necessary
+ * @static
+ * @memberOf fabric.util
+ * @param {CanvasElement} [canvasEl] optional canvas element to initialize;
+ * when not given, element is created implicitly
+ * @return {CanvasElement} initialized canvas element
+ */
+ createCanvasElement: function(canvasEl) {
+ canvasEl || (canvasEl = fabric.document.createElement('canvas'));
+ if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
+ G_vmlCanvasManager.initElement(canvasEl);
+ }
+ return canvasEl;
+ },
+
+ /**
+ * Creates image element (works on client and node)
+ * @static
+ * @memberOf fabric.util
+ * @return {HTMLImageElement} HTML image element
+ */
+ createImage: function() {
+ return fabric.isLikelyNode
+ ? new (require('canvas').Image)()
+ : fabric.document.createElement('img');
+ },
+
+ /**
+ * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} klass "Class" to create accessors for
+ */
+ createAccessors: function(klass) {
+ var proto = klass.prototype;
+
+ for (var i = proto.stateProperties.length; i--; ) {
+
+ var propName = proto.stateProperties[i],
+ capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1),
+ setterName = 'set' + capitalizedPropName,
+ getterName = 'get' + capitalizedPropName;
+
+ // using `new Function` for better introspection
+ if (!proto[getterName]) {
+ proto[getterName] = (function(property) {
+ return new Function('return this.get("' + property + '")');
+ })(propName);
+ }
+ if (!proto[setterName]) {
+ proto[setterName] = (function(property) {
+ return new Function('value', 'return this.set("' + property + '", value)');
+ })(propName);
+ }
+ }
+ },
+
+ /**
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Object} receiver Object implementing `clipTo` method
+ * @param {CanvasRenderingContext2D} ctx Context to clip
+ */
+ clipContext: function(receiver, ctx) {
+ ctx.save();
+ ctx.beginPath();
+ receiver.clipTo(ctx);
+ ctx.clip();
+ },
+
+ /**
+ * Multiply matrix A by matrix B to nest transformations
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} matrixA First transformMatrix
+ * @param {Array} matrixB Second transformMatrix
+ * @return {Array} The product of the two transform matrices
+ */
+ multiplyTransformMatrices: function(matrixA, matrixB) {
+ // Matrix multiply matrixA * matrixB
+ var a = [
+ [matrixA[0], matrixA[2], matrixA[4]],
+ [matrixA[1], matrixA[3], matrixA[5]],
+ [0 , 0 , 1 ]
+ ];
+
+ var b = [
+ [matrixB[0], matrixB[2], matrixB[4]],
+ [matrixB[1], matrixB[3], matrixB[5]],
+ [0 , 0 , 1 ]
+ ];
+
+ var result = [];
+ for (var r=0; r<3; r++) {
+ result[r] = [];
+ for (var c=0; c<3; c++) {
+ var sum = 0;
+ for (var k=0; k<3; k++) {
+ sum += a[r][k]*b[k][c];
+ }
+
+ result[r][c] = sum;
+ }
+ }
+
+ return [
+ result[0][0],
+ result[1][0],
+ result[0][1],
+ result[1][1],
+ result[0][2],
+ result[1][2]
+ ];
+ },
+
+ /**
+ * 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];
+ },
+
+ /**
+ * Normalizes polygon/polyline points according to their dimensions
+ * @param {Array} points
+ * @param {Object} options
+ */
+ normalizePoints: function(points, options) {
+ var minX = fabric.util.array.min(points, 'x'),
+ minY = fabric.util.array.min(points, 'y');
+
+ minX = minX < 0 ? minX : 0;
+ minY = minX < 0 ? minY : 0;
+
+ for (var i = 0, len = points.length; i < len; i++) {
+ // normalize coordinates, according to containing box
+ // (dimensions of which are passed via `options`)
+ points[i].x -= (options.width / 2 + minX) || 0;
+ points[i].y -= (options.height / 2 + minY) || 0;
+ }
+ },
+
+ /**
+ * Returns true if context has transparent pixel
+ * at specified location (taking tolerance into account)
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x x coordinate
+ * @param {Number} y y coordinate
+ * @param {Number} tolerance Tolerance
+ */
+ isTransparent: function(ctx, x, y, tolerance) {
+
+ // If tolerance is > 0 adjust start coords to take into account.
+ // If moves off Canvas fix to 0
+ if (tolerance > 0) {
+ if (x > tolerance) {
+ x -= tolerance;
+ }
+ else {
+ x = 0;
+ }
+ if (y > tolerance) {
+ y -= tolerance;
+ }
+ else {
+ y = 0;
+ }
+ }
+
+ var _isTransparent = true;
+ var imageData = ctx.getImageData(
+ x, y, (tolerance * 2) || 1, (tolerance * 2) || 1);
+
+ // Split image data - for tolerance > 1, pixelDataSize = 4;
+ for (var i = 3, l = imageData.data.length; i < l; i += 4) {
+ var temp = imageData.data[i];
+ _isTransparent = temp <= 0;
+ if (_isTransparent === false) break; // Stop if colour found
+ }
+
+ imageData = null;
+
+ return _isTransparent;
+ }
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function() {
+
+ var arcToSegmentsCache = { },
+ segmentToBezierCache = { },
+ _join = Array.prototype.join,
+ argsString;
+
+ // Generous contribution by Raph Levien, from libsvg-0.1.0.tar.gz
+ function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
+
+ argsString = _join.call(arguments);
+
+ if (arcToSegmentsCache[argsString]) {
+ return arcToSegmentsCache[argsString];
+ }
+
+ var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y);
+
+ var d = (coords.x1-coords.x0) * (coords.x1-coords.x0) +
+ (coords.y1-coords.y0) * (coords.y1-coords.y0);
+
+ var sfactor_sq = 1 / d - 0.25;
+ if (sfactor_sq < 0) sfactor_sq = 0;
+
+ var sfactor = Math.sqrt(sfactor_sq);
+ if (sweep === large) sfactor = -sfactor;
+
+ var xc = 0.5 * (coords.x0 + coords.x1) - sfactor * (coords.y1-coords.y0);
+ var yc = 0.5 * (coords.y0 + coords.y1) + sfactor * (coords.x1-coords.x0);
+
+ var th0 = Math.atan2(coords.y0-yc, coords.x0-xc);
+ var th1 = Math.atan2(coords.y1-yc, coords.x1-xc);
+
+ var th_arc = th1-th0;
+ if (th_arc < 0 && sweep === 1) {
+ th_arc += 2*Math.PI;
+ }
+ else if (th_arc > 0 && sweep === 0) {
+ th_arc -= 2 * Math.PI;
+ }
+
+ var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
+ var result = [];
+ for (var i=0; i 1) {
+ pl = Math.sqrt(pl);
+ rx *= pl;
+ ry *= pl;
+ }
+
+ var a00 = cos_th / rx;
+ var a01 = sin_th / rx;
+ var a10 = (-sin_th) / ry;
+ var a11 = (cos_th) / ry;
+
+ return {
+ x0: a00 * ox + a01 * oy,
+ y0: a10 * ox + a11 * oy,
+ x1: a00 * x + a01 * y,
+ y1: a10 * x + a11 * y,
+ sin_th: sin_th,
+ cos_th: cos_th
+ };
+ }
+
+ function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
+ argsString = _join.call(arguments);
+ if (segmentToBezierCache[argsString]) {
+ return segmentToBezierCache[argsString];
+ }
+
+ var a00 = cos_th * rx;
+ var a01 = -sin_th * ry;
+ var a10 = sin_th * rx;
+ var a11 = cos_th * ry;
+
+ var th_half = 0.5 * (th1 - th0);
+ var t = (8/3) * Math.sin(th_half * 0.5) *
+ Math.sin(th_half * 0.5) / Math.sin(th_half);
+
+ var x1 = cx + Math.cos(th0) - t * Math.sin(th0);
+ var y1 = cy + Math.sin(th0) + t * Math.cos(th0);
+ var x3 = cx + Math.cos(th1);
+ var y3 = cy + Math.sin(th1);
+ var x2 = x3 + t * Math.sin(th1);
+ var y2 = y3 - t * Math.cos(th1);
+
+ segmentToBezierCache[argsString] = [
+ a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
+ a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
+ a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
+ ];
+
+ return segmentToBezierCache[argsString];
+ }
+
+ /**
+ * Draws arc
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Array} coords
+ */
+ fabric.util.drawArc = function(ctx, x, y, coords) {
+ var rx = coords[0];
+ var ry = coords[1];
+ var rot = coords[2];
+ var large = coords[3];
+ var sweep = coords[4];
+ var ex = coords[5];
+ var ey = coords[6];
+ var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
+ for (var i=0; i>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = 0;
+ if (arguments.length > 0) {
+ n = Number(arguments[1]);
+ if (n !== n) { // shortcut for verifying if it's NaN
+ n = 0;
+ }
+ else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+ for (; k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ return -1;
+ };
+ }
+
+ if (!Array.prototype.forEach) {
+ /**
+ * Iterates an array, invoking callback for each element
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.forEach = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ fn.call(context, this[i], i, this);
+ }
+ }
+ };
+ }
+
+ if (!Array.prototype.map) {
+ /**
+ * Returns a result of iterating over an array, invoking callback for each element
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.map = function(fn, context) {
+ var result = [ ];
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ result[i] = fn.call(context, this[i], i, this);
+ }
+ }
+ return result;
+ };
+ }
+
+ if (!Array.prototype.every) {
+ /**
+ * Returns true if a callback returns truthy value for all elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Boolean}
+ */
+ Array.prototype.every = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && !fn.call(context, this[i], i, this)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ if (!Array.prototype.some) {
+ /**
+ * Returns true if a callback returns truthy value for at least one element in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Boolean}
+ */
+ Array.prototype.some = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && fn.call(context, this[i], i, this)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+
+ if (!Array.prototype.filter) {
+ /**
+ * Returns the result of iterating over elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.filter = function(fn, context) {
+ var result = [ ], val;
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ val = this[i]; // in case fn mutates this
+ if (fn.call(context, val, i, this)) {
+ result.push(val);
+ }
+ }
+ }
+ return result;
+ };
+ }
+
+ if (!Array.prototype.reduce) {
+ /**
+ * Returns "folded" (reduced) result of iterating over elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Any}
+ */
+ Array.prototype.reduce = function(fn /*, initial*/) {
+ var len = this.length >>> 0,
+ i = 0,
+ rv;
+
+ if (arguments.length > 1) {
+ rv = arguments[1];
+ }
+ else {
+ do {
+ if (i in this) {
+ rv = this[i++];
+ break;
+ }
+ // if array contains no values, no initial value to return
+ if (++i >= len) {
+ throw new TypeError();
+ }
+ }
+ while (true);
+ }
+ for (; i < len; i++) {
+ if (i in this) {
+ rv = fn.call(null, rv, this[i], i, this);
+ }
+ }
+ return rv;
+ };
+ }
+
+ /* _ES5_COMPAT_END_ */
+
+ /**
+ * Invokes method on all items in a given array
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} method Name of a method to invoke
+ * @return {Array}
+ */
+ function invoke(array, method) {
+ var args = slice.call(arguments, 2), result = [ ];
+ for (var i = 0, len = array.length; i < len; i++) {
+ result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Finds maximum value in array (not necessarily "first" one)
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ * @return {Any}
+ */
+ 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 {Any}
+ */
+ function min(array, byProperty) {
+ return find(array, byProperty, function(value1, value2) {
+ return value1 < value2;
+ });
+ }
+
+ /**
+ * @private
+ */
+ function find(array, byProperty, condition) {
+ if (!array || array.length === 0) return undefined;
+
+ var i = array.length - 1,
+ result = byProperty ? array[i][byProperty] : array[i];
+ if (byProperty) {
+ while (i--) {
+ if (condition(array[i][byProperty], result)) {
+ result = array[i][byProperty];
+ }
+ }
+ }
+ else {
+ while (i--) {
+ if (condition(array[i], result)) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @namespace fabric.util.array
+ */
+ fabric.util.array = {
+ invoke: invoke,
+ min: min,
+ max: max
+ };
+
+})();
+
+
+(function(){
+
+ /**
+ * Copies all enumerable properties of one object to another
+ * @memberOf fabric.util.object
+ * @param {Object} destination Where to copy to
+ * @param {Object} source Where to copy from
+ * @return {Object}
+ */
+ function extend(destination, source) {
+ // JScript DontEnum bug is not taken care of
+ for (var property in source) {
+ destination[property] = source[property];
+ }
+ return destination;
+ }
+
+ /**
+ * Creates an empty object and copies all enumerable properties of another object to it
+ * @memberOf fabric.util.object
+ * @param {Object} object Object to clone
+ * @return {Object}
+ */
+ function clone(object) {
+ return extend({ }, object);
+ }
+
+ /** @namespace fabric.util.object */
+ fabric.util.object = {
+ extend: extend,
+ clone: clone
+ };
+
+})();
+
+
+(function() {
+
+/* _ES5_COMPAT_START_ */
+if (!String.prototype.trim) {
+ /**
+ * Trims a string (removing whitespace from the beginning and the end)
+ * @function external:String#trim
+ * @see String#trim on MDN
+ */
+ String.prototype.trim = function () {
+ // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
+ return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
+ };
+}
+/* _ES5_COMPAT_END_ */
+
+/**
+ * Camelizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to camelize
+ * @return {String} Camelized version of a string
+ */
+function camelize(string) {
+ return string.replace(/-+(.)?/g, function(match, character) {
+ return character ? character.toUpperCase() : '';
+ });
+}
+
+/**
+ * Capitalizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to capitalize
+ * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
+ * and other letters stay untouched, if false first letter is capitalized
+ * and other letters are converted to lowercase.
+ * @return {String} Capitalized version of a string
+ */
+function capitalize(string, firstLetterOnly) {
+ return string.charAt(0).toUpperCase() +
+ (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
+}
+
+/**
+ * Escapes XML in a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to escape
+ * @return {String} Escaped version of a string
+ */
+function escapeXml(string) {
+ return string.replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(//g, '>');
+}
+
+/**
+ * String utilities
+ * @namespace fabric.util.string
+ */
+fabric.util.string = {
+ camelize: camelize,
+ capitalize: capitalize,
+ escapeXml: escapeXml
+};
+}());
+
+
+/* _ES5_COMPAT_START_ */
+(function() {
+
+ var slice = Array.prototype.slice,
+ apply = Function.prototype.apply,
+ Dummy = function() { };
+
+ if (!Function.prototype.bind) {
+ /**
+ * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
+ * @see Function#bind on MDN
+ * @param {Object} thisArg Object to bind function to
+ * @param {Any[]} [...] Values to pass to a bound function
+ * @return {Function}
+ */
+ Function.prototype.bind = function(thisArg) {
+ var fn = this, args = slice.call(arguments, 1), bound;
+ if (args.length) {
+ bound = function() {
+ return apply.call(fn, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
+ };
+ }
+ else {
+ /** @ignore */
+ bound = function() {
+ return apply.call(fn, this instanceof Dummy ? this : thisArg, arguments);
+ };
+ }
+ Dummy.prototype = this.prototype;
+ bound.prototype = new Dummy();
+
+ return bound;
+ };
+ }
+
+})();
+/* _ES5_COMPAT_END_ */
+
+
+(function() {
+
+ var slice = Array.prototype.slice, emptyFunction = function() { };
+
+ var IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') return false;
+ }
+ return true;
+ })();
+
+ /** @ignore */
+ var addMethods = function(klass, source, parent) {
+ for (var property in source) {
+
+ if (property in klass.prototype &&
+ typeof klass.prototype[property] === 'function' &&
+ (source[property] + '').indexOf('callSuper') > -1) {
+
+ klass.prototype[property] = (function(property) {
+ return function() {
+
+ var superclass = this.constructor.superclass;
+ this.constructor.superclass = parent;
+ var returnValue = source[property].apply(this, arguments);
+ this.constructor.superclass = superclass;
+
+ if (property !== 'initialize') {
+ return returnValue;
+ }
+ };
+ })(property);
+ }
+ else {
+ klass.prototype[property] = source[property];
+ }
+
+ if (IS_DONTENUM_BUGGY) {
+ if (source.toString !== Object.prototype.toString) {
+ klass.prototype.toString = source.toString;
+ }
+ if (source.valueOf !== Object.prototype.valueOf) {
+ klass.prototype.valueOf = source.valueOf;
+ }
+ }
+ }
+ };
+
+ function Subclass() { }
+
+ function callSuper(methodName) {
+ var fn = this.constructor.superclass.prototype[methodName];
+ return (arguments.length > 1)
+ ? fn.apply(this, slice.call(arguments, 1))
+ : fn.call(this);
+ }
+
+ /**
+ * Helper for creation of "classes".
+ * @memberOf fabric.util
+ * @param parent optional "Class" to inherit from
+ * @param properties Properties shared by all instances of this class
+ * (be careful modifying objects defined here as this would affect all instances)
+ */
+ function createClass() {
+ var parent = null,
+ properties = slice.call(arguments, 0);
+
+ if (typeof properties[0] === 'function') {
+ parent = properties.shift();
+ }
+ function klass() {
+ this.initialize.apply(this, arguments);
+ }
+
+ klass.superclass = parent;
+ klass.subclasses = [ ];
+
+ if (parent) {
+ Subclass.prototype = parent.prototype;
+ klass.prototype = new Subclass();
+ parent.subclasses.push(klass);
+ }
+ for (var i = 0, length = properties.length; i < length; i++) {
+ addMethods(klass, properties[i], parent);
+ }
+ if (!klass.prototype.initialize) {
+ klass.prototype.initialize = emptyFunction;
+ }
+ klass.prototype.constructor = klass;
+ klass.prototype.callSuper = callSuper;
+ return klass;
+ }
+
+ fabric.util.createClass = createClass;
+})();
+
+
+(function () {
+
+ var unknown = 'unknown';
+
+ /* EVENT HANDLING */
+
+ function areHostMethods(object) {
+ var methodNames = Array.prototype.slice.call(arguments, 1),
+ t, i, len = methodNames.length;
+ for (i = 0; i < len; i++) {
+ t = typeof object[methodNames[i]];
+ if (!(/^(?:function|object|unknown)$/).test(t)) return false;
+ }
+ return true;
+ }
+ var getUniqueId = (function () {
+ var uid = 0;
+ return function (element) {
+ return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
+ };
+ })();
+
+ /** @ignore */
+ var getElement, setElement;
+
+ (function () {
+ var elements = { };
+ /** @ignore */
+ getElement = function (uid) {
+ return elements[uid];
+ };
+ /** @ignore */
+ setElement = function (uid, element) {
+ elements[uid] = element;
+ };
+ })();
+
+ function createListener(uid, handler) {
+ return {
+ handler: handler,
+ wrappedHandler: createWrappedHandler(uid, handler)
+ };
+ }
+
+ function createWrappedHandler(uid, handler) {
+ return function (e) {
+ handler.call(getElement(uid), e || fabric.window.event);
+ };
+ }
+
+ function createDispatcher(uid, eventName) {
+ return function (e) {
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ handlersForEvent[i].call(this, e || fabric.window.event);
+ }
+ }
+ };
+ }
+
+ var shouldUseAddListenerRemoveListener = (
+ areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
+ areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
+
+ shouldUseAttachEventDetachEvent = (
+ areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
+ areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
+
+ // IE branch
+ listeners = { },
+
+ // DOM L0 branch
+ handlers = { },
+
+ addListener, removeListener;
+
+ if (shouldUseAddListenerRemoveListener) {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ element.addEventListener(eventName, handler, false);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ element.removeEventListener(eventName, handler, false);
+ };
+ }
+
+ else if (shouldUseAttachEventDetachEvent) {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ setElement(uid, element);
+ if (!listeners[uid]) {
+ listeners[uid] = { };
+ }
+ if (!listeners[uid][eventName]) {
+ listeners[uid][eventName] = [ ];
+
+ }
+ var listener = createListener(uid, handler);
+ listeners[uid][eventName].push(listener);
+ element.attachEvent('on' + eventName, listener.wrappedHandler);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element), listener;
+ if (listeners[uid] && listeners[uid][eventName]) {
+ for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
+ listener = listeners[uid][eventName][i];
+ if (listener && listener.handler === handler) {
+ element.detachEvent('on' + eventName, listener.wrappedHandler);
+ listeners[uid][eventName][i] = null;
+ }
+ }
+ }
+ };
+ }
+ else {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ if (!handlers[uid]) {
+ handlers[uid] = { };
+ }
+ if (!handlers[uid][eventName]) {
+ handlers[uid][eventName] = [ ];
+ var existingHandler = element['on' + eventName];
+ if (existingHandler) {
+ handlers[uid][eventName].push(existingHandler);
+ }
+ element['on' + eventName] = createDispatcher(uid, eventName);
+ }
+ handlers[uid][eventName].push(handler);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ if (handlersForEvent[i] === handler) {
+ handlersForEvent.splice(i, 1);
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Adds an event listener to an element
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ fabric.util.addListener = addListener;
+
+ /**
+ * Removes an event listener from an element
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ fabric.util.removeListener = removeListener;
+
+ /**
+ * Cross-browser wrapper for getting event's coordinates
+ * @memberOf fabric.util
+ * @param {Event} event Event object
+ * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn
+ */
+ function getPointer(event, upperCanvasEl) {
+ event || (event = fabric.window.event);
+
+ var element = event.target ||
+ (typeof event.srcElement !== unknown ? event.srcElement : null);
+
+ var scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl);
+
+ return {
+ x: pointerX(event) + scroll.left,
+ y: pointerY(event) + scroll.top
+ };
+ }
+
+ var pointerX = function(event) {
+ // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
+ // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
+ // need to investigate later
+ return (typeof event.clientX !== unknown ? event.clientX : 0);
+ };
+
+ var pointerY = function(event) {
+ return (typeof event.clientY !== unknown ? event.clientY : 0);
+ };
+
+ function _getPointer(event, pageProp, clientProp) {
+ var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
+
+ return (event[touchProp] && event[touchProp][0]
+ ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]))
+ || event[clientProp]
+ : event[clientProp]);
+ }
+
+ if (fabric.isTouchSupported) {
+ pointerX = function(event) {
+ return _getPointer(event, 'pageX', 'clientX');
+ };
+ pointerY = function(event) {
+ return _getPointer(event, 'pageY', 'clientY');
+ };
+ }
+
+ fabric.util.getPointer = getPointer;
+
+ fabric.util.object.extend(fabric.util, fabric.Observable);
+
+})();
+
+
+(function () {
+
+ /**
+ * Cross-browser wrapper for setting element's style
+ * @memberOf fabric.util
+ * @param {HTMLElement} element
+ * @param {Object} styles
+ * @return {HTMLElement} Element that was passed as a first argument
+ */
+ function setStyle(element, styles) {
+ var elementStyle = element.style;
+ if (!elementStyle) {
+ return element;
+ }
+ if (typeof styles === 'string') {
+ element.style.cssText += ';' + styles;
+ return styles.indexOf('opacity') > -1
+ ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1])
+ : element;
+ }
+ for (var property in styles) {
+ if (property === 'opacity') {
+ setOpacity(element, styles[property]);
+ }
+ else {
+ var normalizedProperty = (property === 'float' || property === 'cssFloat')
+ ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat')
+ : property;
+ elementStyle[normalizedProperty] = styles[property];
+ }
+ }
+ return element;
+ }
+
+ var parseEl = fabric.document.createElement('div'),
+ supportsOpacity = typeof parseEl.style.opacity === 'string',
+ supportsFilters = typeof parseEl.style.filter === 'string',
+ reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
+
+ /** @ignore */
+ setOpacity = function (element) { return element; };
+
+ if (supportsOpacity) {
+ /** @ignore */
+ setOpacity = function(element, value) {
+ element.style.opacity = value;
+ return element;
+ };
+ }
+ else if (supportsFilters) {
+ /** @ignore */
+ setOpacity = function(element, value) {
+ var es = element.style;
+ if (element.currentStyle && !element.currentStyle.hasLayout) {
+ es.zoom = 1;
+ }
+ if (reOpacity.test(es.filter)) {
+ value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');
+ es.filter = es.filter.replace(reOpacity, value);
+ }
+ else {
+ es.filter += ' alpha(opacity=' + (value * 100) + ')';
+ }
+ return element;
+ };
+ }
+
+ fabric.util.setStyle = setStyle;
+
+})();
+
+
+(function() {
+
+ var _slice = Array.prototype.slice;
+
+ /**
+ * Takes id and returns an element with that id (if one exists in a document)
+ * @memberOf fabric.util
+ * @param {String|HTMLElement} id
+ * @return {HTMLElement|null}
+ */
+ function getById(id) {
+ return typeof id === 'string' ? fabric.document.getElementById(id) : id;
+ }
+
+ /**
+ * Converts an array-like object (e.g. arguments or NodeList) to an array
+ * @memberOf fabric.util
+ * @param {Object} arrayLike
+ * @return {Array}
+ */
+ var toArray = function(arrayLike) {
+ return _slice.call(arrayLike, 0);
+ };
+
+ var sliceCanConvertNodelists;
+ 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.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;
+ }
+
+ function getScrollLeftTop(element, upperCanvasEl) {
+
+ var firstFixedAncestor,
+ origElement,
+ left = 0,
+ top = 0,
+ docElement = fabric.document.documentElement,
+ body = fabric.document.body || {
+ scrollLeft: 0, scrollTop: 0
+ };
+
+ origElement = element;
+
+ while (element && element.parentNode && !firstFixedAncestor) {
+
+ element = element.parentNode;
+
+ if (element !== fabric.document &&
+ fabric.util.getElementStyle(element, 'position') === 'fixed') {
+ firstFixedAncestor = element;
+ }
+
+ if (element !== fabric.document &&
+ origElement !== upperCanvasEl &&
+ fabric.util.getElementStyle(element, 'position') === 'absolute') {
+ left = 0;
+ top = 0;
+ }
+ else 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;
+ }
+ }
+
+ return { left: left, top: top };
+ }
+
+ /**
+ * Returns offset for a given element
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to get offset for
+ * @return {Object} Object with "left" and "top" properties
+ */
+ function getElementOffset(element) {
+ var docElem,
+ box = {left: 0, top: 0},
+ doc = element && element.ownerDocument,
+ offset = {left: 0, top: 0},
+ scrollLeftTop,
+ offsetAttributes = {
+ 'borderLeftWidth': 'left',
+ 'borderTopWidth': 'top',
+ 'paddingLeft': 'left',
+ 'paddingTop': 'top'
+ };
+
+ if (!doc){
+ return {left: 0, top: 0};
+ }
+
+ 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 = fabric.util.getScrollLeftTop(element, null);
+
+ 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.
+ */
+ function getElementStyle(element, attr) {
+ if (!element.style) {
+ element.style = { };
+ }
+
+ if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
+ return fabric.document.defaultView.getComputedStyle(element, null)[attr];
+ }
+ else {
+ var value = element.style[attr];
+ if (!value && element.currentStyle) value = element.currentStyle[attr];
+ return value;
+ }
+ }
+
+ (function () {
+ var style = fabric.document.documentElement.style;
+
+ var selectProp = 'userSelect' in style
+ ? 'userSelect'
+ : 'MozUserSelect' in style
+ ? 'MozUserSelect'
+ : 'WebkitUserSelect' in style
+ ? 'WebkitUserSelect'
+ : 'KhtmlUserSelect' in style
+ ? 'KhtmlUserSelect'
+ : '';
+
+ /**
+ * Makes element unselectable
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to make unselectable
+ * @return {HTMLElement} Element that was passed in
+ */
+ function makeElementUnselectable(element) {
+ if (typeof element.onselectstart !== 'undefined') {
+ element.onselectstart = fabric.util.falseFunction;
+ }
+ if (selectProp) {
+ element.style[selectProp] = 'none';
+ }
+ else if (typeof element.unselectable === 'string') {
+ element.unselectable = 'on';
+ }
+ return element;
+ }
+
+ /**
+ * Makes element selectable
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to make selectable
+ * @return {HTMLElement} Element that was passed in
+ */
+ function makeElementSelectable(element) {
+ if (typeof element.onselectstart !== 'undefined') {
+ element.onselectstart = null;
+ }
+ if (selectProp) {
+ element.style[selectProp] = '';
+ }
+ else if (typeof element.unselectable === 'string') {
+ element.unselectable = '';
+ }
+ return element;
+ }
+
+ fabric.util.makeElementUnselectable = makeElementUnselectable;
+ fabric.util.makeElementSelectable = makeElementSelectable;
+ })();
+
+ (function() {
+
+ /**
+ * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
+ * @memberOf fabric.util
+ * @param {String} url URL of a script to load
+ * @param {Function} callback Callback to execute when script is finished loading
+ */
+ function getScript(url, callback) {
+ var headEl = fabric.document.getElementsByTagName("head")[0],
+ scriptEl = fabric.document.createElement('script'),
+ loading = true;
+
+ /** @ignore */
+ scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
+ if (loading) {
+ if (typeof this.readyState === 'string' &&
+ this.readyState !== 'loaded' &&
+ this.readyState !== 'complete') return;
+ loading = false;
+ callback(e || fabric.window.event);
+ scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
+ }
+ };
+ scriptEl.src = url;
+ headEl.appendChild(scriptEl);
+ // causes issue in Opera
+ // headEl.removeChild(scriptEl);
+ }
+
+ fabric.util.getScript = getScript;
+ })();
+
+ fabric.util.getById = getById;
+ fabric.util.toArray = toArray;
+ fabric.util.makeElement = makeElement;
+ fabric.util.addClass = addClass;
+ fabric.util.wrapElement = wrapElement;
+ fabric.util.getScrollLeftTop = getScrollLeftTop;
+ fabric.util.getElementOffset = getElementOffset;
+ fabric.util.getElementStyle = getElementStyle;
+
+})();
+
+
+(function(){
+
+ function addParamToUrl(url, param) {
+ return url + (/\?/.test(url) ? '&' : '?') + param;
+ }
+
+ var makeXHR = (function() {
+ var factories = [
+ function() { return new ActiveXObject("Microsoft.XMLHTTP"); },
+ function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
+ function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); },
+ function() { return new XMLHttpRequest(); }
+ ];
+ for (var i = factories.length; i--; ) {
+ try {
+ var req = factories[i]();
+ if (req) {
+ return factories[i];
+ }
+ }
+ catch (err) { }
+ }
+ })();
+
+ function emptyFn() { }
+
+ /**
+ * Cross-browser abstraction for sending XMLHttpRequest
+ * @memberOf fabric.util
+ * @param {String} url URL to send XMLHttpRequest to
+ * @param {Object} [options] Options object
+ * @param {String} [options.method="GET"]
+ * @param {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;
+
+ /** @ignore */
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ onComplete(xhr);
+ xhr.onreadystatechange = emptyFn;
+ }
+ };
+
+ if (method === 'GET') {
+ body = null;
+ if (typeof options.parameters === 'string') {
+ url = addParamToUrl(url, options.parameters);
+ }
+ }
+
+ xhr.open(method, url, true);
+
+ if (method === 'POST' || method === 'PUT') {
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ }
+
+ xhr.send(body);
+ return xhr;
+ }
+
+ fabric.util.request = request;
+})();
+
+
+/**
+ * Wrapper around `console.log` (when available)
+ * @param {Any} values Values to log
+ */
+fabric.log = function() { };
+
+/**
+ * Wrapper around `console.warn` (when available)
+ * @param {Any} Values to log as a warning
+ */
+fabric.warn = function() { };
+
+if (typeof console !== 'undefined') {
+ ['log', 'warn'].forEach(function(methodName) {
+ if (typeof console[methodName] !== 'undefined' && console[methodName].apply) {
+ fabric[methodName] = function() {
+ return console[methodName].apply(console, arguments);
+ };
+ }
+ });
+}
+
+
+(function() {
+
+ /**
+ * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
+ * @memberOf fabric.util
+ * @param {Object} [options] Animation options
+ * @param {Function} [options.onChange] Callback; invoked on every value change
+ * @param {Function} [options.onComplete] Callback; invoked when value change is completed
+ * @param {Number} [options.startValue=0] Starting value
+ * @param {Number} [options.endValue=100] Ending value
+ * @param {Number} [options.byValue=100] Value to modify the property by
+ * @param {Function} [options.easing] Easing function
+ * @param {Number} [options.duration=500] Duration of change
+ */
+ function animate(options) {
+
+ requestAnimFrame(function(timestamp) {
+ options || (options = { });
+
+ var start = timestamp || +new Date(),
+ duration = options.duration || 500,
+ finish = start + duration, time,
+ onChange = options.onChange || function() { },
+ abort = options.abort || function() { return false; },
+ easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t/d * (Math.PI/2)) + c + b;},
+ startValue = 'startValue' in options ? options.startValue : 0,
+ endValue = 'endValue' in options ? options.endValue : 100,
+ byValue = options.byValue || endValue - startValue;
+
+ options.onStart && options.onStart();
+
+ (function tick(ticktime) {
+ time = ticktime || +new Date();
+ var currentTime = time > finish ? duration : (time - start);
+ if (abort()) {
+ options.onComplete && options.onComplete();
+ return;
+ }
+ onChange(easing(currentTime, startValue, byValue, duration));
+ if (time > finish) {
+ options.onComplete && options.onComplete();
+ return;
+ }
+ requestAnimFrame(tick);
+ })(start);
+ });
+
+ }
+
+ var _requestAnimFrame = fabric.window.requestAnimationFrame ||
+ fabric.window.webkitRequestAnimationFrame ||
+ fabric.window.mozRequestAnimationFrame ||
+ fabric.window.oRequestAnimationFrame ||
+ fabric.window.msRequestAnimationFrame ||
+ function(callback) {
+ fabric.window.setTimeout(callback, 1000 / 60);
+ };
+ /**
+ * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
+ * @memberOf fabric.util
+ * @param {Function} callback Callback to invoke
+ * @param {DOMElement} element optional Element to associate with animation
+ */
+ var requestAnimFrame = function() {
+ return _requestAnimFrame.apply(fabric.window, arguments);
+ };
+
+ fabric.util.animate = animate;
+ fabric.util.requestAnimFrame = requestAnimFrame;
+
+})();
+
+
+(function() {
+
+ function normalize(a, c, p, s) {
+ if (a < Math.abs(c)) { a=c; s=p/4; }
+ else s = p/(2*Math.PI) * Math.asin (c/a);
+ return { a: a, c: c, p: p, s: s };
+ }
+
+ function elastic(opts, t, d) {
+ return opts.a *
+ Math.pow(2, 10 * (t -= 1)) *
+ Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
+ }
+
+ /**
+ * Cubic easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutCubic(t, b, c, d) {
+ return c*((t=t/d-1)*t*t + 1) + b;
+ }
+
+ /**
+ * Cubic easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutCubic(t, b, c, d) {
+ t /= d/2;
+ if (t < 1) return c/2*t*t*t + b;
+ return c/2*((t-=2)*t*t + 2) + b;
+ }
+
+ /**
+ * Quartic easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInQuart(t, b, c, d) {
+ return c*(t/=d)*t*t*t + b;
+ }
+
+ /**
+ * Quartic easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutQuart(t, b, c, d) {
+ return -c * ((t=t/d-1)*t*t*t - 1) + b;
+ }
+
+ /**
+ * Quartic easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutQuart(t, b, c, d) {
+ t /= d/2;
+ if (t < 1) return c/2*t*t*t*t + b;
+ return -c/2 * ((t-=2)*t*t*t - 2) + b;
+ }
+
+ /**
+ * Quintic easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInQuint(t, b, c, d) {
+ return c*(t/=d)*t*t*t*t + b;
+ }
+
+ /**
+ * Quintic easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutQuint(t, b, c, d) {
+ return c*((t=t/d-1)*t*t*t*t + 1) + b;
+ }
+
+ /**
+ * Quintic easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutQuint(t, b, c, d) {
+ t /= d/2;
+ if (t < 1) return c/2*t*t*t*t*t + b;
+ return c/2*((t-=2)*t*t*t*t + 2) + b;
+ }
+
+ /**
+ * Sinusoidal easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInSine(t, b, c, d) {
+ return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
+ }
+
+ /**
+ * Sinusoidal easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutSine(t, b, c, d) {
+ return c * Math.sin(t/d * (Math.PI/2)) + b;
+ }
+
+ /**
+ * Sinusoidal easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutSine(t, b, c, d) {
+ return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
+ }
+
+ /**
+ * Exponential easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInExpo(t, b, c, d) {
+ return (t===0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+ }
+
+ /**
+ * Exponential easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutExpo(t, b, c, d) {
+ return (t===d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ }
+
+ /**
+ * Exponential easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutExpo(t, b, c, d) {
+ if (t===0) return b;
+ if (t===d) return b+c;
+ t /= d/2;
+ if (t < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+ return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ }
+
+ /**
+ * Circular easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInCirc(t, b, c, d) {
+ return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
+ }
+
+ /**
+ * Circular easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutCirc(t, b, c, d) {
+ return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
+ }
+
+ /**
+ * Circular easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutCirc(t, b, c, d) {
+ t /= d/2;
+ if (t < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
+ return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
+ }
+
+ /**
+ * Elastic easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInElastic(t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t===0) return b;
+ t /= d;
+ if (t===1) return b+c;
+ if (!p) p=d*0.3;
+ var opts = normalize(a, c, p, s);
+ return -elastic(opts, t, d) + b;
+ }
+
+ /**
+ * Elastic easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutElastic(t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t===0) return b;
+ t /= d;
+ if (t===1) return b+c;
+ if (!p) p=d*0.3;
+ var opts = normalize(a, c, p, s);
+ return opts.a*Math.pow(2,-10*t) * Math.sin( (t*d-opts.s)*(2*Math.PI)/opts.p ) + opts.c + b;
+ }
+
+ /**
+ * Elastic easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutElastic(t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t===0) return b;
+ t /= d/2;
+ if (t===2) return b+c;
+ if (!p) p=d*(0.3*1.5);
+ var opts = normalize(a, c, p, s);
+ if (t < 1) return -0.5 * elastic(opts, t, d) + b;
+ return opts.a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-opts.s)*(2*Math.PI)/opts.p )*0.5 + opts.c + b;
+ }
+
+ /**
+ * Backwards easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInBack(t, b, c, d, s) {
+ if (s === undefined) s = 1.70158;
+ return c*(t/=d)*t*((s+1)*t - s) + b;
+ }
+
+ /**
+ * Backwards easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutBack(t, b, c, d, s) {
+ if (s === undefined) s = 1.70158;
+ return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
+ }
+
+ /**
+ * Backwards easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutBack(t, b, c, d, s) {
+ if (s === undefined) s = 1.70158;
+ t /= d/2;
+ if (t < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
+ return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
+ }
+
+ /**
+ * Bouncing easing in
+ * @memberOf fabric.util.ease
+ */
+ function easeInBounce(t, b, c, d) {
+ return c - easeOutBounce (d-t, 0, c, d) + b;
+ }
+
+ /**
+ * Bouncing easing out
+ * @memberOf fabric.util.ease
+ */
+ function easeOutBounce(t, b, c, d) {
+ if ((t/=d) < (1/2.75)) {
+ return c*(7.5625*t*t) + b;
+ } else if (t < (2/2.75)) {
+ return c*(7.5625*(t-=(1.5/2.75))*t + 0.75) + b;
+ } else if (t < (2.5/2.75)) {
+ return c*(7.5625*(t-=(2.25/2.75))*t + 0.9375) + b;
+ } else {
+ return c*(7.5625*(t-=(2.625/2.75))*t + 0.984375) + b;
+ }
+ }
+
+ /**
+ * Bouncing easing in and out
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutBounce(t, b, c, d) {
+ if (t < d/2) return easeInBounce (t*2, 0, c, d) * 0.5 + b;
+ return easeOutBounce (t*2-d, 0, c, d) * 0.5 + c*0.5 + b;
+ }
+
+ /**
+ * Easing functions
+ * See Easing Equations by Robert Penner
+ * @namespace fabric.util.ease
+ */
+ fabric.util.ease = {
+
+ /**
+ * Quadratic easing in
+ * @memberOf fabric.util.ease
+ */
+ easeInQuad: function(t, b, c, d) {
+ return c*(t/=d)*t + b;
+ },
+
+ /**
+ * Quadratic easing out
+ * @memberOf fabric.util.ease
+ */
+ easeOutQuad: function(t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ },
+
+ /**
+ * Quadratic easing in and out
+ * @memberOf fabric.util.ease
+ */
+ easeInOutQuad: function(t, b, c, d) {
+ t /= (d/2);
+ if (t < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ },
+
+ /**
+ * Cubic easing in
+ * @memberOf fabric.util.ease
+ */
+ easeInCubic: function(t, b, c, d) {
+ return c*(t/=d)*t*t + b;
+ },
+
+ easeOutCubic: easeOutCubic,
+ easeInOutCubic: easeInOutCubic,
+ easeInQuart: easeInQuart,
+ easeOutQuart: easeOutQuart,
+ easeInOutQuart: easeInOutQuart,
+ easeInQuint: easeInQuint,
+ easeOutQuint: easeOutQuint,
+ easeInOutQuint: easeInOutQuint,
+ easeInSine: easeInSine,
+ easeOutSine: easeOutSine,
+ easeInOutSine: easeInOutSine,
+ easeInExpo: easeInExpo,
+ easeOutExpo: easeOutExpo,
+ easeInOutExpo: easeInOutExpo,
+ easeInCirc: easeInCirc,
+ easeOutCirc: easeOutCirc,
+ easeInOutCirc: easeInOutCirc,
+ easeInElastic: easeInElastic,
+ easeOutElastic: easeOutElastic,
+ easeInOutElastic: easeInOutElastic,
+ easeInBack: easeInBack,
+ easeOutBack: easeOutBack,
+ easeInOutBack: easeInOutBack,
+ easeInBounce: easeInBounce,
+ easeOutBounce: easeOutBounce,
+ easeInOutBounce: easeInOutBounce
+ };
+
+}());
+
+
+(function(global) {
+
+ "use strict";
+
+ /**
+ * @name fabric
+ * @namespace
+ */
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ capitalize = fabric.util.string.capitalize,
+ clone = fabric.util.object.clone,
+ toFixed = fabric.util.toFixed,
+ multiplyTransformMatrices = fabric.util.multiplyTransformMatrices;
+
+ var attributesMap = {
+ 'fill-opacity': 'fillOpacity',
+ 'fill-rule': 'fillRule',
+ 'font-family': 'fontFamily',
+ 'font-size': 'fontSize',
+ 'font-style': 'fontStyle',
+ 'font-weight': 'fontWeight',
+ 'cx': 'left',
+ 'x': 'left',
+ 'r': 'radius',
+ 'stroke-dasharray': 'strokeDashArray',
+ 'stroke-linecap': 'strokeLineCap',
+ 'stroke-linejoin': 'strokeLineJoin',
+ 'stroke-miterlimit':'strokeMiterLimit',
+ 'stroke-opacity': 'strokeOpacity',
+ 'stroke-width': 'strokeWidth',
+ 'text-decoration': 'textDecoration',
+ 'cy': 'top',
+ 'y': 'top',
+ 'transform': 'transformMatrix'
+ };
+
+ var colorAttributes = {
+ 'stroke': 'strokeOpacity',
+ 'fill': 'fillOpacity'
+ };
+
+ function normalizeAttr(attr) {
+ // transform attribute names
+ if (attr in attributesMap) {
+ return attributesMap[attr];
+ }
+ return attr;
+ }
+
+ function normalizeValue(attr, value, parentAttributes) {
+ var isArray;
+
+ if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
+ value = '';
+ }
+ else if (attr === 'fillRule') {
+ value = (value === 'evenodd') ? 'destination-over' : value;
+ }
+ else if (attr === 'strokeDashArray') {
+ value = value.replace(/,/g, ' ').split(/\s+/);
+ }
+ else if (attr === 'transformMatrix') {
+ if (parentAttributes && parentAttributes.transformMatrix) {
+ value = multiplyTransformMatrices(
+ parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
+ }
+ else {
+ value = fabric.parseTransformAttribute(value);
+ }
+ }
+
+ isArray = Object.prototype.toString.call(value) === '[object Array]';
+
+ // TODO: need to normalize em, %, pt, etc. to px (!)
+ var parsed = isArray ? value.map(parseFloat) : parseFloat(value);
+
+ return (!isArray && isNaN(parsed) ? value : parsed);
+ }
+
+ /**
+ * @private
+ * @param {Object} attributes Array of attributes to parse
+ */
+ function _setStrokeFillOpacity(attributes) {
+ for (var attr in colorAttributes) {
+
+ if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') continue;
+
+ 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();
+
+ delete attributes[colorAttributes[attr]];
+ }
+ return attributes;
+ }
+
+ /**
+ * Parses "transform" attribute, returning an array of values
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {String} attributeValue String containing attribute value
+ * @return {Array} Array of 6 elements representing transformation matrix
+ */
+ fabric.parseTransformAttribute = (function() {
+ function rotateMatrix(matrix, args) {
+ var angle = args[0];
+
+ matrix[0] = Math.cos(angle);
+ matrix[1] = Math.sin(angle);
+ matrix[2] = -Math.sin(angle);
+ matrix[3] = Math.cos(angle);
+ }
+
+ function scaleMatrix(matrix, args) {
+ var multiplierX = args[0],
+ multiplierY = (args.length === 2) ? args[1] : args[0];
+
+ matrix[0] = multiplierX;
+ matrix[3] = multiplierY;
+ }
+
+ function skewXMatrix(matrix, args) {
+ matrix[2] = args[0];
+ }
+
+ function skewYMatrix(matrix, args) {
+ matrix[1] = 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 = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)',
+
+ comma_wsp = '(?:\\s+,?\\s*|,\\s*)',
+
+ skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
+
+ skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
+
+ rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
+ comma_wsp + '(' + number + ')' +
+ comma_wsp + '(' + number + '))?\\s*\\))',
+
+ scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
+ comma_wsp + '(' + number + '))?\\s*\\))',
+
+ translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
+ comma_wsp + '(' + number + '))?\\s*\\))',
+
+ matrix = '(?:(matrix)\\s*\\(\\s*' +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' +
+ '\\s*\\))',
+
+ transform = '(?:' +
+ matrix + '|' +
+ translate + '|' +
+ scale + '|' +
+ rotate + '|' +
+ skewX + '|' +
+ skewY +
+ ')',
+
+ transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')',
+
+ transform_list = '^\\s*(?:' + transforms + '?)\\s*$',
+
+ // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
+ reTransformList = new RegExp(transform_list),
+ // == end transform regexp
+
+ reTransform = new RegExp(transform, 'g');
+
+ return function(attributeValue) {
+
+ // start with identity matrix
+ var matrix = iMatrix.concat();
+ var 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) {
+ return (match !== '' && match != null);
+ }),
+ operation = m[1],
+ args = m.slice(2).map(parseFloat);
+
+ switch(operation) {
+ case 'translate':
+ translateMatrix(matrix, args);
+ break;
+ case 'rotate':
+ rotateMatrix(matrix, args);
+ break;
+ case 'scale':
+ scaleMatrix(matrix, args);
+ break;
+ case 'skewX':
+ skewXMatrix(matrix, args);
+ break;
+ case 'skewY':
+ skewYMatrix(matrix, args);
+ break;
+ case 'matrix':
+ matrix = args;
+ break;
+ }
+
+ // snapshot current matrix into matrices array
+ matrices.push(matrix.concat());
+ // reset
+ matrix = iMatrix.concat();
+ });
+
+ var combinedMatrix = matrices[0];
+ while (matrices.length > 1) {
+ matrices.shift();
+ combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
+ }
+ return combinedMatrix;
+ };
+ })();
+
+ function parseFontDeclaration(value, oStyle) {
+
+ // TODO: support non-px font size
+ var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);
+
+ if (!match) return;
+
+ var fontStyle = match[1];
+ // Font variant is not used
+ // var fontVariant = match[2];
+ var fontWeight = match[3];
+ var fontSize = match[4];
+ var lineHeight = match[5];
+ var fontFamily = match[6];
+
+ if (fontStyle) {
+ oStyle.fontStyle = fontStyle;
+ }
+ if (fontWeight) {
+ oStyle.fontSize = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
+ }
+ if (fontSize) {
+ oStyle.fontSize = parseFloat(fontSize);
+ }
+ if (fontFamily) {
+ oStyle.fontFamily = fontFamily;
+ }
+ if (lineHeight) {
+ oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
+ }
+ }
+
+ /**
+ * @private
+ */
+ function parseStyleString(style, oStyle) {
+ var attr, value;
+ style.replace(/;$/, '').split(';').forEach(function (chunk) {
+ var pair = chunk.split(':');
+
+ attr = normalizeAttr(pair[0].trim().toLowerCase());
+ value = normalizeValue(attr, pair[1].trim());
+
+ if (attr === 'font') {
+ parseFontDeclaration(value, oStyle);
+ }
+ else {
+ oStyle[attr] = value;
+ }
+ });
+ }
+
+ /**
+ * @private
+ */
+ function parseStyleObject(style, oStyle) {
+ var attr, value;
+ for (var prop in style) {
+ if (typeof style[prop] === 'undefined') continue;
+
+ attr = normalizeAttr(prop.toLowerCase());
+ value = normalizeValue(attr, style[prop]);
+
+ if (attr === 'font') {
+ parseFontDeclaration(value, oStyle);
+ }
+ else {
+ oStyle[attr] = value;
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ function getGlobalStylesForElement(element) {
+ var nodeName = element.nodeName,
+ className = element.getAttribute('class'),
+ id = element.getAttribute('id'),
+ styles = { };
+
+ for (var rule in fabric.cssRules) {
+ var ruleMatchesElement = (className && new RegExp('^\\.' + className).test(rule)) ||
+ (id && new RegExp('^#' + id).test(rule)) ||
+ (new RegExp('^' + nodeName).test(rule));
+
+ if (ruleMatchesElement) {
+ for (var property in fabric.cssRules[rule]) {
+ styles[property] = fabric.cssRules[rule][property];
+ }
+ }
+ }
+
+ return styles;
+ }
+
+ /**
+ * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {SVGDocument} doc SVG document to parse
+ * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document).
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ fabric.parseSVGDocument = (function() {
+
+ var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/;
+
+ // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
+ // \d doesn't quite cut it (as we need to match an actual float number)
+
+ // matches, e.g.: +14.56e-12, etc.
+ var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)';
+
+ var reViewBoxAttrValue = new RegExp(
+ '^' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*' +
+ '$'
+ );
+
+ function hasAncestorWithNodeName(element, nodeName) {
+ while (element && (element = element.parentNode)) {
+ if (nodeName.test(element.nodeName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return function(doc, callback, reviver) {
+ if (!doc) return;
+
+ var startTime = new Date(),
+ descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
+
+ if (descendants.length === 0) {
+ // we're likely in node, where "o3-xml" library fails to gEBTN("*")
+ // https://github.com/ajaxorg/node-o3-xml/issues/21
+ descendants = doc.selectNodes("//*[name(.)!='svg']");
+ var arr = [ ];
+ for (var i = 0, len = descendants.length; i < len; i++) {
+ arr[i] = descendants[i];
+ }
+ descendants = arr;
+ }
+
+ var elements = descendants.filter(function(el) {
+ return reAllowedSVGTagNames.test(el.tagName) &&
+ !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
+ });
+
+ if (!elements || (elements && !elements.length)) return;
+
+ var viewBoxAttr = doc.getAttribute('viewBox'),
+ widthAttr = doc.getAttribute('width'),
+ heightAttr = doc.getAttribute('height'),
+ width = null,
+ height = null,
+ minX,
+ minY;
+
+ if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
+ minX = parseInt(viewBoxAttr[1], 10);
+ minY = parseInt(viewBoxAttr[2], 10);
+ width = parseInt(viewBoxAttr[3], 10);
+ height = parseInt(viewBoxAttr[4], 10);
+ }
+
+ // values of width/height attributes overwrite those extracted from viewbox attribute
+ width = widthAttr ? parseFloat(widthAttr) : width;
+ height = heightAttr ? parseFloat(heightAttr) : height;
+
+ var options = {
+ width: width,
+ height: height
+ };
+
+ fabric.gradientDefs = fabric.getGradientDefs(doc);
+ fabric.cssRules = fabric.getCSSRules(doc);
+
+ // Precedence of rules: style > class > attribute
+
+ fabric.parseElements(elements, function(instances) {
+ fabric.documentParsingTime = new Date() - startTime;
+ if (callback) {
+ callback(instances, options);
+ }
+ }, clone(options), reviver);
+ };
+ })();
+
+ /**
+ * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
+ * @namespace
+ */
+ var svgCache = {
+
+ /**
+ * @param {String} name
+ * @param {Function} callback
+ */
+ has: function (name, callback) {
+ callback(false);
+ },
+
+ /**
+ * @param {String} url
+ * @param {Function} callback
+ */
+ get: function () {
+ /* NOOP */
+ },
+
+ /**
+ * @param {String} url
+ * @param {Object} object
+ */
+ set: function () {
+ /* NOOP */
+ }
+ };
+
+ /**
+ * @private
+ */
+ function _enlivenCachedObject(cachedObject) {
+
+ var objects = cachedObject.objects,
+ options = cachedObject.options;
+
+ objects = objects.map(function (o) {
+ return fabric[capitalize(o.type)].fromObject(o);
+ });
+
+ return ({ objects: objects, options: options });
+ }
+
+ /**
+ * @private
+ */
+ function _createSVGPattern(markup, canvas, property) {
+ if (canvas[property] && canvas[property].toSVG) {
+ markup.push(
+ '',
+ ' '
+ );
+ }
+ }
+
+ extend(fabric, {
+
+ /**
+ * Initializes gradients on instances, according to gradients parsed from a document
+ * @param {Array} instances
+ */
+ resolveGradients: function(instances) {
+ for (var i = instances.length; i--; ) {
+ var instanceFillValue = instances[i].get('fill');
+
+ if (!(/^url\(/).test(instanceFillValue)) continue;
+
+ var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
+
+ if (fabric.gradientDefs[gradientId]) {
+ instances[i].set('fill',
+ fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i]));
+ }
+ }
+ },
+
+ /**
+ * 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 linearGradientEls = doc.getElementsByTagName('linearGradient'),
+ radialGradientEls = doc.getElementsByTagName('radialGradient'),
+ el, i,
+ gradientDefs = { };
+
+ i = linearGradientEls.length;
+ for (; i--; ) {
+ el = linearGradientEls[i];
+ gradientDefs[el.getAttribute('id')] = el;
+ }
+
+ i = radialGradientEls.length;
+ for (; i--; ) {
+ el = radialGradientEls[i];
+ gradientDefs[el.getAttribute('id')] = el;
+ }
+
+ 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) {
+
+ if (!element) {
+ return;
+ }
+
+ var value,
+ parentAttributes = { };
+
+ // if there's a parent container (`g` node), parse its attributes recursively upwards
+ if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) {
+ parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
+ }
+
+ var ownAttributes = attributes.reduce(function(memo, attr) {
+ value = element.getAttribute(attr);
+ if (value) {
+ attr = normalizeAttr(attr);
+ value = normalizeValue(attr, value, parentAttributes);
+
+ 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), fabric.parseStyleAttribute(element)));
+
+ return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
+ },
+
+ /**
+ * Transforms an array of svg elements to corresponding fabric.* instances
+ * @static
+ * @memberOf fabric
+ * @param {Array} elements Array of elements to parse
+ * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
+ * @param {Object} [options] Options object
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ parseElements: function(elements, callback, options, reviver) {
+ fabric.ElementsParser.parse(elements, callback, options, reviver);
+ },
+
+ /**
+ * 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 points {String} points attribute string
+ * @return {Array} array of points
+ */
+ parsePointsAttribute: function(points) {
+
+ // points attribute is required and must not be empty
+ if (!points) return null;
+
+ points = points.trim();
+ var asPairs = points.indexOf(',') > -1;
+
+ points = points.split(/\s+/);
+ var parsedPoints = [ ], i, len;
+
+ // points could look like "10,20 30,40" or "10 20 30 40"
+ if (asPairs) {
+ i = 0;
+ len = points.length;
+ for (; i < len; i++) {
+ var pair = points[i].split(',');
+ parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) });
+ }
+ }
+ else {
+ i = 0;
+ len = points.length;
+ for (; i < len; i+=2) {
+ parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i+1]) });
+ }
+ }
+
+ // odd number of points is an error
+ if (parsedPoints.length % 2 !== 0) {
+ // return null;
+ }
+
+ return parsedPoints;
+ },
+
+ /**
+ * Returns CSS rules for a given SVG document
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {SVGDocument} doc SVG document to parse
+ * @return {Object} CSS rules of this document
+ */
+ getCSSRules: function(doc) {
+ var styles = doc.getElementsByTagName('style'),
+ allRules = { },
+ rules;
+
+ // very crude parsing of style contents
+ for (var i = 0, len = styles.length; i < len; i++) {
+ var styleContents = styles[0].textContent;
+
+ // remove comments
+ styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
+
+ 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*\{([^}]*)\}/);
+ rule = match[1];
+ var declaration = match[2].trim(),
+ propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
+
+ if (!allRules[rule]) {
+ allRules[rule] = { };
+ }
+
+ for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
+ var pair = propertyValuePairs[i].split(/\s*:\s*/),
+ property = pair[0],
+ value = pair[1];
+
+ allRules[rule][property] = value;
+ }
+ });
+ }
+
+ return allRules;
+ },
+
+ /**
+ * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
+ * @memberof fabric
+ * @param {String} url
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ loadSVGFromURL: function(url, callback, reviver) {
+
+ url = url.replace(/^\n\s*/, '').trim();
+
+ svgCache.has(url, function (hasUrl) {
+ if (hasUrl) {
+ svgCache.get(url, function (value) {
+ var enlivedRecord = _enlivenCachedObject(value);
+ callback(enlivedRecord.objects, enlivedRecord.options);
+ });
+ }
+ else {
+ new fabric.util.request(url, {
+ method: 'get',
+ onComplete: onComplete
+ });
+ }
+ });
+
+ function onComplete(r) {
+
+ var xml = r.responseXML;
+ if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
+ xml = new ActiveXObject('Microsoft.XMLDOM');
+ xml.async = 'false';
+ //IE chokes on DOCTYPE
+ xml.loadXML(r.responseText.replace(//i,''));
+ }
+ if (!xml.documentElement) return;
+
+ fabric.parseSVGDocument(xml.documentElement, function (results, options) {
+ svgCache.set(url, {
+ objects: fabric.util.array.invoke(results, 'toObject'),
+ options: options
+ });
+ callback(results, options);
+ }, reviver);
+ }
+ },
+
+ /**
+ * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
+ * @memberof fabric
+ * @param {String} string
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ loadSVGFromString: function(string, callback, reviver) {
+ string = string.trim();
+ var doc;
+ if (typeof DOMParser !== 'undefined') {
+ var parser = new DOMParser();
+ if (parser && parser.parseFromString) {
+ doc = parser.parseFromString(string, 'text/xml');
+ }
+ }
+ else if (fabric.window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ //IE chokes on DOCTYPE
+ doc.loadXML(string.replace(//i,''));
+ }
+
+ fabric.parseSVGDocument(doc.documentElement, function (results, options) {
+ callback(results, options);
+ }, reviver);
+ },
+
+ /**
+ * Creates markup containing SVG font faces
+ * @param {Array} objects Array of fabric objects
+ * @return {String}
+ */
+ createSVGFontFacesMarkup: function(objects) {
+ var markup = '';
+
+ for (var i = 0, len = objects.length; i < len; i++) {
+ if (objects[i].type !== 'text' || !objects[i].path) continue;
+
+ markup += [
+ '@font-face {',
+ 'font-family: ', objects[i].fontFamily, '; ',
+ 'src: url(\'', objects[i].path, '\')',
+ '}'
+ ].join('');
+ }
+
+ if (markup) {
+ markup = [
+ ''
+ ].join('');
+ }
+
+ return markup;
+ },
+
+ /**
+ * Creates markup containing SVG referenced elements like patterns, gradients etc.
+ * @param {fabric.Canvas} canvas instance of fabric.Canvas
+ * @return {String}
+ */
+ createSVGRefElementsMarkup: function(canvas) {
+ var markup = [ ];
+
+ _createSVGPattern(markup, canvas, 'backgroundColor');
+ _createSVGPattern(markup, canvas, 'overlayColor');
+
+ return markup.join('');
+ }
+ });
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+fabric.ElementsParser = {
+
+ parse: function(elements, callback, options, reviver) {
+
+ this.elements = elements;
+ this.callback = callback;
+ this.options = options;
+ this.reviver = reviver;
+
+ this.instances = new Array(elements.length);
+ this.numElements = elements.length;
+
+ this.createObjects();
+ },
+
+ createObjects: function() {
+ for (var i = 0, len = this.elements.length; i < len; i++) {
+ this.createObject(this.elements[i], i);
+ }
+ },
+
+ createObject: function(el, index) {
+ var klass = fabric[fabric.util.string.capitalize(el.tagName)];
+ if (klass && klass.fromElement) {
+ try {
+ this._createObject(klass, el, index);
+ }
+ catch(err) {
+ fabric.log(err);
+ }
+ }
+ else {
+ this.checkIfDone();
+ }
+ },
+
+ _createObject: function(klass, el, index) {
+ if (klass.async) {
+ klass.fromElement(el, this.createCallback(index, el), this.options);
+ }
+ else {
+ var obj = klass.fromElement(el, this.options);
+ this.reviver && this.reviver(el, obj);
+ this.instances.splice(index, 0, obj);
+ this.checkIfDone();
+ }
+ },
+
+ createCallback: function(index, el) {
+ var _this = this;
+ return function(obj) {
+ _this.reviver && _this.reviver(el, obj);
+ _this.instances.splice(index, 0, obj);
+ _this.checkIfDone();
+ };
+ },
+
+ checkIfDone: function() {
+ if (--this.numElements === 0) {
+ this.instances = this.instances.filter(function(el) {
+ return el != null;
+ });
+ fabric.resolveGradients(this.instances);
+ this.callback(this.instances);
+ }
+ }
+};
+
+
+(function(global) {
+
+ "use strict";
+
+ /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Point) {
+ fabric.warn('fabric.Point is already defined');
+ return;
+ }
+
+ fabric.Point = Point;
+
+ /**
+ * Point class
+ * @class fabric.Point
+ * @memberOf fabric
+ * @constructor
+ * @param {Number} x
+ * @param {Number} y
+ * @return {fabric.Point} thisArg
+ */
+ function Point(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ Point.prototype = /** @lends fabric.Point.prototype */ {
+
+ constructor: Point,
+
+ /**
+ * Adds another point to this one and returns another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point} new Point instance with added values
+ */
+ add: function (that) {
+ return new Point(this.x + that.x, this.y + that.y);
+ },
+
+ /**
+ * Adds another point to this one
+ * @param {fabric.Point} that
+ * @return {fabric.Point} thisArg
+ */
+ addEquals: function (that) {
+ this.x += that.x;
+ this.y += that.y;
+ return this;
+ },
+
+ /**
+ * Adds value to this point and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point} new Point with added value
+ */
+ scalarAdd: function (scalar) {
+ return new Point(this.x + scalar, this.y + scalar);
+ },
+
+ /**
+ * Adds value to this point
+ * @param {Number} scalar
+ * @param {fabric.Point} thisArg
+ */
+ scalarAddEquals: function (scalar) {
+ this.x += scalar;
+ this.y += scalar;
+ return this;
+ },
+
+ /**
+ * Subtracts another point from this point and returns a new one
+ * @param {fabric.Point} that
+ * @return {fabric.Point} new Point object with subtracted values
+ */
+ subtract: function (that) {
+ return new Point(this.x - that.x, this.y - that.y);
+ },
+
+ /**
+ * Subtracts another point from this point
+ * @param {fabric.Point} that
+ * @return {fabric.Point} thisArg
+ */
+ subtractEquals: function (that) {
+ this.x -= that.x;
+ this.y -= that.y;
+ return this;
+ },
+
+ /**
+ * Subtracts value from this point and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point}
+ */
+ scalarSubtract: function (scalar) {
+ return new Point(this.x - scalar, this.y - scalar);
+ },
+
+ /**
+ * Subtracts value from this point
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ */
+ scalarSubtractEquals: function (scalar) {
+ this.x -= scalar;
+ this.y -= scalar;
+ return this;
+ },
+
+ /**
+ * Miltiplies this point by a value and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point}
+ */
+ multiply: function (scalar) {
+ return new Point(this.x * scalar, this.y * scalar);
+ },
+
+ /**
+ * Miltiplies this point by a value
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ */
+ multiplyEquals: function (scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ return this;
+ },
+
+ /**
+ * Divides this point by a value and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point}
+ */
+ divide: function (scalar) {
+ return new Point(this.x / scalar, this.y / scalar);
+ },
+
+ /**
+ * Divides this point by a value
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ */
+ divideEquals: function (scalar) {
+ this.x /= scalar;
+ this.y /= scalar;
+ return this;
+ },
+
+ /**
+ * Returns true if this point is equal to another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ eq: function (that) {
+ return (this.x === that.x && this.y === that.y);
+ },
+
+ /**
+ * Returns true if this point is less than another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ lt: function (that) {
+ return (this.x < that.x && this.y < that.y);
+ },
+
+ /**
+ * Returns true if this point is less than or equal to another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ lte: function (that) {
+ return (this.x <= that.x && this.y <= that.y);
+ },
+
+ /**
+
+ * Returns true if this point is greater another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ gt: function (that) {
+ return (this.x > that.x && this.y > that.y);
+ },
+
+ /**
+ * Returns true if this point is greater than or equal to another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ gte: function (that) {
+ return (this.x >= that.x && this.y >= that.y);
+ },
+
+ /**
+ * Returns new point which is the result of linear interpolation with this one and another one
+ * @param {fabric.Point} that
+ * @param {Number} t
+ * @return {fabric.Point}
+ */
+ lerp: function (that, t) {
+ return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
+ },
+
+ /**
+ * Returns distance from this point and another one
+ * @param {fabric.Point} that
+ * @return {Number}
+ */
+ distanceFrom: function (that) {
+ var dx = this.x - that.x,
+ dy = this.y - that.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+
+ /**
+ * Returns the point between this point and another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point}
+ */
+ midPointFrom: function (that) {
+ return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2);
+ },
+
+ /**
+ * Returns a new point which is the min of this and another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point}
+ */
+ min: function (that) {
+ return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
+ },
+
+ /**
+ * Returns a new point which is the max of this and another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point}
+ */
+ max: function (that) {
+ return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
+ },
+
+ /**
+ * Returns string representation of this point
+ * @return {String}
+ */
+ toString: function () {
+ return this.x + "," + this.y;
+ },
+
+ /**
+ * Sets x/y of this point
+ * @param {Number} x
+ * @return {Number} y
+ */
+ setXY: function (x, y) {
+ this.x = x;
+ this.y = y;
+ },
+
+ /**
+ * Sets x/y of this point from another point
+ * @param {fabric.Point} that
+ */
+ setFromPoint: function (that) {
+ this.x = that.x;
+ this.y = that.y;
+ },
+
+ /**
+ * Swaps x/y of this point and another point
+ * @param {fabric.Point} that
+ */
+ swap: function (that) {
+ var x = this.x,
+ y = this.y;
+ this.x = that.x;
+ this.y = that.y;
+ that.x = x;
+ that.y = y;
+ }
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function(global) {
+
+ "use strict";
+
+ /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Intersection) {
+ fabric.warn('fabric.Intersection is already defined');
+ return;
+ }
+
+ /**
+ * Intersection class
+ * @class fabric.Intersection
+ * @memberOf fabric
+ * @constructor
+ */
+ function Intersection(status) {
+ this.status = status;
+ this.points = [];
+ }
+
+ fabric.Intersection = Intersection;
+
+ fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
+
+ /**
+ * Appends a point to intersection
+ * @param {fabric.Point} point
+ */
+ appendPoint: function (point) {
+ this.points.push(point);
+ },
+
+ /**
+ * Appends points to intersection
+ * @param {Array} points
+ */
+ appendPoints: function (points) {
+ this.points = this.points.concat(points);
+ }
+ };
+
+ /**
+ * Checks if one line intersects another
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {fabric.Point} b1
+ * @param {fabric.Point} b2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
+ var result,
+ ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
+ ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
+ u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
+ if (u_b !== 0) {
+ var ua = ua_t / u_b,
+ ub = ub_t / u_b;
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
+ result = new Intersection("Intersection");
+ result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
+ }
+ else {
+ result = new Intersection();
+ }
+ }
+ else {
+ if (ua_t === 0 || ub_t === 0) {
+ result = new Intersection("Coincident");
+ }
+ else {
+ result = new Intersection("Parallel");
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Checks if line intersects polygon
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {Array} points
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLinePolygon = function(a1,a2,points){
+ var result = new Intersection(),
+ length = points.length;
+
+ for (var i = 0; i < length; i++) {
+ var b1 = points[i],
+ b2 = points[(i+1) % length],
+ inter = Intersection.intersectLineLine(a1, a2, b1, b2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects another polygon
+ * @static
+ * @param {Array} points1
+ * @param {Array} points2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
+ var result = new Intersection(),
+ length = points1.length;
+
+ for (var i = 0; i < length; i++) {
+ var a1 = points1[i],
+ a2 = points1[(i+1) % length],
+ inter = Intersection.intersectLinePolygon(a1, a2, points2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects rectangle
+ * @static
+ * @param {Array} points
+ * @param {Number} r1
+ * @param {Number} r2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
+ var min = r1.min(r2),
+ max = r1.max(r2),
+ topRight = new fabric.Point(max.x, min.y),
+ bottomLeft = new fabric.Point(min.x, max.y),
+ inter1 = Intersection.intersectLinePolygon(min, topRight, points),
+ inter2 = Intersection.intersectLinePolygon(topRight, max, points),
+ inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
+ inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
+ result = new Intersection();
+
+ result.appendPoints(inter1.points);
+ result.appendPoints(inter2.points);
+ result.appendPoints(inter3.points);
+ result.appendPoints(inter4.points);
+
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Color) {
+ fabric.warn('fabric.Color is already defined.');
+ return;
+ }
+
+ /**
+ * Color class
+ * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
+ * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
+ *
+ * @class fabric.Color
+ * @param {String} color optional in hex or rgb(a) format
+ * @return {fabric.Color} thisArg
+ * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors}
+ */
+ function Color(color) {
+ if (!color) {
+ this.setSource([0, 0, 0, 1]);
+ }
+ else {
+ this._tryParsingColor(color);
+ }
+ }
+
+ fabric.Color = Color;
+
+ fabric.Color.prototype = /** @lends fabric.Color.prototype */ {
+
+ /**
+ * @private
+ * @param {String|Array} color Color value to parse
+ */
+ _tryParsingColor: function(color) {
+ var source;
+
+ if (color in Color.colorNameMap) {
+ color = Color.colorNameMap[color];
+ }
+
+ source = Color.sourceFromHex(color);
+
+ if (!source) {
+ source = Color.sourceFromRgb(color);
+ }
+ if (!source) {
+ source = Color.sourceFromHsl(color);
+ }
+ if (source) {
+ this.setSource(source);
+ }
+ },
+
+ /**
+ * Adapted from https://github.com/mjijackson
+ * @private
+ * @param {Number} r Red color value
+ * @param {Number} g Green color value
+ * @param {Number} b Blue color value
+ * @return {Array} Hsl color
+ */
+ _rgbToHsl: function(r, g, b) {
+ r /= 255, g /= 255, b /= 255;
+
+ var h, s, l,
+ max = fabric.util.array.max([r, g, b]),
+ min = fabric.util.array.min([r, g, b]);
+
+ l = (max + min) / 2;
+
+ if (max === min) {
+ h = s = 0; // achromatic
+ }
+ else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+
+ return [
+ Math.round(h * 360),
+ Math.round(s * 100),
+ Math.round(l * 100)
+ ];
+ },
+
+ /**
+ * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
+ * @return {Array}
+ */
+ getSource: function() {
+ return this._source;
+ },
+
+ /**
+ * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
+ * @param {Array} source
+ */
+ setSource: function(source) {
+ this._source = source;
+ },
+
+ /**
+ * Returns color represenation in RGB format
+ * @return {String} ex: rgb(0-255,0-255,0-255)
+ */
+ toRgb: function() {
+ var source = this.getSource();
+ return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';
+ },
+
+ /**
+ * Returns color represenation in RGBA format
+ * @return {String} ex: rgba(0-255,0-255,0-255,0-1)
+ */
+ toRgba: function() {
+ var source = this.getSource();
+ return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';
+ },
+
+ /**
+ * Returns color represenation in HSL format
+ * @return {String} ex: hsl(0-360,0%-100%,0%-100%)
+ */
+ toHsl: function() {
+ var source = this.getSource(),
+ hsl = this._rgbToHsl(source[0], source[1], source[2]);
+
+ return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)';
+ },
+
+ /**
+ * Returns color represenation in HSLA format
+ * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)
+ */
+ toHsla: function() {
+ var source = this.getSource(),
+ hsl = this._rgbToHsl(source[0], source[1], source[2]);
+
+ return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')';
+ },
+
+ /**
+ * Returns color represenation in HEX format
+ * @return {String} ex: FF5555
+ */
+ toHex: function() {
+ var source = this.getSource();
+
+ var r = source[0].toString(16);
+ r = (r.length === 1) ? ('0' + r) : r;
+
+ var g = source[1].toString(16);
+ g = (g.length === 1) ? ('0' + g) : g;
+
+ var b = source[2].toString(16);
+ b = (b.length === 1) ? ('0' + b) : b;
+
+ return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
+ },
+
+ /**
+ * Gets value of alpha channel for this color
+ * @return {Number} 0-1
+ */
+ getAlpha: function() {
+ return this.getSource()[3];
+ },
+
+ /**
+ * Sets value of alpha channel for this color
+ * @param {Number} alpha Alpha value 0-1
+ * @return {fabric.Color} thisArg
+ */
+ setAlpha: function(alpha) {
+ var source = this.getSource();
+ source[3] = alpha;
+ this.setSource(source);
+ return this;
+ },
+
+ /**
+ * Transforms color to its grayscale representation
+ * @return {fabric.Color} thisArg
+ */
+ toGrayscale: function() {
+ var source = this.getSource(),
+ average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),
+ currentAlpha = source[3];
+ this.setSource([average, average, average, currentAlpha]);
+ return this;
+ },
+
+ /**
+ * Transforms color to its black and white representation
+ * @param {Number} threshold
+ * @return {fabric.Color} thisArg
+ */
+ toBlackWhite: function(threshold) {
+ var source = this.getSource(),
+ average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
+ currentAlpha = source[3];
+
+ threshold = threshold || 127;
+
+ average = (Number(average) < Number(threshold)) ? 0 : 255;
+ this.setSource([average, average, average, currentAlpha]);
+ return this;
+ },
+
+ /**
+ * Overlays color with another color
+ * @param {String|fabric.Color} otherColor
+ * @return {fabric.Color} thisArg
+ */
+ overlayWith: function(otherColor) {
+ if (!(otherColor instanceof Color)) {
+ otherColor = new Color(otherColor);
+ }
+
+ var result = [],
+ alpha = this.getAlpha(),
+ otherAlpha = 0.5,
+ source = this.getSource(),
+ otherSource = otherColor.getSource();
+
+ for (var i = 0; i < 3; i++) {
+ result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));
+ }
+
+ result[3] = alpha;
+ this.setSource(result);
+ return this;
+ }
+ };
+
+ /**
+ * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5))
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ */
+ fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
+
+ /**
+ * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 ))
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ */
+ fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
+
+ /**
+ * Regex matching color in HEX format (ex: #FF5555, 010155, aff)
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ */
+ fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
+
+ /**
+ * Map of the 17 basic color names with HEX code
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units
+ */
+ fabric.Color.colorNameMap = {
+ 'aqua': '#00FFFF',
+ 'black': '#000000',
+ 'blue': '#0000FF',
+ 'fuchsia': '#FF00FF',
+ 'gray': '#808080',
+ 'green': '#008000',
+ 'lime': '#00FF00',
+ 'maroon': '#800000',
+ 'navy': '#000080',
+ 'olive': '#808000',
+ 'orange': '#FFA500',
+ 'purple': '#800080',
+ 'red': '#FF0000',
+ 'silver': '#C0C0C0',
+ 'teal': '#008080',
+ 'white': '#FFFFFF',
+ 'yellow': '#FFFF00'
+ };
+
+ /**
+ * @private
+ * @param {Number} p
+ * @param {Number} q
+ * @param {Number} t
+ * @return {Number}
+ */
+ function hue2rgb(p, q, t){
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1/6) return p + (q - p) * 6 * t;
+ if (t < 1/2) return q;
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ }
+
+ /**
+ * Returns new color object, when given a color in RGB format
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: rgb(0-255,0-255,0-255)
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromRgb = function(color) {
+ return Color.fromSource(Color.sourceFromRgb(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%)
+ * @return {Array} source
+ */
+ fabric.Color.sourceFromRgb = function(color) {
+ var match = color.match(Color.reRGBa);
+ if (match) {
+ var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1),
+ g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1),
+ b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1);
+
+ return [
+ parseInt(r, 10),
+ parseInt(g, 10),
+ parseInt(b, 10),
+ match[4] ? parseFloat(match[4]) : 1
+ ];
+ }
+ };
+
+ /**
+ * Returns new color object, when given a color in RGBA format
+ * @static
+ * @function
+ * @memberOf fabric.Color
+ * @param {String} color
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromRgba = Color.fromRgb;
+
+ /**
+ * Returns new color object, when given a color in HSL format
+ * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%)
+ * @memberOf fabric.Color
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromHsl = function(color) {
+ return Color.fromSource(Color.sourceFromHsl(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format.
+ * Adapted from https://github.com/mjijackson
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)
+ * @return {Array} source
+ * @see http://http://www.w3.org/TR/css3-color/#hsl-color
+ */
+ fabric.Color.sourceFromHsl = function(color) {
+ var match = color.match(Color.reHSLa);
+ if (!match) return;
+
+ var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
+ s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),
+ l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),
+ r, g, b;
+
+ if (s === 0) {
+ r = g = b = l;
+ }
+ else {
+ var q = l <= 0.5 ? l * (s + 1) : l + s - l * s;
+ var p = l * 2 - q;
+
+ r = hue2rgb(p, q, h + 1/3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+
+ return [
+ Math.round(r * 255),
+ Math.round(g * 255),
+ Math.round(b * 255),
+ match[4] ? parseFloat(match[4]) : 1
+ ];
+ };
+
+ /**
+ * Returns new color object, when given a color in HSLA format
+ * @static
+ * @function
+ * @memberOf fabric.Color
+ * @param {String} color
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromHsla = Color.fromHsl;
+
+ /**
+ * Returns new color object, when given a color in HEX format
+ * @static
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: FF5555
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromHex = function(color) {
+ return Color.fromSource(Color.sourceFromHex(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format
+ * @static
+ * @memberOf fabric.Color
+ * @param {String} color ex: FF5555
+ * @return {Array} source
+ */
+ fabric.Color.sourceFromHex = function(color) {
+ if (color.match(Color.reHex)) {
+ var value = color.slice(color.indexOf('#') + 1),
+ isShortNotation = (value.length === 3),
+ 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);
+
+ return [
+ parseInt(r, 16),
+ parseInt(g, 16),
+ parseInt(b, 16),
+ 1
+ ];
+ }
+ };
+
+ /**
+ * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
+ * @static
+ * @memberOf fabric.Color
+ * @param {Array} source
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromSource = function(source) {
+ var oColor = new Color();
+ oColor.setSource(source);
+ return oColor;
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function() {
+
+ /* _FROM_SVG_START_ */
+ function getColorStop(el) {
+ var style = el.getAttribute('style'),
+ offset = el.getAttribute('offset'),
+ color, opacity;
+
+ // convert percents to absolute values
+ offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
+
+ if (style) {
+ var keyValuePairs = style.split(/\s*;\s*/);
+
+ if (keyValuePairs[keyValuePairs.length-1] === '') {
+ keyValuePairs.pop();
+ }
+
+ for (var i = keyValuePairs.length; i--; ) {
+
+ var split = keyValuePairs[i].split(/\s*:\s*/),
+ key = split[0].trim(),
+ value = split[1].trim();
+
+ if (key === 'stop-color') {
+ color = value;
+ }
+ else if (key === 'stop-opacity') {
+ opacity = value;
+ }
+ }
+ }
+
+ if (!color) {
+ color = el.getAttribute('stop-color') || 'rgb(0,0,0)';
+ }
+ if (!opacity) {
+ opacity = el.getAttribute('stop-opacity');
+ }
+
+ // convert rgba color to rgb color - alpha value has no affect in svg
+ color = new fabric.Color(color).toRgb();
+
+ return {
+ offset: offset,
+ color: color,
+ opacity: isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity)
+ };
+ }
+
+ function getLinearCoords(el) {
+ return {
+ x1: el.getAttribute('x1') || 0,
+ y1: el.getAttribute('y1') || 0,
+ x2: el.getAttribute('x2') || '100%',
+ y2: el.getAttribute('y2') || 0
+ };
+ }
+
+ function getRadialCoords(el) {
+ return {
+ x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
+ y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
+ r1: 0,
+ x2: el.getAttribute('cx') || '50%',
+ y2: el.getAttribute('cy') || '50%',
+ r2: el.getAttribute('r') || '50%'
+ };
+ }
+ /* _FROM_SVG_END_ */
+
+ /**
+ * Gradient class
+ * @class fabric.Gradient
+ * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients}
+ * @see {@link fabric.Gradient#initialize} for constructor definition
+ */
+ fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
+
+ /**
+ * Constructor
+ * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
+ * @return {fabric.Gradient} thisArg
+ */
+ initialize: function(options) {
+ options || (options = { });
+
+ var coords = { };
+
+ this.id = fabric.Object.__uid++;
+ this.type = options.type || 'linear';
+
+ coords = {
+ x1: options.coords.x1 || 0,
+ y1: options.coords.y1 || 0,
+ x2: options.coords.x2 || 0,
+ y2: options.coords.y2 || 0
+ };
+
+ if (this.type === 'radial') {
+ coords.r1 = options.coords.r1 || 0;
+ coords.r2 = options.coords.r2 || 0;
+ }
+
+ this.coords = coords;
+ this.gradientUnits = options.gradientUnits || 'objectBoundingBox';
+ this.colorStops = options.colorStops.slice();
+ },
+
+ /**
+ * Adds another colorStop
+ * @param {Object} colorStop Object with offset and color
+ * @return {fabric.Gradient} thisArg
+ */
+ addColorStop: function(colorStop) {
+ for (var position in colorStop) {
+ var color = new fabric.Color(colorStop[position]);
+ this.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()});
+ }
+ return this;
+ },
+
+ /**
+ * Returns object representation of a gradient
+ * @return {Object}
+ */
+ toObject: function() {
+ return {
+ type: this.type,
+ coords: this.coords,
+ gradientUnits: this.gradientUnits,
+ colorStops: this.colorStops
+ };
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns SVG representation of an gradient
+ * @param {Object} object Object to create a gradient for
+ * @param {Boolean} normalize Whether coords should be normalized
+ * @return {String} SVG representation of an gradient (linear/radial)
+ */
+ toSVG: function(object, normalize) {
+ var coords = fabric.util.object.clone(this.coords),
+ markup;
+
+ // colorStops must be sorted ascending
+ this.colorStops.sort(function(a, b) {
+ return a.offset - b.offset;
+ });
+
+ if (normalize && this.gradientUnits === 'userSpaceOnUse') {
+ coords.x1 += object.width / 2;
+ coords.y1 += object.height / 2;
+ coords.x2 += object.width / 2;
+ coords.y2 += object.height / 2;
+ }
+ else if (this.gradientUnits === 'objectBoundingBox') {
+ _convertValuesToPercentUnits(object, coords);
+ }
+
+ if (this.type === 'linear') {
+ markup = [
+ ''
+ ];
+ }
+ else if (this.type === 'radial') {
+ markup = [
+ ''
+ ];
+ }
+
+ for (var i = 0; i < this.colorStops.length; i++) {
+ markup.push(
+ ' '
+ );
+ }
+
+ markup.push((this.type === 'linear' ? ' ' : ''));
+
+ return markup.join('');
+ },
+ /* _TO_SVG_END_ */
+
+ /**
+ * Returns an instance of CanvasGradient
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @return {CanvasGradient}
+ */
+ toLive: function(ctx) {
+ var gradient;
+
+ if (!this.type) return;
+
+ if (this.type === 'linear') {
+ gradient = ctx.createLinearGradient(
+ this.coords.x1, this.coords.y1, this.coords.x2, this.coords.y2);
+ }
+ else if (this.type === 'radial') {
+ gradient = ctx.createRadialGradient(
+ this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2);
+ }
+
+ for (var i = 0, len = this.colorStops.length; i < len; i++) {
+ var color = this.colorStops[i].color,
+ opacity = this.colorStops[i].opacity,
+ offset = this.colorStops[i].offset;
+
+ if (typeof opacity !== 'undefined') {
+ color = new fabric.Color(color).setAlpha(opacity).toRgba();
+ }
+ gradient.addColorStop(parseFloat(offset), color);
+ }
+
+ return gradient;
+ }
+ });
+
+ fabric.util.object.extend(fabric.Gradient, {
+
+ /* _FROM_SVG_START_ */
+ /**
+ * Returns {@link fabric.Gradient} instance from an SVG element
+ * @static
+ * @memberof fabric.Gradient
+ * @param {SVGGradientElement} el SVG gradient element
+ * @param {fabric.Object} instance
+ * @return {fabric.Gradient} Gradient instance
+ * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
+ * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
+ */
+ fromElement: function(el, instance) {
+
+ /**
+ * @example:
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+ var colorStopEls = el.getElementsByTagName('stop'),
+ type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'),
+ gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
+ colorStops = [],
+ coords = { };
+
+ if (type === 'linear') {
+ coords = getLinearCoords(el);
+ }
+ else if (type === 'radial') {
+ coords = getRadialCoords(el);
+ }
+
+ for (var i = colorStopEls.length; i--; ) {
+ colorStops.push(getColorStop(colorStopEls[i]));
+ }
+
+ _convertPercentUnitsToValues(instance, coords);
+
+ return new fabric.Gradient({
+ type: type,
+ coords: coords,
+ gradientUnits: gradientUnits,
+ colorStops: colorStops
+ });
+ },
+ /* _FROM_SVG_END_ */
+
+ /**
+ * Returns {@link fabric.Gradient} instance from its object representation
+ * @static
+ * @memberof fabric.Gradient
+ * @param {Object} obj
+ * @param {Object} [options] Options object
+ */
+ forObject: function(obj, options) {
+ options || (options = { });
+ _convertPercentUnitsToValues(obj, options);
+ return new fabric.Gradient(options);
+ }
+ });
+
+ /**
+ * @private
+ */
+ function _convertPercentUnitsToValues(object, options) {
+ for (var prop in options) {
+ if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
+ var percents = parseFloat(options[prop], 10);
+ if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
+ options[prop] = fabric.util.toFixed(object.width * percents / 100, 2);
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ options[prop] = fabric.util.toFixed(object.height * percents / 100, 2);
+ }
+ }
+ normalize(options, prop, object);
+ }
+ }
+
+ // normalize rendering point (should be from top/left corner rather than center of the shape)
+ function normalize(options, prop, object) {
+ if (prop === 'x1' || prop === 'x2') {
+ options[prop] -= fabric.util.toFixed(object.width / 2, 2);
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ options[prop] -= fabric.util.toFixed(object.height / 2, 2);
+ }
+ }
+
+ /* _TO_SVG_START_ */
+ /**
+ * @private
+ */
+ function _convertValuesToPercentUnits(object, options) {
+ for (var prop in options) {
+
+ normalize(options, prop, object);
+
+ // convert to percent units
+ if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
+ options[prop] = fabric.util.toFixed(options[prop] / object.width * 100, 2) + '%';
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ options[prop] = fabric.util.toFixed(options[prop] / object.height * 100, 2) + '%';
+ }
+ }
+ }
+ /* _TO_SVG_END_ */
+
+})();
+
+
+/**
+ * Pattern class
+ * @class fabric.Pattern
+ * @see {@link http://fabricjs.com/patterns/|Pattern demo}
+ * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo}
+ * @see {@link fabric.Pattern#initialize} for constructor definition
+ */
+fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
+
+ /**
+ * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
+ * @type String
+ * @default
+ */
+ repeat: 'repeat',
+
+ /**
+ * Pattern horizontal offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetX: 0,
+
+ /**
+ * Pattern vertical offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetY: 0,
+
+ /**
+ * Constructor
+ * @param {Object} [options] Options object
+ * @return {fabric.Pattern} thisArg
+ */
+ initialize: function(options) {
+ options || (options = { });
+
+ this.id = fabric.Object.__uid++;
+
+ if (options.source) {
+ if (typeof options.source === 'string') {
+ // function string
+ if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') {
+ this.source = new Function(fabric.util.getFunctionBody(options.source));
+ }
+ else {
+ // img src string
+ var _this = this;
+ this.source = fabric.util.createImage();
+ fabric.util.loadImage(options.source, function(img) {
+ _this.source = img;
+ });
+ }
+ }
+ else {
+ // img element
+ this.source = options.source;
+ }
+ }
+ if (options.repeat) {
+ this.repeat = options.repeat;
+ }
+ if (options.offsetX) {
+ this.offsetX = options.offsetX;
+ }
+ if (options.offsetY) {
+ this.offsetY = options.offsetY;
+ }
+ },
+
+ /**
+ * Returns object representation of a pattern
+ * @return {Object} Object representation of a pattern instance
+ */
+ toObject: function() {
+
+ var source;
+
+ // callback
+ if (typeof this.source === 'function') {
+ source = String(this.source);
+ }
+ // element
+ else if (typeof this.source.src === 'string') {
+ source = this.source.src;
+ }
+
+ return {
+ source: source,
+ repeat: this.repeat,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns SVG representation of a pattern
+ * @param {fabric.Object} object
+ * @return {String} SVG representation of a pattern
+ */
+ toSVG: function(object) {
+ var patternSource = typeof this.source === 'function' ? this.source() : this.source;
+ var patternWidth = patternSource.width / object.getWidth();
+ var patternHeight = patternSource.height / object.getHeight();
+ var patternImgSrc = '';
+
+ if (patternSource.src) {
+ patternImgSrc = patternSource.src;
+ }
+ else if (patternSource.toDataURL) {
+ patternImgSrc = patternSource.toDataURL();
+ }
+
+ return '' +
+ ' ' +
+ ' ';
+ },
+ /* _TO_SVG_END_ */
+
+ /**
+ * Returns an instance of CanvasPattern
+ * @param {CanvasRenderingContext2D} ctx Context to create pattern
+ * @return {CanvasPattern}
+ */
+ toLive: function(ctx) {
+ var source = typeof this.source === 'function' ? this.source() : this.source;
+ // if an image
+ if (typeof source.src !== 'undefined') {
+ if (!source.complete) return '';
+ if (source.naturalWidth === 0 || source.naturalHeight === 0) return '';
+ }
+ return ctx.createPattern(source, this.repeat);
+ }
+});
+
+
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Shadow) {
+ fabric.warn('fabric.Shadow is already defined.');
+ return;
+ }
+
+ /**
+ * Shadow class
+ * @class fabric.Shadow
+ * @see {@link http://fabricjs.com/shadows/|Shadow demo}
+ * @see {@link fabric.Shadow#initialize} for constructor definition
+ */
+ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ {
+
+ /**
+ * Shadow color
+ * @type String
+ * @default
+ */
+ color: 'rgb(0,0,0)',
+
+ /**
+ * Shadow blur
+ * @type Number
+ */
+ blur: 0,
+
+ /**
+ * Shadow horizontal offset
+ * @type Number
+ * @default
+ */
+ offsetX: 0,
+
+ /**
+ * Shadow vertical offset
+ * @type Number
+ * @default
+ */
+ offsetY: 0,
+
+ /**
+ * Whether the shadow should affect stroke operations
+ * @type Boolean
+ * @default
+ */
+ affectStroke: false,
+
+ /**
+ * Indicates whether toObject should include default values
+ * @type Boolean
+ * @default
+ */
+ includeDefaultValues: true,
+
+ /**
+ * Constructor
+ * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")
+ * @return {fabric.Shadow} thisArg
+ */
+ initialize: function(options) {
+ if (typeof options === 'string') {
+ options = this._parseShadow(options);
+ }
+
+ for (var prop in options) {
+ this[prop] = options[prop];
+ }
+
+ this.id = fabric.Object.__uid++;
+ },
+
+ /**
+ * @private
+ * @param {String} shadow Shadow value to parse
+ * @return {Object} Shadow object with color, offsetX, offsetY and blur
+ */
+ _parseShadow: function(shadow) {
+ var shadowStr = shadow.trim();
+
+ var offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ],
+ color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)';
+
+ return {
+ color: color.trim(),
+ offsetX: parseInt(offsetsAndBlur[1], 10) || 0,
+ offsetY: parseInt(offsetsAndBlur[2], 10) || 0,
+ blur: parseInt(offsetsAndBlur[3], 10) || 0
+ };
+ },
+
+ /**
+ * Returns a string representation of an instance
+ * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow
+ * @return {String} Returns CSS3 text-shadow declaration
+ */
+ toString: function() {
+ return [this.offsetX, this.offsetY, this.blur, this.color].join('px ');
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns SVG representation of a shadow
+ * @param {fabric.Object} object
+ * @return {String} SVG representation of a shadow
+ */
+ toSVG: function(object) {
+ var mode = 'SourceAlpha';
+
+ if (object && (object.fill === this.color || object.stroke === this.color)) {
+ mode = 'SourceGraphic';
+ }
+
+ return (
+ '' +
+ ' ' +
+ ' ' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ');
+ },
+ /* _TO_SVG_END_ */
+
+ /**
+ * Returns object representation of a shadow
+ * @return {Object} Object representation of a shadow instance
+ */
+ toObject: function() {
+ if (this.includeDefaultValues) {
+ return {
+ color: this.color,
+ blur: this.blur,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ }
+ var obj = { }, proto = fabric.Shadow.prototype;
+ if (this.color !== proto.color) {
+ obj.color = this.color;
+ }
+ if (this.blur !== proto.blur) {
+ obj.blur = this.blur;
+ }
+ if (this.offsetX !== proto.offsetX) {
+ obj.offsetX = this.offsetX;
+ }
+ if (this.offsetY !== proto.offsetY) {
+ obj.offsetY = this.offsetY;
+ }
+ return obj;
+ }
+ });
+
+ /**
+ * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
+ * @static
+ * @field
+ * @memberOf fabric.Shadow
+ */
+ fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/;
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function () {
+
+ "use strict";
+
+ if (fabric.StaticCanvas) {
+ fabric.warn('fabric.StaticCanvas is already defined.');
+ return;
+ }
+
+ // aliases for faster resolution
+ var extend = fabric.util.object.extend,
+ getElementOffset = fabric.util.getElementOffset,
+ removeFromArray = fabric.util.removeFromArray,
+
+ CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
+
+ /**
+ * Static canvas class
+ * @class fabric.StaticCanvas
+ * @mixes fabric.Collection
+ * @mixes fabric.Observable
+ * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo}
+ * @see {@link fabric.StaticCanvas#initialize} for constructor definition
+ * @fires before:render
+ * @fires after:render
+ * @fires canvas:cleared
+ * @fires object:added
+ * @fires object:removed
+ */
+ fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ {
+
+ /**
+ * Constructor
+ * @param {HTMLElement | String} el <canvas> element to initialize instance on
+ * @param {Object} [options] Options object
+ * @return {Object} thisArg
+ */
+ initialize: function(el, options) {
+ options || (options = { });
+
+ this._initStatic(el, options);
+ fabric.StaticCanvas.activeInstance = this;
+ },
+
+ /**
+ * Background color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
+ * @type {(String|fabric.Pattern)}
+ * @default
+ */
+ backgroundColor: '',
+
+ /**
+ * Background image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
+ * Backwards incompatibility note: The "backgroundImageOpacity"
+ * and "backgroundImageStretch" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
+ * @type fabric.Image
+ * @default
+ */
+ backgroundImage: null,
+
+ /**
+ * Overlay color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
+ * @since 1.3.9
+ * @type {(String|fabric.Pattern)}
+ * @default
+ */
+ overlayColor: '',
+
+ /**
+ * Overlay image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
+ * Backwards incompatibility note: The "overlayImageLeft"
+ * and "overlayImageTop" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
+ * @type fabric.Image
+ * @default
+ */
+ overlayImage: null,
+
+ /**
+ * Indicates whether toObject/toDatalessObject should include default values
+ * @type Boolean
+ * @default
+ */
+ includeDefaultValues: true,
+
+ /**
+ * Indicates whether objects' state should be saved
+ * @type Boolean
+ * @default
+ */
+ stateful: true,
+
+ /**
+ * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
+ * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once
+ * (followed by a manual rendering after addition/deletion)
+ * @type Boolean
+ * @default
+ */
+ renderOnAddRemove: true,
+
+ /**
+ * Function that determines clipping of entire canvas area
+ * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ}
+ * @type Function
+ * @default
+ */
+ clipTo: null,
+
+ /**
+ * Indicates whether object controls (borders/controls) are rendered above overlay image
+ * @type Boolean
+ * @default
+ */
+ controlsAboveOverlay: false,
+
+ /**
+ * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas
+ * @type Boolean
+ * @default
+ */
+ allowTouchScrolling: false,
+
+ /**
+ * Callback; invoked right before object is about to be scaled/rotated
+ * @param {fabric.Object} target Object that's about to be scaled/rotated
+ */
+ onBeforeScaleRotate: function () {
+ /* NOOP */
+ },
+
+ /**
+ * @private
+ * @param {HTMLElement | String} el <canvas> element to initialize instance on
+ * @param {Object} [options] Options object
+ */
+ _initStatic: function(el, options) {
+ this._objects = [];
+
+ this._createLowerCanvas(el);
+ this._initOptions(options);
+
+ if (options.overlayImage) {
+ this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
+ }
+ if (options.backgroundImage) {
+ this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
+ }
+ if (options.backgroundColor) {
+ this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
+ }
+ if (options.overlayColor) {
+ this.setOverlayColor(options.overlayColor, this.renderAll.bind(this));
+ }
+ this.calcOffset();
+ },
+
+ /**
+ * Calculates canvas element offset relative to the document
+ * This method is also attached as "resize" event handler of window
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ calcOffset: function () {
+ this._offset = getElementOffset(this.lowerCanvasEl);
+ return this;
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
+ * @param {Function} callback callback to invoke when image is loaded and set as an overlay
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
+ * @example Normal overlayImage with left/top = 0
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example overlayImage with different properties
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched overlayImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched overlayImage #2 - width/height correspond to canvas width/height
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ */
+ setOverlayImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('overlayImage', image, callback, options);
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
+ * @example Normal backgroundImage with left/top = 0
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example backgroundImage with different properties
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ */
+ setBackgroundImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('backgroundImage', image, callback, options);
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
+ * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
+ * @example Normal overlayColor - color value
+ * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png'
+ * }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor with repeat and offset
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
+ */
+ setOverlayColor: function(overlayColor, callback) {
+ return this.__setBgOverlayColor('overlayColor', overlayColor, callback);
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
+ * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
+ * @example Normal backgroundColor - color value
+ * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as backgroundColor
+ * canvas.setBackgroundColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png'
+ * }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as backgroundColor with repeat and offset
+ * canvas.setBackgroundColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
+ */
+ setBackgroundColor: function(backgroundColor, callback) {
+ return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}
+ * or {@link fabric.StaticCanvas#overlayImage|overlayImage})
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background or overlay to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
+ */
+ __setBgOverlayImage: function(property, image, callback, options) {
+ if (typeof image === 'string') {
+ fabric.util.loadImage(image, function(img) {
+ this[property] = new fabric.Image(img, options);
+ callback && callback();
+ }, this);
+ }
+ else {
+ this[property] = image;
+ callback && callback();
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor}
+ * or {@link fabric.StaticCanvas#overlayColor|overlayColor})
+ * @param {(Object|String)} color Object with pattern information or color value
+ * @param {Function} [callback] Callback is invoked when color is set
+ */
+ __setBgOverlayColor: function(property, color, callback) {
+ if (color.source) {
+ var _this = this;
+ fabric.util.loadImage(color.source, function(img) {
+ _this[property] = new fabric.Pattern({
+ source: img,
+ repeat: color.repeat,
+ offsetX: color.offsetX,
+ offsetY: color.offsetY
+ });
+ callback && callback();
+ });
+ }
+ else {
+ this[property] = color;
+ callback && callback();
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ */
+ _createCanvasElement: function() {
+ var element = fabric.document.createElement('canvas');
+ if (!element.style) {
+ element.style = { };
+ }
+ if (!element) {
+ throw CANVAS_INIT_ERROR;
+ }
+ this._initCanvasElement(element);
+ return element;
+ },
+
+ /**
+ * @private
+ * @param {HTMLElement} element
+ */
+ _initCanvasElement: function(element) {
+ fabric.util.createCanvasElement(element);
+
+ if (typeof element.getContext === 'undefined') {
+ throw CANVAS_INIT_ERROR;
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [options] Options object
+ */
+ _initOptions: function (options) {
+ for (var prop in options) {
+ this[prop] = options[prop];
+ }
+
+ this.width = parseInt(this.lowerCanvasEl.width, 10) || 0;
+ this.height = parseInt(this.lowerCanvasEl.height, 10) || 0;
+
+ if (!this.lowerCanvasEl.style) return;
+
+ this.lowerCanvasEl.style.width = this.width + 'px';
+ this.lowerCanvasEl.style.height = this.height + 'px';
+ },
+
+ /**
+ * Creates a bottom canvas
+ * @private
+ * @param {HTMLElement} [canvasEl]
+ */
+ _createLowerCanvas: function (canvasEl) {
+ this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
+ this._initCanvasElement(this.lowerCanvasEl);
+
+ fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
+
+ if (this.interactive) {
+ this._applyCanvasStyle(this.lowerCanvasEl);
+ }
+
+ this.contextContainer = this.lowerCanvasEl.getContext('2d');
+ },
+
+ /**
+ * Returns canvas width (in px)
+ * @return {Number}
+ */
+ getWidth: function () {
+ return this.width;
+ },
+
+ /**
+ * Returns canvas height (in px)
+ * @return {Number}
+ */
+ getHeight: function () {
+ return this.height;
+ },
+
+ /**
+ * Sets width of this canvas instance
+ * @param {Number} width value to set width to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setWidth: function (value) {
+ return this._setDimension('width', value);
+ },
+
+ /**
+ * Sets height of this canvas instance
+ * @param {Number} height value to set height to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setHeight: function (value) {
+ return this._setDimension('height', value);
+ },
+
+ /**
+ * Sets dimensions (width, height) of this canvas instance
+ * @param {Object} dimensions Object with width/height properties
+ * @param {Number} [dimensions.width] Width of canvas element
+ * @param {Number} [dimensions.height] Height of canvas element
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ setDimensions: function(dimensions) {
+ for (var prop in dimensions) {
+ this._setDimension(prop, dimensions[prop]);
+ }
+ return this;
+ },
+
+ /**
+ * Helper for setting width/height
+ * @private
+ * @param {String} prop property (width|height)
+ * @param {Number} value value to set property to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ _setDimension: function (prop, value) {
+ this.lowerCanvasEl[prop] = value;
+ this.lowerCanvasEl.style[prop] = value + 'px';
+
+ if (this.upperCanvasEl) {
+ this.upperCanvasEl[prop] = value;
+ this.upperCanvasEl.style[prop] = value + 'px';
+ }
+
+ if (this.cacheCanvasEl) {
+ this.cacheCanvasEl[prop] = value;
+ }
+
+ if (this.wrapperEl) {
+ this.wrapperEl.style[prop] = value + 'px';
+ }
+
+ this[prop] = value;
+
+ this.calcOffset();
+ this.renderAll();
+
+ return this;
+ },
+
+ /**
+ * Returns <canvas> element corresponding to this instance
+ * @return {HTMLCanvasElement}
+ */
+ getElement: function () {
+ return this.lowerCanvasEl;
+ },
+
+ /**
+ * Returns currently selected object, if any
+ * @return {fabric.Object}
+ */
+ getActiveObject: function() {
+ return null;
+ },
+
+ /**
+ * Returns currently selected group of object, if any
+ * @return {fabric.Group}
+ */
+ getActiveGroup: function() {
+ return null;
+ },
+
+ /**
+ * Given a context, renders an object on that context
+ * @param {CanvasRenderingContext2D} ctx Context to render object on
+ * @param {fabric.Object} object Object to render
+ * @private
+ */
+ _draw: function (ctx, object) {
+ if (!object) return;
+
+ if (this.controlsAboveOverlay) {
+ var hasBorders = object.hasBorders, hasControls = object.hasControls;
+ object.hasBorders = object.hasControls = false;
+ object.render(ctx);
+ object.hasBorders = hasBorders;
+ object.hasControls = hasControls;
+ }
+ else {
+ object.render(ctx);
+ }
+ },
+
+ /**
+ * @private
+ * @param {fabric.Object} obj Object that was added
+ */
+ _onObjectAdded: function(obj) {
+ this.stateful && obj.setupState();
+ obj.setCoords();
+ obj.canvas = this;
+ this.fire('object:added', { target: obj });
+ obj.fire('added');
+ },
+
+ /**
+ * @private
+ * @param {fabric.Object} obj Object that was removed
+ */
+ _onObjectRemoved: function(obj) {
+ // removing active object should fire "selection:cleared" events
+ if (this.getActiveObject() === obj) {
+ this.fire('before:selection:cleared', { target: obj });
+ this._discardActiveObject();
+ this.fire('selection:cleared');
+ }
+
+ this.fire('object:removed', { target: obj });
+ obj.fire('removed');
+ },
+
+ /**
+ * Clears specified context of canvas element
+ * @param {CanvasRenderingContext2D} ctx Context to clear
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ clearContext: function(ctx) {
+ ctx.clearRect(0, 0, this.width, this.height);
+ return this;
+ },
+
+ /**
+ * Returns context of canvas where objects are drawn
+ * @return {CanvasRenderingContext2D}
+ */
+ getContext: function () {
+ return this.contextContainer;
+ },
+
+ /**
+ * Clears all contexts (background, main, top) of an instance
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ clear: function () {
+ this._objects.length = 0;
+ if (this.discardActiveGroup) {
+ this.discardActiveGroup();
+ }
+ if (this.discardActiveObject) {
+ this.discardActiveObject();
+ }
+ this.clearContext(this.contextContainer);
+ if (this.contextTop) {
+ this.clearContext(this.contextTop);
+ }
+ this.fire('canvas:cleared');
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Renders both the top canvas and the secondary container canvas.
+ * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ renderAll: function (allOnTop) {
+
+ var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'];
+ var activeGroup = this.getActiveGroup();
+
+ if (this.contextTop && this.selection && !this._groupSelector) {
+ this.clearContext(this.contextTop);
+ }
+
+ if (!allOnTop) {
+ this.clearContext(canvasToDrawOn);
+ }
+
+ this.fire('before:render');
+
+ if (this.clipTo) {
+ fabric.util.clipContext(this, canvasToDrawOn);
+ }
+
+ this._renderBackground(canvasToDrawOn);
+ this._renderObjects(canvasToDrawOn, activeGroup);
+ this._renderActiveGroup(canvasToDrawOn, activeGroup);
+
+ if (this.clipTo) {
+ canvasToDrawOn.restore();
+ }
+
+ this._renderOverlay(canvasToDrawOn);
+
+ if (this.controlsAboveOverlay && this.interactive) {
+ this.drawControls(canvasToDrawOn);
+ }
+
+ this.fire('after:render');
+
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
+ */
+ _renderObjects: function(ctx, activeGroup) {
+ for (var i = 0, length = this._objects.length; i < length; ++i) {
+ if (!activeGroup ||
+ (activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
+ this._draw(ctx, this._objects[i]);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
+ */
+ _renderActiveGroup: function(ctx, activeGroup) {
+
+ // delegate rendering to group selection (if one exists)
+ if (activeGroup) {
+
+ //Store objects in group preserving order, then replace
+ var sortedObjects = [];
+ this.forEachObject(function (object) {
+ if (activeGroup.contains(object)) {
+ sortedObjects.push(object);
+ }
+ });
+ activeGroup._set('objects', sortedObjects);
+ this._draw(ctx, activeGroup);
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderBackground: function(ctx) {
+ if (this.backgroundColor) {
+ ctx.fillStyle = this.backgroundColor.toLive
+ ? this.backgroundColor.toLive(ctx)
+ : this.backgroundColor;
+
+ ctx.fillRect(
+ this.backgroundColor.offsetX || 0,
+ this.backgroundColor.offsetY || 0,
+ this.width,
+ this.height);
+ }
+ if (this.backgroundImage) {
+ this.backgroundImage.render(ctx);
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderOverlay: function(ctx) {
+ if (this.overlayColor) {
+ ctx.fillStyle = this.overlayColor.toLive
+ ? this.overlayColor.toLive(ctx)
+ : this.overlayColor;
+
+ ctx.fillRect(
+ this.overlayColor.offsetX || 0,
+ this.overlayColor.offsetY || 0,
+ this.width,
+ this.height);
+ }
+ if (this.overlayImage) {
+ this.overlayImage.render(ctx);
+ }
+ },
+
+ /**
+ * Method to render only the top canvas.
+ * Also used to render the group selection box.
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ renderTop: function () {
+ var ctx = this.contextTop || this.contextContainer;
+ this.clearContext(ctx);
+
+ // we render the top context - last object
+ if (this.selection && this._groupSelector) {
+ this._drawSelection();
+ }
+
+ // delegate rendering to group selection if one exists
+ // used for drawing selection borders/controls
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ activeGroup.render(ctx);
+ }
+
+ if (this.overlayImage) {
+ ctx.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
+ }
+
+ this.fire('after:render');
+
+ return this;
+ },
+
+ /**
+ * Returns coordinates of a center of canvas.
+ * Returned value is an object with top and left properties
+ * @return {Object} object with "top" and "left" number values
+ */
+ getCenter: function () {
+ return {
+ top: this.getHeight() / 2,
+ left: this.getWidth() / 2
+ };
+ },
+
+ /**
+ * Centers object horizontally.
+ * You might need to call `setCoords` on an object after centering, to update controls area.
+ * @param {fabric.Object} object Object to center horizontally
+ * @return {fabric.Canvas} thisArg
+ */
+ centerObjectH: function (object) {
+ this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Centers object vertically.
+ * You might need to call `setCoords` on an object after centering, to update controls area.
+ * @param {fabric.Object} object Object to center vertically
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ centerObjectV: function (object) {
+ this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Centers object vertically and horizontally.
+ * You might need to call `setCoords` on an object after centering, to update controls area.
+ * @param {fabric.Object} object Object to center vertically and horizontally
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ centerObject: function(object) {
+ var center = this.getCenter();
+
+ this._centerObject(object, new fabric.Point(center.left, center.top));
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {fabric.Object} object Object to center
+ * @param {fabric.Point} center Center point
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ _centerObject: function(object, center) {
+ object.setPositionByOrigin(center, 'center', 'center');
+ return this;
+ },
+
+ /**
+ * Returs dataless JSON representation of canvas
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
+ * @return {String} json string
+ */
+ toDatalessJSON: function (propertiesToInclude) {
+ return this.toDatalessObject(propertiesToInclude);
+ },
+
+ /**
+ * Returns object representation of canvas
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
+ * @return {Object} object representation of an instance
+ */
+ toObject: function (propertiesToInclude) {
+ return this._toObjectMethod('toObject', propertiesToInclude);
+ },
+
+ /**
+ * Returns dataless object representation of canvas
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
+ * @return {Object} object representation of an instance
+ */
+ toDatalessObject: function (propertiesToInclude) {
+ return this._toObjectMethod('toDatalessObject', propertiesToInclude);
+ },
+
+ /**
+ * @private
+ */
+ _toObjectMethod: function (methodName, propertiesToInclude) {
+
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ this.discardActiveGroup();
+ }
+
+ var data = {
+ objects: this._toObjects(methodName, propertiesToInclude)
+ };
+
+ extend(data, this.__serializeBgOverlay());
+
+ fabric.util.populateWithProperties(this, data, propertiesToInclude);
+
+ if (activeGroup) {
+ this.setActiveGroup(new fabric.Group(activeGroup.getObjects()));
+ activeGroup.forEachObject(function(o) {
+ o.set('active', true);
+ });
+ }
+ return data;
+ },
+
+ /**
+ * @private
+ */
+ _toObjects: function(methodName, propertiesToInclude) {
+ return this.getObjects().map(function(instance) {
+ return this._toObject(instance, methodName, propertiesToInclude);
+ }, this);
+ },
+
+ /**
+ * @private
+ */
+ _toObject: function(instance, methodName, propertiesToInclude) {
+ var originalValue;
+
+ if (!this.includeDefaultValues) {
+ originalValue = instance.includeDefaultValues;
+ instance.includeDefaultValues = false;
+ }
+ var object = instance[methodName](propertiesToInclude);
+ if (!this.includeDefaultValues) {
+ instance.includeDefaultValues = originalValue;
+ }
+ return object;
+ },
+
+ /**
+ * @private
+ */
+ __serializeBgOverlay: function() {
+ var data = {
+ background: (this.backgroundColor && this.backgroundColor.toObject)
+ ? this.backgroundColor.toObject()
+ : this.backgroundColor
+ };
+
+ if (this.overlayColor) {
+ data.overlay = this.overlayColor.toObject
+ ? this.overlayColor.toObject()
+ : this.overlayColor;
+ }
+ if (this.backgroundImage) {
+ data.backgroundImage = this.backgroundImage.toObject();
+ }
+ if (this.overlayImage) {
+ data.overlayImage = this.overlayImage.toObject();
+ }
+
+ return data;
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns SVG representation of canvas
+ * @function
+ * @param {Object} [options] Options object for SVG output
+ * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included
+ * @param {Object} [options.viewBox] SVG viewbox object
+ * @param {Number} [options.viewBox.x] x-cooridnate of viewbox
+ * @param {Number} [options.viewBox.y] y-coordinate of viewbox
+ * @param {Number} [options.viewBox.width] Width of viewbox
+ * @param {Number} [options.viewBox.height] Height of viewbox
+ * @param {String} [options.encoding=UTF-8] Encoding of SVG output
+ * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation.
+ * @return {String} SVG string
+ * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}
+ * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo}
+ * @example Normal SVG output
+ * var svg = canvas.toSVG();
+ * @example SVG output without preamble (without <?xml ../>)
+ * var svg = canvas.toSVG({suppressPreamble: true});
+ * @example SVG output with viewBox attribute
+ * var svg = canvas.toSVG({
+ * viewBox: {
+ * x: 100,
+ * y: 100,
+ * width: 200,
+ * height: 300
+ * }
+ * });
+ * @example SVG output with different encoding (default: UTF-8)
+ * var svg = canvas.toSVG({encoding: 'ISO-8859-1'});
+ * @example Modify SVG output with reviver function
+ * var svg = canvas.toSVG(null, function(svg) {
+ * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', '');
+ * });
+ */
+ toSVG: function(options, reviver) {
+ options || (options = { });
+
+ var markup = [];
+
+ this._setSVGPreamble(markup, options);
+ this._setSVGHeader(markup, options);
+
+ this._setSVGBgOverlayColor(markup, 'backgroundColor');
+ this._setSVGBgOverlayImage(markup, 'backgroundImage');
+
+ this._setSVGObjects(markup, reviver);
+
+ this._setSVGBgOverlayColor(markup, 'overlayColor');
+ this._setSVGBgOverlayImage(markup, 'overlayImage');
+
+ markup.push('