Blame | Letzte Änderung | Log anzeigen | RSS feed
/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` *//*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric = fabric || { version: "1.5.0" };if (typeof exports !== 'undefined') {exports.fabric = fabric;}if (typeof document !== 'undefined' && typeof window !== 'undefined') {fabric.document = document;fabric.window = window;// ensure globality even if entire library were function wrapped (as in Meteor.js packaging system)window.fabric = fabric;}else {// assume we're running under node.js when document/window are not presentfabric.document = require("jsdom").jsdom("<!DOCTYPE html><html><head></head><body></body></html>");if (fabric.document.createWindow) {fabric.window = fabric.document.createWindow();} else {fabric.window = fabric.document.parentWindow;}}/*** True when in environment that supports touch events* @type boolean*/fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;/*** True when in environment that's probably Node.js* @type boolean*/fabric.isLikelyNode = typeof Buffer !== 'undefined' &&typeof window === 'undefined';/* _FROM_SVG_START_ *//*** Attributes parsed from all SVG elements* @type array*/fabric.SHARED_ATTRIBUTES = ["display","transform","fill", "fill-opacity", "fill-rule","opacity","stroke", "stroke-dasharray", "stroke-linecap","stroke-linejoin", "stroke-miterlimit","stroke-opacity", "stroke-width"];/* _FROM_SVG_END_ *//*** Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.*/fabric.DPI = 96;fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';(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 passedif (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 passedelse 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. reasonslistenersForEvent[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 {...fabric.Object} object Zero or more fabric instances* @return {Self} thisArg*/add: function () {this._objects.push.apply(this._objects, arguments);for (var i = 0, length = arguments.length; i < length; i++) {this._onObjectAdded(arguments[i]);}this.renderOnAddRemove && this.renderAll();return this;},/*** Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)* An object should be an instance of (or inherit from) fabric.Object* @param {Object} object Object to insert* @param {Number} index Index to insert object at* @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs* @return {Self} thisArg* @chainable*/insertAt: function (object, index, nonSplicing) {var objects = this.getObjects();if (nonSplicing) {objects[index] = object;}else {objects.splice(index, 0, object);}this._onObjectAdded(object);this.renderOnAddRemove && this.renderAll();return this;},/*** Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)* @param {...fabric.Object} object Zero or more fabric instances* @return {Self} thisArg* @chainable*/remove: function() {var objects = this.getObjects(),index;for (var i = 0, length = arguments.length; i < length; i++) {index = objects.indexOf(arguments[i]);// only call onObjectRemoved if an object was actually removedif (index !== -1) {objects.splice(index, 1);this._onObjectRemoved(arguments[i]);}}this.renderOnAddRemove && this.renderAll();return this;},/*** Executes given function for each object in this group* @param {Function} callback* Callback invoked with current object as first argument,* index - as second and an array of all objects - as third.* 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} point The point to rotate* @param {fabric.Point} origin The origin of the rotation* @param {Number} radians The radians of the angle for the rotation* @return {fabric.Point} The new rotated point*/rotatePoint: function(point, origin, radians) {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);},/*** Apply transform t to point p* @static* @memberOf fabric.util* @param {fabric.Point} p The point to transform* @param {Array} t The transform* @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied* @return {fabric.Point} The transformed point*/transformPoint: function(p, t, ignoreOffset) {if (ignoreOffset) {return new fabric.Point(t[0] * p.x + t[2] * p.y,t[1] * p.x + t[3] * p.y);}return new fabric.Point(t[0] * p.x + t[2] * p.y + t[4],t[1] * p.x + t[3] * p.y + t[5]);},/*** Invert transformation t* @static* @memberOf fabric.util* @param {Array} t The transform* @return {Array} The inverted transform*/invertTransform: function(t) {var r = t.slice(),a = 1 / (t[0] * t[3] - t[1] * t[2]);r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0];var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r);r[4] = -o.x;r[5] = -o.y;return r;},/*** A wrapper around Number#toFixed, which contrary to native method returns number, not string.* @static* @memberOf fabric.util* @param {Number|String} number number to operate on* @param {Number} fractionDigits number of fraction digits to "leave"* @return {Number}*/toFixed: function(number, fractionDigits) {return parseFloat(Number(number).toFixed(fractionDigits));},/*** Converts from attribute value to pixel value if applicable.* Returns converted pixels or original value not converted.* @param {Number|String} value number to operate on* @return {Number|String}*/parseUnit: function(value, fontSize) {var unit = /\D{0,2}$/.exec(value),number = parseFloat(value);if (!fontSize) {fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;}switch (unit[0]) {case 'mm':return number * fabric.DPI / 25.4;case 'cm':return number * fabric.DPI / 2.54;case 'in':return number * fabric.DPI;case 'pt':return number * fabric.DPI / 72; // or * 4 / 3case 'pc':return number * fabric.DPI / 72 * 12; // or * 16case 'em':return number * fontSize;default:return number;}},/*** Function which always returns `false`.* @static* @memberOf fabric.util* @return {Boolean}*/falseFunction: function() {return false;},/*** Returns klass "Class" object of given namespace* @memberOf fabric.util* @param {String} type Type of object (eg. 'circle')* @param {String} namespace Namespace to get klass "Class" object from* @return {Object} klass "Class"*/getKlass: function(type, namespace) {// capitalize first letter onlytype = 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=935069if (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 {String} namespace Namespace to get klass "Class" object from* @param {Function} reviver Method for further parsing of object elements,* called after each fabric object created.*/enlivenObjects: function(objects, callback, namespace, reviver) {objects = objects || [ ];function onLoaded() {if (++numLoadedObjects === numTotalObjects) {callback && callback(enlivenedObjects);}}var enlivenedObjects = [ ],numLoadedObjects = 0,numTotalObjects = objects.length;if (!numTotalObjects) {callback && callback(enlivenedObjects);return;}objects.forEach(function (o, index) {// if sparse arrayif (!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;object = new fabric.PathGroup(elements, options);if (typeof path !== 'undefined') {object.setSourcePath(path);}return object;},/*** Populates an object with properties of another object* @static* @memberOf fabric.util* @param {Object} source Source object* @param {Object} destination Destination object* @return {Array} properties Propertie names to include*/populateWithProperties: function(source, destination, properties) {if (properties && Object.prototype.toString.call(properties) === '[object Array]') {for (var i = 0, len = properties.length; i < len; i++) {if (properties[i] in source) {destination[properties[i]] = source[properties[i]];}}}},/*** Draws a dashed line between two points** This method is used to draw dashed line around selection area.* See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>** @param {CanvasRenderingContext2D} ctx context* @param {Number} x start x coordinate* @param {Number} y start y coordinate* @param {Number} x2 end x coordinate* @param {Number} y2 end y coordinate* @param {Array} da dash array pattern*/drawDashedLine: function(ctx, x, y, x2, y2, da) {var dx = x2 - x,dy = y2 - y,len = sqrt(dx * dx + dy * dy),rot = atan2(dy, dx),dc = da.length,di = 0,draw = true;ctx.save();ctx.translate(x, y);ctx.moveTo(0, 0);ctx.rotate(rot);x = 0;while (len > x) {x += da[di++ % dc];if (x > len) {x = len;}ctx[draw ? 'lineTo' : 'moveTo'](x, 0);draw = !draw;}ctx.restore();},/*** Creates canvas element and initializes it via excanvas if necessary* @static* @memberOf fabric.util* @param {CanvasElement} [canvasEl] optional canvas element to initialize;* when not given, element is created implicitly* @return {CanvasElement} initialized canvas element*/createCanvasElement: function(canvasEl) {canvasEl || (canvasEl = fabric.document.createElement('canvas'));//jscs:disable requireCamelCaseOrUpperCaseIdentifiersif (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {G_vmlCanvasManager.initElement(canvasEl);}//jscs:enable requireCamelCaseOrUpperCaseIdentifiersreturn 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 introspectionif (!proto[getterName]) {proto[getterName] = (function(property) {return new Function('return this.get("' + property + '")');})(propName);}if (!proto[setterName]) {proto[setterName] = (function(property) {return new Function('value', 'return this.set("' + property + '", value)');})(propName);}}},/*** @static* @memberOf fabric.util* @param {fabric.Object} receiver Object implementing `clipTo` method* @param {CanvasRenderingContext2D} ctx Context to clip*/clipContext: function(receiver, ctx) {ctx.save();ctx.beginPath();receiver.clipTo(ctx);ctx.clip();},/*** Multiply matrix A by matrix B to nest transformations* @static* @memberOf fabric.util* @param {Array} a First transformMatrix* @param {Array} b Second transformMatrix* @return {Array} The product of the two transform matrices*/multiplyTransformMatrices: function(a, b) {// Matrix multiply a * breturn [a[0] * b[0] + a[2] * b[1],a[1] * b[0] + a[3] * b[1],a[0] * b[2] + a[2] * b[3],a[1] * b[2] + a[3] * b[3],a[0] * b[4] + a[2] * b[5] + a[4],a[1] * b[4] + a[3] * b[5] + a[5]];},/*** Returns string representation of function body* @param {Function} fn Function to get body of* @return {String} Function body*/getFunctionBody: function(fn) {return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];},/*** Returns true if context has transparent pixel* at specified location (taking tolerance into account)* @param {CanvasRenderingContext2D} ctx context* @param {Number} x x coordinate* @param {Number} y y coordinate* @param {Number} tolerance Tolerance*/isTransparent: function(ctx, x, y, tolerance) {// If tolerance is > 0 adjust start coords to take into account.// If moves off Canvas fix to 0if (tolerance > 0) {if (x > tolerance) {x -= tolerance;}else {x = 0;}if (y > tolerance) {y -= tolerance;}else {y = 0;}}var _isTransparent = true,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 = { },boundsOfCurveCache = { },_join = Array.prototype.join;/* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp* by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here* http://mozilla.org/MPL/2.0/*/function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {var argsString = _join.call(arguments);if (arcToSegmentsCache[argsString]) {return arcToSegmentsCache[argsString];}var PI = Math.PI, th = rotateX * PI / 180,sinTh = Math.sin(th),cosTh = Math.cos(th),fromX = 0, fromY = 0;rx = Math.abs(rx);ry = Math.abs(ry);var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,root = 0;if (pl < 0) {var s = Math.sqrt(1 - pl/(rx2 * ry2));rx *= s;ry *= s;}else {root = (large === sweep ? -1.0 : 1.0) *Math.sqrt( pl /(rx2 * py2 + ry2 * px2));}var cx = root * rx * py / ry,cy = -root * ry * px / rx,cx1 = cosTh * cx - sinTh * cy + toX * 0.5,cy1 = sinTh * cx + cosTh * cy + toY * 0.5,mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);if (sweep === 0 && dtheta > 0) {dtheta -= 2 * PI;}else if (sweep === 1 && dtheta < 0) {dtheta += 2 * PI;}// Convert into cubic bezier segments <= 90degvar segments = Math.ceil(Math.abs(dtheta / PI * 2)),result = [], mDelta = dtheta / segments,mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),th3 = mTheta + mDelta;for (var i = 0; i < segments; i++) {result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);fromX = result[i][4];fromY = result[i][5];mTheta = th3;th3 += mDelta;}arcToSegmentsCache[argsString] = result;return result;}function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {var argsString2 = _join.call(arguments);if (segmentToBezierCache[argsString2]) {return segmentToBezierCache[argsString2];}var costh2 = Math.cos(th2),sinth2 = Math.sin(th2),costh3 = Math.cos(th3),sinth3 = Math.sin(th3),toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2),cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2),cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);segmentToBezierCache[argsString2] = [cp1X, cp1Y,cp2X, cp2Y,toX, toY];return segmentToBezierCache[argsString2];}/** Private*/function calcVectorAngle(ux, uy, vx, vy) {var ta = Math.atan2(uy, ux),tb = Math.atan2(vy, vx);if (tb >= ta) {return tb - ta;}else {return 2 * Math.PI - (ta - tb);}}/*** Draws arc* @param {CanvasRenderingContext2D} ctx* @param {Number} fx* @param {Number} fy* @param {Array} coords*/fabric.util.drawArc = function(ctx, fx, fy, coords) {var rx = coords[0],ry = coords[1],rot = coords[2],large = coords[3],sweep = coords[4],tx = coords[5],ty = coords[6],segs = [[ ], [ ], [ ], [ ]],segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);for (var i = 0, len = segsNorm.length; i < len; i++) {segs[i][0] = segsNorm[i][0] + fx;segs[i][1] = segsNorm[i][1] + fy;segs[i][2] = segsNorm[i][2] + fx;segs[i][3] = segsNorm[i][3] + fy;segs[i][4] = segsNorm[i][4] + fx;segs[i][5] = segsNorm[i][5] + fy;ctx.bezierCurveTo.apply(ctx, segs[i]);}};/*** Calculate bounding box of a elliptic-arc* @param {Number} fx start point of arc* @param {Number} fy* @param {Number} rx horizontal radius* @param {Number} ry vertical radius* @param {Number} rot angle of horizontal axe* @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points* @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction* @param {Number} tx end point of arc* @param {Number} ty*/fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {var fromX = 0, fromY = 0, bound = [ ], bounds = [ ],segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot),boundCopy = [[ ], [ ]];for (var i = 0, len = segs.length; i < len; i++) {bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);boundCopy[0].x = bound[0].x + fx;boundCopy[0].y = bound[0].y + fy;boundCopy[1].x = bound[1].x + fx;boundCopy[1].y = bound[1].y + fy;bounds.push(boundCopy[0]);bounds.push(boundCopy[1]);fromX = segs[i][4];fromY = segs[i][5];}return bounds;};/*** Calculate bounding box of a beziercurve* @param {Number} x0 starting point* @param {Number} y0* @param {Number} x1 first control point* @param {Number} y1* @param {Number} x2 secondo control point* @param {Number} y2* @param {Number} x3 end of beizer* @param {Number} y3*/// taken from http://jsbin.com/ivomiq/56/edit no credits available for that.function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {var argsString = _join.call(arguments);if (boundsOfCurveCache[argsString]) {return boundsOfCurveCache[argsString];}var sqrt = Math.sqrt,min = Math.min, max = Math.max,abs = Math.abs, tvalues = [ ],bounds = [[ ], [ ]],a, b, c, t, t1, t2, b2ac, sqrtb2ac;b = 6 * x0 - 12 * x1 + 6 * x2;a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;c = 3 * x1 - 3 * x0;for (var i = 0; i < 2; ++i) {if (i > 0) {b = 6 * y0 - 12 * y1 + 6 * y2;a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;c = 3 * y1 - 3 * y0;}if (abs(a) < 1e-12) {if (abs(b) < 1e-12) {continue;}t = -c / b;if (0 < t && t < 1) {tvalues.push(t);}continue;}b2ac = b * b - 4 * c * a;if (b2ac < 0) {continue;}sqrtb2ac = sqrt(b2ac);t1 = (-b + sqrtb2ac) / (2 * a);if (0 < t1 && t1 < 1) {tvalues.push(t1);}t2 = (-b - sqrtb2ac) / (2 * a);if (0 < t2 && t2 < 1) {tvalues.push(t2);}}var x, y, j = tvalues.length, jlen = j, mt;while (j--) {t = tvalues[j];mt = 1 - t;x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);bounds[0][j] = x;y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);bounds[1][j] = y;}bounds[0][jlen] = x0;bounds[1][jlen] = y0;bounds[0][jlen + 1] = x3;bounds[1][jlen + 1] = y3;var result = [{x: min.apply(null, bounds[0]),y: min.apply(null, bounds[1])},{x: max.apply(null, bounds[0]),y: max.apply(null, bounds[1])}];boundsOfCurveCache[argsString] = result;return result;}fabric.util.getBoundsOfCurve = getBoundsOfCurve;})();(function() {var slice = Array.prototype.slice;/* _ES5_COMPAT_START_ */if (!Array.prototype.indexOf) {/*** Finds index of an element in an array* @param {Any} searchElement* @param {Number} [fromIndex]* @return {Number}*/Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {if (this === void 0 || this === null) {throw new TypeError();}var t = Object(this), len = t.length >>> 0;if (len === 0) {return -1;}var n = 0;if (arguments.length > 0) {n = Number(arguments[1]);if (n !== n) { // shortcut for verifying if it's NaNn = 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 thisif (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} [initial] Object to use as the first argument to the first call of the callback* @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 returnif (++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;}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 offor (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 <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>*/String.prototype.trim = function () {// this trim is not fully ES3 or ES5 compliant, but it should cover most cases for nowreturn this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');};}/* _ES5_COMPAT_END_ *//*** Camelizes a string* @memberOf fabric.util.string* @param {String} string String to camelize* @return {String} Camelized version of a string*/function camelize(string) {return string.replace(/-+(.)?/g, function(match, character) {return character ? character.toUpperCase() : '';});}/*** Capitalizes a string* @memberOf fabric.util.string* @param {String} string String to capitalize* @param {Boolean} [firstLetterOnly] If true only first letter is capitalized* and other letters stay untouched, if false first letter is capitalized* and other letters are converted to lowercase.* @return {String} Capitalized version of a string*/function capitalize(string, firstLetterOnly) {return string.charAt(0).toUpperCase() +(firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());}/*** Escapes XML in a string* @memberOf fabric.util.string* @param {String} string String to escape* @return {String} Escaped version of a string*/function escapeXml(string) {return string.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');}/*** String utilities* @namespace fabric.util.string*/fabric.util.string = {camelize: camelize,capitalize: capitalize,escapeXml: escapeXml};}());/* _ES5_COMPAT_START_ */(function() {var slice = Array.prototype.slice,apply = Function.prototype.apply,Dummy = function() { };if (!Function.prototype.bind) {/*** Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)* @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a>* @param {Object} thisArg Object to bind function to* @param {Any[]} [...] Values to pass to a bound function* @return {Function}*/Function.prototype.bind = function(thisArg) {var _this = this, args = slice.call(arguments, 1), bound;if (args.length) {bound = function() {return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));};}else {/** @ignore */bound = function() {return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments);};}Dummy.prototype = this.prototype;bound.prototype = new Dummy();return bound;};}})();/* _ES5_COMPAT_END_ */(function() {var slice = Array.prototype.slice, emptyFunction = function() { },IS_DONTENUM_BUGGY = (function() {for (var p in { toString: 1 }) {if (p === 'toString') {return false;}}return true;})(),/** @ignore */addMethods = function(klass, source, parent) {for (var property in source) {if (property in klass.prototype &&typeof klass.prototype[property] === 'function' &&(source[property] + '').indexOf('callSuper') > -1) {klass.prototype[property] = (function(property) {return function() {var superclass = this.constructor.superclass;this.constructor.superclass = parent;var returnValue = source[property].apply(this, arguments);this.constructor.superclass = superclass;if (property !== 'initialize') {return returnValue;}};})(property);}else {klass.prototype[property] = source[property];}if (IS_DONTENUM_BUGGY) {if (source.toString !== Object.prototype.toString) {klass.prototype.toString = source.toString;}if (source.valueOf !== Object.prototype.valueOf) {klass.prototype.valueOf = source.valueOf;}}}};function Subclass() { }function callSuper(methodName) {var fn = this.constructor.superclass.prototype[methodName];return (arguments.length > 1)? fn.apply(this, slice.call(arguments, 1)): fn.call(this);}/*** Helper for creation of "classes".* @memberOf fabric.util* @param {Function} [parent] optional "Class" to inherit from* @param {Object} [properties] Properties shared by all instances of this class* (be careful modifying objects defined here as this would affect all instances)*/function createClass() {var parent = null,properties = slice.call(arguments, 0);if (typeof properties[0] === 'function') {parent = properties.shift();}function klass() {this.initialize.apply(this, arguments);}klass.superclass = parent;klass.subclasses = [ ];if (parent) {Subclass.prototype = parent.prototype;klass.prototype = new Subclass();parent.subclasses.push(klass);}for (var i = 0, length = properties.length; i < length; i++) {addMethods(klass, properties[i], parent);}if (!klass.prototype.initialize) {klass.prototype.initialize = emptyFunction;}klass.prototype.constructor = klass;klass.prototype.callSuper = callSuper;return klass;}fabric.util.createClass = createClass;})();(function () {var unknown = 'unknown';/* EVENT HANDLING */function areHostMethods(object) {var methodNames = Array.prototype.slice.call(arguments, 1),t, i, len = methodNames.length;for (i = 0; i < len; i++) {t = typeof object[methodNames[i]];if (!(/^(?:function|object|unknown)$/).test(t)) {return false;}}return true;}/** @ignore */var getElement,setElement,getUniqueId = (function () {var uid = 0;return function (element) {return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);};})();(function () {var elements = { };/** @ignore */getElement = function (uid) {return elements[uid];};/** @ignore */setElement = function (uid, element) {elements[uid] = element;};})();function createListener(uid, handler) {return {handler: handler,wrappedHandler: createWrappedHandler(uid, handler)};}function createWrappedHandler(uid, handler) {return function (e) {handler.call(getElement(uid), e || fabric.window.event);};}function createDispatcher(uid, eventName) {return function (e) {if (handlers[uid] && handlers[uid][eventName]) {var handlersForEvent = handlers[uid][eventName];for (var i = 0, len = handlersForEvent.length; i < len; i++) {handlersForEvent[i].call(this, e || fabric.window.event);}}};}var shouldUseAddListenerRemoveListener = (areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),shouldUseAttachEventDetachEvent = (areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),// IE branchlisteners = { },// DOM L0 branchhandlers = { },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),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 laterreturn (typeof event.clientX !== unknown ? event.clientX : 0);},pointerY = function(event) {return (typeof event.clientY !== unknown ? event.clientY : 0);};function _getPointer(event, pageProp, clientProp) {var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';return (event[touchProp] && event[touchProp][0]? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]))|| event[clientProp]: event[clientProp]);}if (fabric.isTouchSupported) {pointerX = function(event) {return _getPointer(event, 'pageX', 'clientX');};pointerY = function(event) {return _getPointer(event, 'pageY', 'clientY');};}fabric.util.getPointer = getPointer;fabric.util.object.extend(fabric.util, fabric.Observable);})();(function () {/*** Cross-browser wrapper for setting element's style* @memberOf fabric.util* @param {HTMLElement} element* @param {Object} styles* @return {HTMLElement} Element that was passed as a first argument*/function setStyle(element, styles) {var elementStyle = element.style;if (!elementStyle) {return element;}if (typeof styles === 'string') {element.style.cssText += ';' + styles;return styles.indexOf('opacity') > -1? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]): element;}for (var property in styles) {if (property === 'opacity') {setOpacity(element, styles[property]);}else {var normalizedProperty = (property === 'float' || property === 'cssFloat')? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat'): property;elementStyle[normalizedProperty] = styles[property];}}return element;}var parseEl = fabric.document.createElement('div'),supportsOpacity = typeof parseEl.style.opacity === 'string',supportsFilters = typeof parseEl.style.filter === 'string',reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,/** @ignore */setOpacity = function (element) { return element; };if (supportsOpacity) {/** @ignore */setOpacity = function(element, value) {element.style.opacity = value;return element;};}else if (supportsFilters) {/** @ignore */setOpacity = function(element, value) {var es = element.style;if (element.currentStyle && !element.currentStyle.hasLayout) {es.zoom = 1;}if (reOpacity.test(es.filter)) {value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');es.filter = es.filter.replace(reOpacity, value);}else {es.filter += ' alpha(opacity=' + (value * 100) + ')';}return element;};}fabric.util.setStyle = setStyle;})();(function() {var _slice = Array.prototype.slice;/*** Takes id and returns an element with that id (if one exists in a document)* @memberOf fabric.util* @param {String|HTMLElement} id* @return {HTMLElement|null}*/function getById(id) {return typeof id === 'string' ? fabric.document.getElementById(id) : id;}var sliceCanConvertNodelists,/*** Converts an array-like object (e.g. arguments or NodeList) to an array* @memberOf fabric.util* @param {Object} arrayLike* @return {Array}*/toArray = function(arrayLike) {return _slice.call(arrayLike, 0);};try {sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;}catch (err) { }if (!sliceCanConvertNodelists) {toArray = function(arrayLike) {var arr = new Array(arrayLike.length), i = arrayLike.length;while (i--) {arr[i] = arrayLike[i];}return arr;};}/*** Creates specified element with specified attributes* @memberOf fabric.util* @param {String} tagName Type of an element to create* @param {Object} [attributes] Attributes to set on an element* @return {HTMLElement} Newly created element*/function makeElement(tagName, attributes) {var el = fabric.document.createElement(tagName);for (var prop in attributes) {if (prop === 'class') {el.className = attributes[prop];}else if (prop === 'for') {el.htmlFor = attributes[prop];}else {el.setAttribute(prop, attributes[prop]);}}return el;}/*** Adds class to an element* @memberOf fabric.util* @param {HTMLElement} element Element to add class to* @param {String} className Class to add to an element*/function addClass(element, className) {if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {element.className += (element.className ? ' ' : '') + className;}}/*** Wraps element with another element* @memberOf fabric.util* @param {HTMLElement} element Element to wrap* @param {HTMLElement|String} wrapper Element to wrap with* @param {Object} [attributes] Attributes to set on a wrapper* @return {HTMLElement} wrapper*/function wrapElement(element, wrapper, attributes) {if (typeof wrapper === 'string') {wrapper = makeElement(wrapper, attributes);}if (element.parentNode) {element.parentNode.replaceChild(wrapper, element);}wrapper.appendChild(element);return wrapper;}/*** Returns element scroll offsets* @memberOf fabric.util* @param {HTMLElement} element Element to operate on* @param {HTMLElement} upperCanvasEl Upper canvas element* @return {Object} Object with left/top values*/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.nodeType === 1 &&fabric.util.getElementStyle(element, 'position') === 'fixed') {firstFixedAncestor = element;}if (element.nodeType === 1 &&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,doc = element && element.ownerDocument,box = { left: 0, top: 0 },offset = { left: 0, top: 0 },scrollLeftTop,offsetAttributes = {borderLeftWidth: 'left',borderTopWidth: 'top',paddingLeft: 'left',paddingTop: 'top'};if (!doc) {return { 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.*/var getElementStyle;if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {getElementStyle = function(element, attr) {var style = fabric.document.defaultView.getComputedStyle(element, null);return style ? style[attr] : undefined;};}else {getElementStyle = function(element, attr) {var value = element.style[attr];if (!value && element.currentStyle) {value = element.currentStyle[attr];}return value;};}(function () {var style = fabric.document.documentElement.style,selectProp = 'userSelect' in style? 'userSelect': 'MozUserSelect' in style? 'MozUserSelect': 'WebkitUserSelect' in style? 'WebkitUserSelect': 'KhtmlUserSelect' in style? 'KhtmlUserSelect': '';/*** Makes element unselectable* @memberOf fabric.util* @param {HTMLElement} element Element to make unselectable* @return {HTMLElement} Element that was passed in*/function makeElementUnselectable(element) {if (typeof element.onselectstart !== 'undefined') {element.onselectstart = fabric.util.falseFunction;}if (selectProp) {element.style[selectProp] = 'none';}else if (typeof element.unselectable === 'string') {element.unselectable = 'on';}return element;}/*** Makes element selectable* @memberOf fabric.util* @param {HTMLElement} element Element to make selectable* @return {HTMLElement} Element that was passed in*/function makeElementSelectable(element) {if (typeof element.onselectstart !== 'undefined') {element.onselectstart = null;}if (selectProp) {element.style[selectProp] = '';}else if (typeof element.unselectable === 'string') {element.unselectable = '';}return element;}fabric.util.makeElementUnselectable = makeElementUnselectable;fabric.util.makeElementSelectable = makeElementSelectable;})();(function() {/*** Inserts a script element with a given url into a document; invokes callback, when that script is finished loading* @memberOf fabric.util* @param {String} url URL of a script to load* @param {Function} callback Callback to execute when script is finished loading*/function getScript(url, callback) {var headEl = fabric.document.getElementsByTagName('head')[0],scriptEl = fabric.document.createElement('script'),loading = true;/** @ignore */scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {if (loading) {if (typeof this.readyState === 'string' &&this.readyState !== 'loaded' &&this.readyState !== 'complete') {return;}loading = false;callback(e || fabric.window.event);scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;}};scriptEl.src = url;headEl.appendChild(scriptEl);// causes issue in Opera// headEl.removeChild(scriptEl);}fabric.util.getScript = getScript;})();fabric.util.getById = getById;fabric.util.toArray = toArray;fabric.util.makeElement = makeElement;fabric.util.addClass = addClass;fabric.util.wrapElement = wrapElement;fabric.util.getScrollLeftTop = getScrollLeftTop;fabric.util.getElementOffset = getElementOffset;fabric.util.getElementStyle = getElementStyle;})();(function() {function addParamToUrl(url, param) {return url + (/\?/.test(url) ? '&' : '?') + param;}var makeXHR = (function() {var factories = [function() { return new ActiveXObject('Microsoft.XMLHTTP'); },function() { return new ActiveXObject('Msxml2.XMLHTTP'); },function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); },function() { return new XMLHttpRequest(); }];for (var i = factories.length; i--; ) {try {var req = factories[i]();if (req) {return factories[i];}}catch (err) { }}})();function emptyFn() { }/*** Cross-browser abstraction for sending XMLHttpRequest* @memberOf fabric.util* @param {String} url URL to send XMLHttpRequest to* @param {Object} [options] Options object* @param {String} [options.method="GET"]* @param {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] Values to log as a warning*/fabric.warn = function() { };if (typeof console !== 'undefined') {['log', 'warn'].forEach(function(methodName) {if (typeof console[methodName] !== 'undefined' &&typeof console[methodName].apply === 'function') {fabric[methodName] = function() {return console[methodName].apply(console, arguments);};}});}(function() {/*** Changes value from one to another within certain period of time, invoking callbacks as value is being changed.* @memberOf fabric.util* @param {Object} [options] Animation options* @param {Function} [options.onChange] Callback; invoked on every value change* @param {Function} [options.onComplete] Callback; invoked when value change is completed* @param {Number} [options.startValue=0] Starting value* @param {Number} [options.endValue=100] Ending value* @param {Number} [options.byValue=100] Value to modify the property by* @param {Function} [options.easing] Easing function* @param {Number} [options.duration=500] Duration of change (in ms)*/function animate(options) {requestAnimFrame(function(timestamp) {options || (options = { });var start = timestamp || +new Date(),duration = options.duration || 500,finish = start + duration, time,onChange = options.onChange || function() { },abort = options.abort || function() { return false; },easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;},startValue = 'startValue' in options ? options.startValue : 0,endValue = 'endValue' in options ? options.endValue : 100,byValue = options.byValue || endValue - startValue;options.onStart && options.onStart();(function tick(ticktime) {time = ticktime || +new Date();var currentTime = time > finish ? duration : (time - start);if (abort()) {options.onComplete && options.onComplete();return;}onChange(easing(currentTime, startValue, byValue, duration));if (time > finish) {options.onComplete && options.onComplete();return;}requestAnimFrame(tick);})(start);});}var _requestAnimFrame = fabric.window.requestAnimationFrame ||fabric.window.webkitRequestAnimationFrame ||fabric.window.mozRequestAnimationFrame ||fabric.window.oRequestAnimationFrame ||fabric.window.msRequestAnimationFrame ||function(callback) {fabric.window.setTimeout(callback, 1000 / 60);};/*** requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/* In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method* @memberOf fabric.util* @param {Function} callback Callback to invoke* @param {DOMElement} element optional Element to associate with animation*/function requestAnimFrame() {return _requestAnimFrame.apply(fabric.window, arguments);}fabric.util.animate = animate;fabric.util.requestAnimFrame = requestAnimFrame;})();(function() {function normalize(a, c, p, s) {if (a < Math.abs(c)) {a = c;s = p / 4;}else {s = p / (2 * Math.PI) * Math.asin(c / a);}return { a: a, c: c, p: p, s: s };}function elastic(opts, t, d) {return opts.a *Math.pow(2, 10 * (t -= 1)) *Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );}/*** Cubic easing out* @memberOf fabric.util.ease*/function easeOutCubic(t, b, c, d) {return c * ((t = t / d - 1) * t * t + 1) + b;}/*** Cubic easing in and out* @memberOf fabric.util.ease*/function easeInOutCubic(t, b, c, d) {t /= d/2;if (t < 1) {return c / 2 * t * t * t + b;}return c / 2 * ((t -= 2) * t * t + 2) + b;}/*** Quartic easing in* @memberOf fabric.util.ease*/function easeInQuart(t, b, c, d) {return c * (t /= d) * t * t * t + b;}/*** Quartic easing out* @memberOf fabric.util.ease*/function easeOutQuart(t, b, c, d) {return -c * ((t = t / d - 1) * t * t * t - 1) + b;}/*** Quartic easing in and out* @memberOf fabric.util.ease*/function easeInOutQuart(t, b, c, d) {t /= d / 2;if (t < 1) {return c / 2 * t * t * t * t + b;}return -c / 2 * ((t -= 2) * t * t * t - 2) + b;}/*** Quintic easing in* @memberOf fabric.util.ease*/function easeInQuint(t, b, c, d) {return c * (t /= d) * t * t * t * t + b;}/*** Quintic easing out* @memberOf fabric.util.ease*/function easeOutQuint(t, b, c, d) {return c * ((t = t / d - 1) * t * t * t * t + 1) + b;}/*** Quintic easing in and out* @memberOf fabric.util.ease*/function easeInOutQuint(t, b, c, d) {t /= d / 2;if (t < 1) {return c / 2 * t * t * t * t * t + b;}return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;}/*** Sinusoidal easing in* @memberOf fabric.util.ease*/function easeInSine(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}/*** Sinusoidal easing out* @memberOf fabric.util.ease*/function easeOutSine(t, b, c, d) {return c * Math.sin(t / d * (Math.PI / 2)) + b;}/*** Sinusoidal easing in and out* @memberOf fabric.util.ease*/function easeInOutSine(t, b, c, d) {return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;}/*** Exponential easing in* @memberOf fabric.util.ease*/function easeInExpo(t, b, c, d) {return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;}/*** Exponential easing out* @memberOf fabric.util.ease*/function easeOutExpo(t, b, c, d) {return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;}/*** Exponential easing in and out* @memberOf fabric.util.ease*/function easeInOutExpo(t, b, c, d) {if (t === 0) {return b;}if (t === d) {return b + c;}t /= d / 2;if (t < 1) {return c / 2 * Math.pow(2, 10 * (t - 1)) + b;}return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;}/*** Circular easing in* @memberOf fabric.util.ease*/function easeInCirc(t, b, c, d) {return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;}/*** Circular easing out* @memberOf fabric.util.ease*/function easeOutCirc(t, b, c, d) {return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;}/*** Circular easing in and out* @memberOf fabric.util.ease*/function easeInOutCirc(t, b, c, d) {t /= d / 2;if (t < 1) {return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;}return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;}/*** Elastic easing in* @memberOf fabric.util.ease*/function easeInElastic(t, b, c, d) {var s = 1.70158, p = 0, a = c;if (t === 0) {return b;}t /= d;if (t === 1) {return b + c;}if (!p) {p = d * 0.3;}var opts = normalize(a, c, p, s);return -elastic(opts, t, d) + b;}/*** Elastic easing out* @memberOf fabric.util.ease*/function easeOutElastic(t, b, c, d) {var s = 1.70158, p = 0, a = c;if (t === 0) {return b;}t /= d;if (t === 1) {return b + c;}if (!p) {p = d * 0.3;}var opts = normalize(a, c, p, s);return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b;}/*** Elastic easing in and out* @memberOf fabric.util.ease*/function easeInOutElastic(t, b, c, d) {var s = 1.70158, p = 0, a = c;if (t === 0) {return b;}t /= d / 2;if (t === 2) {return b + c;}if (!p) {p = d * (0.3 * 1.5);}var opts = normalize(a, c, p, s);if (t < 1) {return -0.5 * elastic(opts, t, d) + b;}return opts.a * Math.pow(2, -10 * (t -= 1)) *Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b;}/*** Backwards easing in* @memberOf fabric.util.ease*/function easeInBack(t, b, c, d, s) {if (s === undefined) {s = 1.70158;}return c * (t /= d) * t * ((s + 1) * t - s) + b;}/*** Backwards easing out* @memberOf fabric.util.ease*/function easeOutBack(t, b, c, d, s) {if (s === undefined) {s = 1.70158;}return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;}/*** Backwards easing in and out* @memberOf fabric.util.ease*/function easeInOutBack(t, b, c, d, s) {if (s === undefined) {s = 1.70158;}t /= d / 2;if (t < 1) {return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;}return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;}/*** Bouncing easing in* @memberOf fabric.util.ease*/function easeInBounce(t, b, c, d) {return c - easeOutBounce (d - t, 0, c, d) + b;}/*** Bouncing easing out* @memberOf fabric.util.ease*/function easeOutBounce(t, b, c, d) {if ((t /= d) < (1 / 2.75)) {return c * (7.5625 * t * t) + b;}else if (t < (2/2.75)) {return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;}else if (t < (2.5/2.75)) {return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;}else {return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;}}/*** Bouncing easing in and out* @memberOf fabric.util.ease*/function easeInOutBounce(t, b, c, d) {if (t < d / 2) {return easeInBounce (t * 2, 0, c, d) * 0.5 + b;}return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;}/*** Easing functions* See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a>* @namespace fabric.util.ease*/fabric.util.ease = {/*** Quadratic easing in* @memberOf fabric.util.ease*/easeInQuad: function(t, b, c, d) {return c * (t /= d) * t + b;},/*** Quadratic easing out* @memberOf fabric.util.ease*/easeOutQuad: function(t, b, c, d) {return -c * (t /= d) * (t - 2) + b;},/*** Quadratic easing in and out* @memberOf fabric.util.ease*/easeInOutQuad: function(t, b, c, d) {t /= (d / 2);if (t < 1) {return c / 2 * t * t + b;}return -c / 2 * ((--t) * (t - 2) - 1) + b;},/*** Cubic easing in* @memberOf fabric.util.ease*/easeInCubic: function(t, b, c, d) {return c * (t /= d) * t * t + b;},easeOutCubic: easeOutCubic,easeInOutCubic: easeInOutCubic,easeInQuart: easeInQuart,easeOutQuart: easeOutQuart,easeInOutQuart: easeInOutQuart,easeInQuint: easeInQuint,easeOutQuint: easeOutQuint,easeInOutQuint: easeInOutQuint,easeInSine: easeInSine,easeOutSine: easeOutSine,easeInOutSine: easeInOutSine,easeInExpo: easeInExpo,easeOutExpo: easeOutExpo,easeInOutExpo: easeInOutExpo,easeInCirc: easeInCirc,easeOutCirc: easeOutCirc,easeInOutCirc: easeInOutCirc,easeInElastic: easeInElastic,easeOutElastic: easeOutElastic,easeInOutElastic: easeInOutElastic,easeInBack: easeInBack,easeOutBack: easeOutBack,easeInOutBack: easeInOutBack,easeInBounce: easeInBounce,easeOutBounce: easeOutBounce,easeInOutBounce: easeInOutBounce};}());(function(global) {'use strict';/*** @name fabric* @namespace*/var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend,capitalize = fabric.util.string.capitalize,clone = fabric.util.object.clone,toFixed = fabric.util.toFixed,parseUnit = fabric.util.parseUnit,multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,attributesMap = {cx: 'left',x: 'left',r: 'radius',cy: 'top',y: 'top',display: 'visible',visibility: 'visible',transform: 'transformMatrix','fill-opacity': 'fillOpacity','fill-rule': 'fillRule','font-family': 'fontFamily','font-size': 'fontSize','font-style': 'fontStyle','font-weight': 'fontWeight','stroke-dasharray': 'strokeDashArray','stroke-linecap': 'strokeLineCap','stroke-linejoin': 'strokeLineJoin','stroke-miterlimit': 'strokeMiterLimit','stroke-opacity': 'strokeOpacity','stroke-width': 'strokeWidth','text-decoration': 'textDecoration','text-anchor': 'originX'},colorAttributes = {stroke: 'strokeOpacity',fill: 'fillOpacity'};fabric.cssRules = { };fabric.gradientDefs = { };function normalizeAttr(attr) {// transform attribute namesif (attr in attributesMap) {return attributesMap[attr];}return attr;}function normalizeValue(attr, value, parentAttributes, fontSize) {var isArray = Object.prototype.toString.call(value) === '[object Array]',parsed;if ((attr === 'fill' || attr === 'stroke') && value === 'none') {value = '';}else if (attr === 'strokeDashArray') {value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {return parseFloat(n);});}else if (attr === 'transformMatrix') {if (parentAttributes && parentAttributes.transformMatrix) {value = multiplyTransformMatrices(parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));}else {value = fabric.parseTransformAttribute(value);}}else if (attr === 'visible') {value = (value === 'none' || value === 'hidden') ? false : true;// display=none on parent element always takes precedence over child elementif (parentAttributes && parentAttributes.visible === false) {value = false;}}else if (attr === 'originX' /* text-anchor */) {value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';}else {parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);}return (!isArray && isNaN(parsed) ? value : parsed);}/*** @private* @param {Object} attributes Array of attributes to parse*/function _setStrokeFillOpacity(attributes) {for (var attr in colorAttributes) {if (!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();}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] = Math.tan(fabric.util.degreesToRadians(args[0]));}function skewYMatrix(matrix, args) {matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0]));}function translateMatrix(matrix, args) {matrix[4] = args[0];if (args.length === 2) {matrix[5] = args[1];}}// identity matrixvar iMatrix = [1, // a0, // b0, // c1, // d0, // e0 // f],// == begin transform regexpnumber = fabric.reNum,commaWsp = '(?:\\s+,?\\s*|,\\s*)',skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +commaWsp + '(' + number + ')' +commaWsp + '(' + number + '))?\\s*\\))',scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +commaWsp + '(' + number + '))?\\s*\\))',translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +commaWsp + '(' + number + '))?\\s*\\))',matrix = '(?:(matrix)\\s*\\(\\s*' +'(' + number + ')' + commaWsp +'(' + number + ')' + commaWsp +'(' + number + ')' + commaWsp +'(' + number + ')' + commaWsp +'(' + number + ')' + commaWsp +'(' + number + ')' +'\\s*\\))',transform = '(?:' +matrix + '|' +translate + '|' +scale + '|' +rotate + '|' +skewX + '|' +skewY +')',transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')',transformList = '^\\s*(?:' + transforms + '?)\\s*$',// http://www.w3.org/TR/SVG/coords.html#TransformAttributereTransformList = new RegExp(transformList),// == end transform regexpreTransform = new RegExp(transform, 'g');return function(attributeValue) {// start with identity matrixvar matrix = iMatrix.concat(),matrices = [ ];// return if no argument was given or// an argument does not match transform attribute regexpif (!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':args[0] = fabric.util.degreesToRadians(args[0]);rotateMatrix(matrix, args);break;case 'scale':scaleMatrix(matrix, args);break;case 'skewX':skewXMatrix(matrix, args);break;case 'skewY':skewYMatrix(matrix, args);break;case 'matrix':matrix = args;break;}// snapshot current matrix into matrices arraymatrices.push(matrix.concat());// resetmatrix = iMatrix.concat();});var combinedMatrix = matrices[0];while (matrices.length > 1) {matrices.shift();combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);}return combinedMatrix;};})();/*** @private*/function parseStyleString(style, oStyle) {var attr, value;style.replace(/;$/, '').split(';').forEach(function (chunk) {var pair = chunk.split(':');attr = normalizeAttr(pair[0].trim().toLowerCase());value = normalizeValue(attr, pair[1].trim());oStyle[attr] = value;});}/*** @private*/function parseStyleObject(style, oStyle) {var attr, value;for (var prop in style) {if (typeof style[prop] === 'undefined') {continue;}attr = normalizeAttr(prop.toLowerCase());value = normalizeValue(attr, style[prop]);oStyle[attr] = value;}}/*** @private*/function getGlobalStylesForElement(element, svgUid) {var styles = { };for (var rule in fabric.cssRules[svgUid]) {if (elementMatchesRule(element, rule.split(' '))) {for (var property in fabric.cssRules[svgUid][rule]) {styles[property] = fabric.cssRules[svgUid][rule][property];}}}return styles;}/*** @private*/function elementMatchesRule(element, selectors) {var firstMatching, parentMatching = true;//start from rightmost selector.firstMatching = selectorMatches(element, selectors.pop());if (firstMatching && selectors.length) {parentMatching = doesSomeParentMatch(element, selectors);}return firstMatching && parentMatching && (selectors.length === 0);}function doesSomeParentMatch(element, selectors) {var selector, parentMatching = true;while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {if (parentMatching) {selector = selectors.pop();}element = element.parentNode;parentMatching = selectorMatches(element, selector);}return selectors.length === 0;}/*** @private*/function selectorMatches(element, selector) {var nodeName = element.nodeName,classNames = element.getAttribute('class'),id = element.getAttribute('id'), matcher;// i check if a selector matches slicing away part from it.// if i get empty string i should matchmatcher = new RegExp('^' + nodeName, 'i');selector = selector.replace(matcher, '');if (id && selector.length) {matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');selector = selector.replace(matcher, '');}if (classNames && selector.length) {classNames = classNames.split(' ');for (var i = classNames.length; i--;) {matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');selector = selector.replace(matcher, '');}}return selector.length === 0;}/*** @private*/function parseUseDirectives(doc) {var nodelist = doc.getElementsByTagName('use');while (nodelist.length) {var el = nodelist[0],xlink = el.getAttribute('xlink:href').substr(1),x = el.getAttribute('x') || 0,y = el.getAttribute('y') || 0,el2 = doc.getElementById(xlink).cloneNode(true),currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',parentNode;for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {var attr = attrs.item(j);if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {continue;}if (attr.nodeName === 'transform') {currentTrans = attr.nodeValue + ' ' + currentTrans;}else {el2.setAttribute(attr.nodeName, attr.nodeValue);}}el2.setAttribute('transform', currentTrans);el2.setAttribute('instantiated_by_use', '1');el2.removeAttribute('id');parentNode = el.parentNode;parentNode.replaceChild(el2, el);}}// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute// matches, e.g.: +14.56e-12, etc.var reViewBoxAttrValue = new RegExp('^' +'\\s*(' + fabric.reNum + '+)\\s*,?' +'\\s*(' + fabric.reNum + '+)\\s*,?' +'\\s*(' + fabric.reNum + '+)\\s*,?' +'\\s*(' + fabric.reNum + '+)\\s*' +'$');/*** Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements*/function addVBTransform(element, widthAttr, heightAttr) {var viewBoxAttr = element.getAttribute('viewBox'),scaleX = 1,scaleY = 1,minX = 0,minY = 0,viewBoxWidth, viewBoxHeight, matrix, el;if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {minX = -parseFloat(viewBoxAttr[1]),minY = -parseFloat(viewBoxAttr[2]),viewBoxWidth = parseFloat(viewBoxAttr[3]),viewBoxHeight = parseFloat(viewBoxAttr[4]);}else {return;}if (widthAttr && widthAttr !== viewBoxWidth) {scaleX = widthAttr / viewBoxWidth;}if (heightAttr && heightAttr !== viewBoxHeight) {scaleY = heightAttr / viewBoxHeight;}// default is to preserve aspect ratio// preserveAspectRatio attribute to be implementedscaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) {return;}matrix = ' matrix(' + scaleX +' 0' +' 0 ' +scaleY + ' ' +(minX * scaleX) + ' ' +(minY * scaleY) + ') ';if (element.tagName === 'svg') {el = element.ownerDocument.createElement('g');while (element.firstChild != null) {el.appendChild(element.firstChild);}element.appendChild(el);}else {el = element;matrix = el.getAttribute('transform') + matrix;}el.setAttribute('transform', matrix);}/*** 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)$/,reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/;function hasAncestorWithNodeName(element, nodeName) {while (element && (element = element.parentNode)) {if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) {return true;}}return false;}return function(doc, callback, reviver) {if (!doc) {return;}parseUseDirectives(doc);var startTime = new Date(),svgUid = fabric.Object.__uid++,widthAttr, heightAttr, toBeParsed = false;if (doc.getAttribute('width') && doc.getAttribute('width') !== '100%') {widthAttr = parseUnit(doc.getAttribute('width'));}if (doc.getAttribute('height') && doc.getAttribute('height') !== '100%') {heightAttr = parseUnit(doc.getAttribute('height'));}if (!widthAttr || !heightAttr) {var viewBoxAttr = doc.getAttribute('viewBox');if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {widthAttr = parseFloat(viewBoxAttr[3]),heightAttr = parseFloat(viewBoxAttr[4]);}else {toBeParsed = true;}}addVBTransform(doc, widthAttr, heightAttr);var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));if (descendants.length === 0 && fabric.isLikelyNode) {// we're likely in node, where "o3-xml" library fails to gEBTN("*")// https://github.com/ajaxorg/node-o3-xml/issues/21descendants = 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) {reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0);return reAllowedSVGTagNames.test(el.tagName) &&!hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement});if (!elements || (elements && !elements.length)) {callback && callback([], {});return;}var options = {width: widthAttr,height: heightAttr,svgUid: svgUid,toBeParsed: toBeParsed};fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);fabric.cssRules[svgUid] = fabric.getCSSRules(doc);// Precedence of rules: style > class > attributefabric.parseElements(elements, function(instances) {fabric.documentParsingTime = new Date() - startTime;if (callback) {callback(instances, options);}}, clone(options), reviver);};})();/*** Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)* @namespace*/var svgCache = {/*** @param {String} name* @param {Function} callback*/has: function (name, callback) {callback(false);},get: function () {/* NOOP */},set: function () {/* NOOP */}};/*** @private*/function _enlivenCachedObject(cachedObject) {var objects = cachedObject.objects,options = cachedObject.options;objects = objects.map(function (o) {return fabric[capitalize(o.type)].fromObject(o);});return ({ objects: objects, options: options });}/*** @private*/function _createSVGPattern(markup, canvas, property) {if (canvas[property] && canvas[property].toSVG) {markup.push('<pattern x="0" y="0" id="', property, 'Pattern" ','width="', canvas[property].source.width,'" height="', canvas[property].source.height,'" patternUnits="userSpaceOnUse">','<image x="0" y="0" ','width="', canvas[property].source.width,'" height="', canvas[property].source.height,'" xlink:href="', canvas[property].source.src,'"></image></pattern>');}}var reFontDeclaration = new RegExp('(normal|italic)?\\s*(normal|small-caps)?\\s*' +'(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +fabric.reNum +'(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');extend(fabric, {/*** Parses a short font declaration, building adding its properties to a style object* @static* @function* @memberOf fabric* @param {String} value font declaration* @param {Object} oStyle definition*/parseFontDeclaration: function(value, oStyle) {var match = value.match(reFontDeclaration);if (!match) {return;}var fontStyle = match[1],// font variant is not used// fontVariant = match[2],fontWeight = match[3],fontSize = match[4],lineHeight = match[5],fontFamily = match[6];if (fontStyle) {oStyle.fontStyle = fontStyle;}if (fontWeight) {oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);}if (fontSize) {oStyle.fontSize = parseUnit(fontSize);}if (fontFamily) {oStyle.fontFamily = fontFamily;}if (lineHeight) {oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;}},/*** Parses an SVG document, returning all of the gradient declarations found in it* @static* @function* @memberOf fabric* @param {SVGDocument} doc SVG document to parse* @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element*/getGradientDefs: function(doc) {var linearGradientEls = doc.getElementsByTagName('linearGradient'),radialGradientEls = doc.getElementsByTagName('radialGradient'),el, i, j = 0, id, xlink, elList = [ ],gradientDefs = { }, idsToXlinkMap = { };elList.length = linearGradientEls.length + radialGradientEls.length;i = linearGradientEls.length;while (i--) {elList[j++] = linearGradientEls[i];}i = radialGradientEls.length;while (i--) {elList[j++] = radialGradientEls[i];}while (j--) {el = elList[j];xlink = el.getAttribute('xlink:href');id = el.getAttribute('id');if (xlink) {idsToXlinkMap[id] = xlink.substr(1);}gradientDefs[id] = el;}for (id in idsToXlinkMap) {var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);el = gradientDefs[id];while (el2.firstChild) {el.appendChild(el2.firstChild);}}return gradientDefs;},/*** Returns an object of attributes' name/value, given element and an array of attribute names;* Parses parent "g" nodes recursively upwards.* @static* @memberOf fabric* @param {DOMElement} element Element to parse* @param {Array} attributes Array of attributes to parse* @return {Object} object containing parsed attributes' names/values*/parseAttributes: function(element, attributes, svgUid) {if (!element) {return;}var value,parentAttributes = { },fontSize;if (typeof svgUid === 'undefined') {svgUid = element.getAttribute('svgUid');}// if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwardsif (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) {parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);}fontSize = (parentAttributes && parentAttributes.fontSize ) ||element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;var ownAttributes = attributes.reduce(function(memo, attr) {value = element.getAttribute(attr);if (value) {attr = normalizeAttr(attr);value = normalizeValue(attr, value, parentAttributes, fontSize);memo[attr] = value;}return memo;}, { });// add values parsed from style, which take precedence over attributes// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)ownAttributes = extend(ownAttributes,extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));if (ownAttributes.font) {fabric.parseFontDeclaration(ownAttributes.font, ownAttributes);}return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));},/*** Transforms an array of svg elements to corresponding fabric.* instances* @static* @memberOf fabric* @param {Array} elements Array of elements to parse* @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)* @param {Object} [options] Options object* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.*/parseElements: function(elements, callback, options, reviver) {new fabric.ElementsParser(elements, callback, options, reviver).parse();},/*** Parses "style" attribute, retuning an object with values* @static* @memberOf fabric* @param {SVGElement} element Element to parse* @return {Object} Objects with values parsed from style attribute of an element*/parseStyleAttribute: function(element) {var oStyle = { },style = element.getAttribute('style');if (!style) {return oStyle;}if (typeof style === 'string') {parseStyleString(style, oStyle);}else {parseStyleObject(style, oStyle);}return oStyle;},/*** Parses "points" attribute, returning an array of values* @static* @memberOf fabric* @param {String} points points attribute string* @return {Array} array of points*/parsePointsAttribute: function(points) {// points attribute is required and must not be emptyif (!points) {return null;}// replace commas with whitespace and remove bookending whitespacepoints = points.replace(/,/g, ' ').trim();points = points.split(/\s+/);var parsedPoints = [ ], i, len;i = 0;len = points.length;for (; i < len; i+=2) {parsedPoints.push({x: parseFloat(points[i]),y: parseFloat(points[i + 1])});}// odd number of points is an error// if (parsedPoints.length % 2 !== 0) {// return null;// }return parsedPoints;},/*** Returns CSS rules for a given SVG document* @static* @function* @memberOf fabric* @param {SVGDocument} doc SVG document to parse* @return {Object} CSS rules of this document*/getCSSRules: function(doc) {var styles = doc.getElementsByTagName('style'),allRules = { }, rules;// very crude parsing of style contentsfor (var i = 0, len = styles.length; i < len; i++) {var styleContents = styles[i].textContent;// remove commentsstyleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');if (styleContents.trim() === '') {continue;}rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);rules = rules.map(function(rule) { return rule.trim(); });rules.forEach(function(rule) {var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),ruleObj = { }, declaration = match[2].trim(),propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);for (var i = 0, len = propertyValuePairs.length; i < len; i++) {var pair = propertyValuePairs[i].split(/\s*:\s*/),property = normalizeAttr(pair[0]),value = normalizeValue(property, pair[1], pair[0]);ruleObj[property] = value;}rule = match[1];rule.split(',').forEach(function(_rule) {_rule = _rule.replace(/^svg/i, '').trim();if (_rule === '') {return;}allRules[_rule] = fabric.util.object.clone(ruleObj);});});}return allRules;},/*** Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)* @memberof fabric* @param {String} url* @param {Function} callback* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.*/loadSVGFromURL: function(url, callback, reviver) {url = url.replace(/^\n\s*/, '').trim();svgCache.has(url, function (hasUrl) {if (hasUrl) {svgCache.get(url, function (value) {var enlivedRecord = _enlivenCachedObject(value);callback(enlivedRecord.objects, enlivedRecord.options);});}else {new fabric.util.request(url, {method: 'get',onComplete: onComplete});}});function onComplete(r) {var xml = r.responseXML;if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {xml = new ActiveXObject('Microsoft.XMLDOM');xml.async = 'false';//IE chokes on DOCTYPExml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));}if (!xml || !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 DOCTYPEdoc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));}fabric.parseSVGDocument(doc.documentElement, function (results, options) {callback(results, options);}, reviver);},/*** Creates markup containing SVG font faces* @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 += [//jscs:disable validateIndentation'@font-face {','font-family: ', objects[i].fontFamily, '; ','src: url(\'', objects[i].path, '\')','}'//jscs:enable validateIndentation].join('');}if (markup) {markup = [//jscs:disable validateIndentation'<style type="text/css">','<![CDATA[',markup,']]>','</style>'//jscs:enable validateIndentation].join('');}return markup;},/*** Creates markup containing SVG referenced elements like patterns, gradients etc.* @param {fabric.Canvas} canvas instance of fabric.Canvas* @return {String}*/createSVGRefElementsMarkup: function(canvas) {var markup = [ ];_createSVGPattern(markup, canvas, 'backgroundColor');_createSVGPattern(markup, canvas, 'overlayColor');return markup.join('');}});})(typeof exports !== 'undefined' ? exports : this);fabric.ElementsParser = function(elements, callback, options, reviver) {this.elements = elements;this.callback = callback;this.options = options;this.reviver = reviver;this.svgUid = (options && options.svgUid) || 0;};fabric.ElementsParser.prototype.parse = function() {this.instances = new Array(this.elements.length);this.numElements = this.elements.length;this.createObjects();};fabric.ElementsParser.prototype.createObjects = function() {for (var i = 0, len = this.elements.length; i < len; i++) {this.elements[i].setAttribute('svgUid', this.svgUid);(function(_this, i) {setTimeout(function() {_this.createObject(_this.elements[i], i);}, 0);})(this, i);}};fabric.ElementsParser.prototype.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();}};fabric.ElementsParser.prototype._createObject = function(klass, el, index) {if (klass.async) {klass.fromElement(el, this.createCallback(index, el), this.options);}else {var obj = klass.fromElement(el, this.options);this.resolveGradient(obj, 'fill');this.resolveGradient(obj, 'stroke');this.reviver && this.reviver(el, obj);this.instances[index] = obj;this.checkIfDone();}};fabric.ElementsParser.prototype.createCallback = function(index, el) {var _this = this;return function(obj) {_this.resolveGradient(obj, 'fill');_this.resolveGradient(obj, 'stroke');_this.reviver && _this.reviver(el, obj);_this.instances[index] = obj;_this.checkIfDone();};};fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {var instanceFillValue = obj.get(property);if (!(/^url\(/).test(instanceFillValue)) {return;}var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);if (fabric.gradientDefs[this.svgUid][gradientId]) {obj.set(property,fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));}};fabric.ElementsParser.prototype.checkIfDone = function() {if (--this.numElements === 0) {this.instances = this.instances.filter(function(el) {return el != null;});this.callback(this.instances);}};(function(global) {'use strict';/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */var fabric = global.fabric || (global.fabric = { });if (fabric.Point) {fabric.warn('fabric.Point is already defined');return;}fabric.Point = Point;/*** Point class* @class fabric.Point* @memberOf fabric* @constructor* @param {Number} x* @param {Number} y* @return {fabric.Point} thisArg*/function Point(x, y) {this.x = x;this.y = y;}Point.prototype = /** @lends fabric.Point.prototype */ {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* @return {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* @param {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,uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);if (uB !== 0) {var ua = uaT / uB,ub = ubT / uB;if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {result = new Intersection('Intersection');result.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 (uaT === 0 || ubT === 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];}if (color === 'transparent') {this.setSource([255, 255, 255, 0]);return;}source = Color.sourceFromHex(color);if (!source) {source = Color.sourceFromRgb(color);}if (!source) {source = Color.sourceFromHsl(color);}if (source) {this.setSource(source);}},/*** Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>* @private* @param {Number} r Red color value* @param {Number} g Green color value* @param {Number} b Blue color value* @return {Array} Hsl color*/_rgbToHsl: function(r, g, b) {r /= 255, g /= 255, b /= 255;var h, s, l,max = fabric.util.array.max([r, g, b]),min = fabric.util.array.min([r, g, b]);l = (max + min) / 2;if (max === min) {h = s = 0; // achromatic}else {var d = max - min;s = l > 0.5 ? d / (2 - max - min) : d / (max + min);switch (max) {case r:h = (g - b) / d + (g < b ? 6 : 0);break;case g:h = (b - r) / d + 2;break;case b:h = (r - g) / d + 4;break;}h /= 6;}return [Math.round(h * 360),Math.round(s * 100),Math.round(l * 100)];},/*** Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])* @return {Array}*/getSource: function() {return this._source;},/*** Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])* @param {Array} source*/setSource: function(source) {this._source = source;},/*** Returns color represenation in RGB format* @return {String} ex: rgb(0-255,0-255,0-255)*/toRgb: function() {var source = this.getSource();return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';},/*** Returns color represenation in RGBA format* @return {String} ex: rgba(0-255,0-255,0-255,0-1)*/toRgba: function() {var source = this.getSource();return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';},/*** Returns color represenation in HSL format* @return {String} ex: hsl(0-360,0%-100%,0%-100%)*/toHsl: function() {var source = this.getSource(),hsl = this._rgbToHsl(source[0], source[1], source[2]);return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)';},/*** Returns color represenation in HSLA format* @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)*/toHsla: function() {var source = this.getSource(),hsl = this._rgbToHsl(source[0], source[1], source[2]);return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')';},/*** Returns color represenation in HEX format* @return {String} ex: FF5555*/toHex: function() {var source = this.getSource(), r, g, b;r = source[0].toString(16);r = (r.length === 1) ? ('0' + r) : r;g = source[1].toString(16);g = (g.length === 1) ? ('0' + g) : g;b = source[2].toString(16);b = (b.length === 1) ? ('0' + b) : b;return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();},/*** Gets value of alpha channel for this color* @return {Number} 0-1*/getAlpha: function() {return this.getSource()[3];},/*** Sets value of alpha channel for this color* @param {Number} alpha Alpha value 0-1* @return {fabric.Color} thisArg*/setAlpha: function(alpha) {var source = this.getSource();source[3] = alpha;this.setSource(source);return this;},/*** Transforms color to its grayscale representation* @return {fabric.Color} thisArg*/toGrayscale: function() {var source = this.getSource(),average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),currentAlpha = source[3];this.setSource([average, average, average, currentAlpha]);return this;},/*** Transforms color to its black and white representation* @param {Number} threshold* @return {fabric.Color} thisArg*/toBlackWhite: function(threshold) {var source = this.getSource(),average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),currentAlpha = source[3];threshold = threshold || 127;average = (Number(average) < Number(threshold)) ? 0 : 255;this.setSource([average, average, average, currentAlpha]);return this;},/*** Overlays color with another color* @param {String|fabric.Color} otherColor* @return {fabric.Color} thisArg*/overlayWith: function(otherColor) {if (!(otherColor instanceof Color)) {otherColor = new Color(otherColor);}var result = [],alpha = this.getAlpha(),otherAlpha = 0.5,source = this.getSource(),otherSource = otherColor.getSource();for (var i = 0; i < 3; i++) {result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));}result[3] = alpha;this.setSource(result);return this;}};/*** Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5))* @static* @field* @memberOf fabric.Color*/fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;/*** Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 ))* @static* @field* @memberOf fabric.Color*/fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;/*** Regex matching color in HEX format (ex: #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 <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>* @memberOf fabric.Color* @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)* @return {Array} source* @see http://http://www.w3.org/TR/css3-color/#hsl-color*/fabric.Color.sourceFromHsl = function(color) {var match = color.match(Color.reHSLa);if (!match) {return;}var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),r, g, b;if (s === 0) {r = g = b = l;}else {var q = l <= 0.5 ? l * (s + 1) : l + s - l * s,p = l * 2 - q;r = hue2rgb(p, q, h + 1/3);g = hue2rgb(p, q, h);b = hue2rgb(p, q, h - 1/3);}return [Math.round(r * 255),Math.round(g * 255),Math.round(b * 255),match[4] ? parseFloat(match[4]) : 1];};/*** Returns new color object, when given a color in HSLA format* @static* @function* @memberOf fabric.Color* @param {String} color* @return {fabric.Color}*/fabric.Color.fromHsla = Color.fromHsl;/*** Returns new color object, when given a color in HEX format* @static* @memberOf fabric.Color* @param {String} color Color value ex: FF5555* @return {fabric.Color}*/fabric.Color.fromHex = function(color) {return Color.fromSource(Color.sourceFromHex(color));};/*** Returns array 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, colorAlpha, opacity;// convert percents to absolute valuesoffset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;if (style) {var keyValuePairs = style.split(/\s*;\s*/);if (keyValuePairs[keyValuePairs.length - 1] === '') {keyValuePairs.pop();}for (var i = keyValuePairs.length; i--; ) {var split = keyValuePairs[i].split(/\s*:\s*/),key = split[0].trim(),value = split[1].trim();if (key === 'stop-color') {color = value;}else if (key === 'stop-opacity') {opacity = value;}}}if (!color) {color = el.getAttribute('stop-color') || 'rgb(0,0,0)';}if (!opacity) {opacity = el.getAttribute('stop-opacity');}color = new fabric.Color(color);colorAlpha = color.getAlpha();opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);opacity *= colorAlpha;return {offset: offset,color: color.toRgb(),opacity: opacity};}function getLinearCoords(el) {return {x1: el.getAttribute('x1') || 0,y1: el.getAttribute('y1') || 0,x2: el.getAttribute('x2') || '100%',y2: el.getAttribute('y2') || 0};}function getRadialCoords(el) {return {x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',r1: 0,x2: el.getAttribute('cx') || '50%',y2: el.getAttribute('cy') || '50%',r2: el.getAttribute('r') || '50%'};}/* _FROM_SVG_END_ *//*** Gradient class* @class fabric.Gradient* @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients}* @see {@link fabric.Gradient#initialize} for constructor definition*/fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {/*** Horizontal offset for aligning gradients coming from SVG when outside pathgroups* @type Number* @default 0*/offsetX: 0,/*** Vertical offset for aligning gradients coming from SVG when outside pathgroups* @type Number* @default 0*/offsetY: 0,/*** Constructor* @param {Object} [options] Options object with type, coords, gradientUnits and colorStops* @return {fabric.Gradient} thisArg*/initialize: function(options) {options || (options = { });var coords = { };this.id = fabric.Object.__uid++;this.type = options.type || 'linear';coords = {x1: options.coords.x1 || 0,y1: options.coords.y1 || 0,x2: options.coords.x2 || 0,y2: options.coords.y2 || 0};if (this.type === 'radial') {coords.r1 = options.coords.r1 || 0;coords.r2 = options.coords.r2 || 0;}this.coords = coords;this.colorStops = options.colorStops.slice();if (options.gradientTransform) {this.gradientTransform = options.gradientTransform;}this.offsetX = options.offsetX || this.offsetX;this.offsetY = options.offsetY || this.offsetY;},/*** Adds another colorStop* @param {Object} colorStop Object with offset and color* @return {fabric.Gradient} thisArg*/addColorStop: function(colorStop) {for (var position in colorStop) {var color = new fabric.Color(colorStop[position]);this.colorStops.push({offset: position,color: color.toRgb(),opacity: color.getAlpha()});}return this;},/*** Returns object representation of a gradient* @return {Object}*/toObject: function() {return {type: this.type,coords: this.coords,colorStops: this.colorStops,offsetX: this.offsetX,offsetY: this.offsetY};},/* _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) {var coords = fabric.util.object.clone(this.coords),markup, commonAttributes;// colorStops must be sorted ascendingthis.colorStops.sort(function(a, b) {return a.offset - b.offset;});if (!(object.group && object.group.type === 'path-group')) {for (var prop in coords) {if (prop === 'x1' || prop === 'x2' || prop === 'r2') {coords[prop] += this.offsetX - object.width / 2;}else if (prop === 'y1' || prop === 'y2') {coords[prop] += this.offsetY - object.height / 2;}}}commonAttributes = 'id="SVGID_' + this.id +'" gradientUnits="userSpaceOnUse"';if (this.gradientTransform) {commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';}if (this.type === 'linear') {markup = [//jscs:disable validateIndentation'<linearGradient ',commonAttributes,' x1="', coords.x1,'" y1="', coords.y1,'" x2="', coords.x2,'" y2="', coords.y2,'">\n'//jscs:enable validateIndentation];}else if (this.type === 'radial') {markup = [//jscs:disable validateIndentation'<radialGradient ',commonAttributes,' cx="', coords.x2,'" cy="', coords.y2,'" r="', coords.r2,'" fx="', coords.x1,'" fy="', coords.y1,'">\n'//jscs:enable validateIndentation];}for (var i = 0; i < this.colorStops.length; i++) {markup.push(//jscs:disable validateIndentation'<stop ','offset="', (this.colorStops[i].offset * 100) + '%','" style="stop-color:', this.colorStops[i].color,(this.colorStops[i].opacity != null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'),'"/>\n'//jscs:enable validateIndentation);}markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n'));return markup.join('');},/* _TO_SVG_END_ *//*** Returns an instance of CanvasGradient* @param {CanvasRenderingContext2D} ctx Context to render on* @return {CanvasGradient}*/toLive: function(ctx, object) {var gradient, prop, coords = fabric.util.object.clone(this.coords);if (!this.type) {return;}if (object.group && object.group.type === 'path-group') {for (prop in coords) {if (prop === 'x1' || prop === 'x2') {coords[prop] += -this.offsetX + object.width / 2;}else if (prop === 'y1' || prop === 'y2') {coords[prop] += -this.offsetY + object.height / 2;}}}if (object.type === 'text' || object.type === 'i-text') {for (prop in coords) {if (prop === 'x1' || prop === 'x2') {coords[prop] -= object.width / 2;}else if (prop === 'y1' || prop === 'y2') {coords[prop] -= object.height / 2;}}}if (this.type === 'linear') {gradient = ctx.createLinearGradient(coords.x1, coords.y1, coords.x2, coords.y2);}else if (this.type === 'radial') {gradient = ctx.createRadialGradient(coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2);}for (var i = 0, len = this.colorStops.length; i < len; i++) {var color = this.colorStops[i].color,opacity = this.colorStops[i].opacity,offset = this.colorStops[i].offset;if (typeof opacity !== 'undefined') {color = new fabric.Color(color).setAlpha(opacity).toRgba();}gradient.addColorStop(parseFloat(offset), color);}return gradient;}});fabric.util.object.extend(fabric.Gradient, {/* _FROM_SVG_START_ *//*** Returns {@link fabric.Gradient} instance from an SVG element* @static* @memberof fabric.Gradient* @param {SVGGradientElement} el SVG gradient element* @param {fabric.Object} instance* @return {fabric.Gradient} Gradient instance* @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement* @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement*/fromElement: function(el, instance) {/*** @example:** <linearGradient id="linearGrad1">* <stop offset="0%" stop-color="white"/>* <stop offset="100%" stop-color="black"/>* </linearGradient>** OR** <linearGradient id="linearGrad2">* <stop offset="0" style="stop-color:rgb(255,255,255)"/>* <stop offset="1" style="stop-color:rgb(0,0,0)"/>* </linearGradient>** OR** <radialGradient id="radialGrad1">* <stop offset="0%" stop-color="white" stop-opacity="1" />* <stop offset="50%" stop-color="black" stop-opacity="0.5" />* <stop offset="100%" stop-color="white" stop-opacity="1" />* </radialGradient>** OR** <radialGradient id="radialGrad2">* <stop offset="0" stop-color="rgb(255,255,255)" />* <stop offset="0.5" stop-color="rgb(0,0,0)" />* <stop offset="1" stop-color="rgb(255,255,255)" />* </radialGradient>**/var colorStopEls = el.getElementsByTagName('stop'),type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'),gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',gradientTransform = el.getAttribute('gradientTransform'),colorStops = [],coords = { }, ellipseMatrix;if (type === 'linear') {coords = getLinearCoords(el);}else if (type === 'radial') {coords = getRadialCoords(el);}for (var i = colorStopEls.length; i--; ) {colorStops.push(getColorStop(colorStopEls[i]));}ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);var gradient = new fabric.Gradient({type: type,coords: coords,colorStops: colorStops,offsetX: -instance.left,offsetY: -instance.top});if (gradientTransform || ellipseMatrix !== '') {gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);}return gradient;},/* _FROM_SVG_END_ *//*** Returns {@link fabric.Gradient} instance from its object representation* @static* @memberof fabric.Gradient* @param {Object} obj* @param {Object} [options] Options object*/forObject: function(obj, options) {options || (options = { });_convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');return new fabric.Gradient(options);}});/*** @private*/function _convertPercentUnitsToValues(object, options, gradientUnits) {var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';for (var prop in options) {propValue = parseFloat(options[prop], 10);if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {multFactor = 0.01;}else {multFactor = 1;}if (prop === 'x1' || prop === 'x2' || prop === 'r2') {multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;}else if (prop === 'y1' || prop === 'y2') {multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;}options[prop] = propValue * multFactor + addFactor;}if (object.type === 'ellipse' &&options.r2 !== null &&gradientUnits === 'objectBoundingBox' &&object.rx !== object.ry) {var scaleFactor = object.ry/object.rx;ellipseMatrix = ' scale(1, ' + scaleFactor + ')';if (options.y1) {options.y1 /= scaleFactor;}if (options.y2) {options.y2 /= scaleFactor;}}return ellipseMatrix;}})();/*** Pattern class* @class fabric.Pattern* @see {@link http://fabricjs.com/patterns/|Pattern demo}* @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo}* @see {@link fabric.Pattern#initialize} for constructor definition*/fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {/*** Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)* @type String* @default*/repeat: 'repeat',/*** Pattern horizontal offset from object's left/top corner* @type Number* @default*/offsetX: 0,/*** Pattern vertical offset from object's left/top corner* @type Number* @default*/offsetY: 0,/*** Constructor* @param {Object} [options] Options object* @return {fabric.Pattern} thisArg*/initialize: function(options) {options || (options = { });this.id = fabric.Object.__uid++;if (options.source) {if (typeof options.source === 'string') {// function stringif (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') {this.source = new Function(fabric.util.getFunctionBody(options.source));}else {// img src stringvar _this = this;this.source = fabric.util.createImage();fabric.util.loadImage(options.source, function(img) {_this.source = img;});}}else {// img elementthis.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;// callbackif (typeof this.source === 'function') {source = String(this.source);}// <img> elementelse 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,patternWidth = patternSource.width / object.getWidth(),patternHeight = patternSource.height / object.getHeight(),patternOffsetX = this.offsetX / object.getWidth(),patternOffsetY = this.offsetY / object.getHeight(),patternImgSrc = '';if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') {patternHeight = 1;}if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') {patternWidth = 1;}if (patternSource.src) {patternImgSrc = patternSource.src;}else if (patternSource.toDataURL) {patternImgSrc = patternSource.toDataURL();}return '<pattern id="SVGID_' + this.id +'" x="' + patternOffsetX +'" y="' + patternOffsetY +'" width="' + patternWidth +'" height="' + patternHeight + '">\n' +'<image x="0" y="0"' +' width="' + patternSource.width +'" height="' + patternSource.height +'" xlink:href="' + patternImgSrc +'"></image>\n' +'</pattern>\n';},/* _TO_SVG_END_ *//*** Returns an instance of CanvasPattern* @param {CanvasRenderingContext2D} ctx Context to create pattern* @return {CanvasPattern}*/toLive: function(ctx) {var source = typeof this.source === 'function'? this.source(): this.source;// if the image failed to load, return, and allow rest to continue loadingif (!source) {return '';}// if an imageif (typeof source.src !== 'undefined') {if (!source.complete) {return '';}if (source.naturalWidth === 0 || source.naturalHeight === 0) {return '';}}return ctx.createPattern(source, this.repeat);}});(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),toFixed = fabric.util.toFixed;if (fabric.Shadow) {fabric.warn('fabric.Shadow is already defined.');return;}/*** Shadow class* @class fabric.Shadow* @see {@link http://fabricjs.com/shadows/|Shadow demo}* @see {@link fabric.Shadow#initialize} for constructor definition*/fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ {/*** Shadow color* @type String* @default*/color: 'rgb(0,0,0)',/*** Shadow blur* @type Number*/blur: 0,/*** Shadow horizontal offset* @type Number* @default*/offsetX: 0,/*** Shadow vertical offset* @type Number* @default*/offsetY: 0,/*** Whether the shadow should affect stroke operations* @type Boolean* @default*/affectStroke: false,/*** Indicates whether toObject should include default values* @type Boolean* @default*/includeDefaultValues: true,/*** Constructor* @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")* @return {fabric.Shadow} thisArg*/initialize: function(options) {if (typeof options === 'string') {options = this._parseShadow(options);}for (var prop in options) {this[prop] = options[prop];}this.id = fabric.Object.__uid++;},/*** @private* @param {String} shadow Shadow value to parse* @return {Object} Shadow object with color, offsetX, offsetY and blur*/_parseShadow: function(shadow) {var shadowStr = shadow.trim(),offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ],color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)';return {color: color.trim(),offsetX: parseInt(offsetsAndBlur[1], 10) || 0,offsetY: parseInt(offsetsAndBlur[2], 10) || 0,blur: parseInt(offsetsAndBlur[3], 10) || 0};},/*** Returns a string representation of an instance* @see http://www.w3.org/TR/css-text-decor-3/#text-shadow* @return {String} Returns CSS3 text-shadow declaration*/toString: function() {return [this.offsetX, this.offsetY, this.blur, this.color].join('px ');},/* _TO_SVG_START_ *//*** Returns SVG representation of a shadow* @param {fabric.Object} object* @return {String} SVG representation of a shadow*/toSVG: function(object) {var mode = 'SourceAlpha', fBoxX = 40, fBoxY = 40;if (object && (object.fill === this.color || object.stroke === this.color)) {mode = 'SourceGraphic';}if (object.width && object.height) {//http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion// we add some extra space to filter box to contain the blur ( 20 )fBoxX = toFixed(Math.abs(this.offsetX / object.getWidth()), 2) * 100 + 20;fBoxY = toFixed(Math.abs(this.offsetY / object.getHeight()), 2) * 100 + 20;}return ('<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' +'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' +'\t<feGaussianBlur in="' + mode + '" stdDeviation="' +toFixed(this.blur ? this.blur / 2 : 0, 3) +'" result="blurOut"></feGaussianBlur>\n' +'\t<feColorMatrix result="matrixOut" in="blurOut" type="matrix" ' +'values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.30 0" ></feColorMatrix >\n' +'\t<feOffset dx="' + this.offsetX + '" dy="' + this.offsetY + '"></feOffset>\n' +'\t<feMerge>\n' +'\t\t<feMergeNode></feMergeNode>\n' +'\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' +'\t</feMerge>\n' +'</filter>\n');},/* _TO_SVG_END_ *//*** Returns object representation of a shadow* @return {Object} Object representation of a shadow instance*/toObject: function() {if (this.includeDefaultValues) {return {color: this.color,blur: this.blur,offsetX: this.offsetX,offsetY: this.offsetY};}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 resolutionvar 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}.* <b>Backwards incompatibility note:</b> The "backgroundImageOpacity"* and "backgroundImageStretch" properties are deprecated since 1.3.9.* Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.* @type fabric.Image* @default*/backgroundImage: null,/*** Overlay color of canvas instance.* Should be set via {@link fabric.StaticCanvas#setOverlayColor}* @since 1.3.9* @type {(String|fabric.Pattern)}* @default*/overlayColor: '',/*** Overlay image of canvas instance.* Should be set via {@link fabric.StaticCanvas#setOverlayImage}.* <b>Backwards incompatibility note:</b> The "overlayImageLeft"* and "overlayImageTop" properties are deprecated since 1.3.9.* Use {@link fabric.Image#left} and {@link fabric.Image#top}.* @type fabric.Image* @default*/overlayImage: null,/*** Indicates whether toObject/toDatalessObject should include default values* @type Boolean* @default*/includeDefaultValues: true,/*** Indicates whether objects' state should be saved* @type Boolean* @default*/stateful: true,/*** Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.* Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once* (followed by a manual rendering after addition/deletion)* @type Boolean* @default*/renderOnAddRemove: true,/*** Function that determines clipping of entire canvas area* Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ}* @type Function* @default*/clipTo: null,/*** Indicates whether object controls (borders/controls) are rendered above overlay image* @type Boolean* @default*/controlsAboveOverlay: false,/*** Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas* @type Boolean* @default*/allowTouchScrolling: false,/*** Indicates whether this canvas will use image smoothing, this is on by default in browsers* @type Boolean* @default*/imageSmoothingEnabled: true,/*** Indicates whether objects should remain in current stack position when selected. When false objects are brought to top and rendered as part of the selection group* @type Boolean* @default*/preserveObjectStacking: false,/*** The transformation (in the format of Canvas transform) which focuses the viewport* @type Array* @default*/viewportTransform: [1, 0, 0, 1, 0, 0],/*** Callback; invoked right before object is 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);this._setImageSmoothing();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 <caption>Normal overlayImage with left/top = 0</caption>* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {* // Needed to position overlayImage at 0/0* originX: 'left',* originY: 'top'* });* @example <caption>overlayImage with different properties</caption>* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {* opacity: 0.5,* angle: 45,* left: 400,* top: 400,* originX: 'left',* originY: 'top'* });* @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption>* fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {* img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});* canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));* });* @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption>* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {* width: canvas.width,* height: canvas.height,* // Needed to position overlayImage at 0/0* originX: 'left',* originY: 'top'* });* @example <caption>overlayImage loaded from cross-origin</caption>* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {* opacity: 0.5,* angle: 45,* left: 400,* top: 400,* originX: 'left',* originY: 'top',* crossOrigin: 'anonymous'* });*/setOverlayImage: function (image, callback, options) {return this.__setBgOverlayImage('overlayImage', image, callback, options);},/*** Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas* @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to* @param {Function} callback Callback to invoke when image is loaded and set as background* @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.* @return {fabric.Canvas} thisArg* @chainable* @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}* @example <caption>Normal backgroundImage with left/top = 0</caption>* canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {* // Needed to position backgroundImage at 0/0* originX: 'left',* originY: 'top'* });* @example <caption>backgroundImage with different properties</caption>* canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {* opacity: 0.5,* angle: 45,* left: 400,* top: 400,* originX: 'left',* originY: 'top'* });* @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption>* fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {* img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});* canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));* });* @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption>* canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {* width: canvas.width,* height: canvas.height,* // Needed to position backgroundImage at 0/0* originX: 'left',* originY: 'top'* });* @example <caption>backgroundImage loaded from cross-origin</caption>* canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {* opacity: 0.5,* angle: 45,* left: 400,* top: 400,* originX: 'left',* originY: 'top',* crossOrigin: 'anonymous'* });*/setBackgroundImage: function (image, callback, options) {return this.__setBgOverlayImage('backgroundImage', image, callback, options);},/*** Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas* @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to* @param {Function} callback Callback to invoke when background color is set* @return {fabric.Canvas} thisArg* @chainable* @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}* @example <caption>Normal overlayColor - color value</caption>* canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));* @example <caption>fabric.Pattern used as overlayColor</caption>* canvas.setOverlayColor({* source: 'http://fabricjs.com/assets/escheresque_ste.png'* }, canvas.renderAll.bind(canvas));* @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption>* canvas.setOverlayColor({* source: 'http://fabricjs.com/assets/escheresque_ste.png',* repeat: 'repeat',* offsetX: 200,* offsetY: 100* }, canvas.renderAll.bind(canvas));*/setOverlayColor: function(overlayColor, callback) {return this.__setBgOverlayColor('overlayColor', overlayColor, callback);},/*** Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas* @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to* @param {Function} callback Callback to invoke when background color is set* @return {fabric.Canvas} thisArg* @chainable* @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}* @example <caption>Normal backgroundColor - color value</caption>* canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));* @example <caption>fabric.Pattern used as backgroundColor</caption>* canvas.setBackgroundColor({* source: 'http://fabricjs.com/assets/escheresque_ste.png'* }, canvas.renderAll.bind(canvas));* @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption>* canvas.setBackgroundColor({* source: 'http://fabricjs.com/assets/escheresque_ste.png',* repeat: 'repeat',* offsetX: 200,* offsetY: 100* }, canvas.renderAll.bind(canvas));*/setBackgroundColor: function(backgroundColor, callback) {return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);},/*** @private* @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard}*/_setImageSmoothing: function() {var ctx = this.getContext();ctx.imageSmoothingEnabled = this.imageSmoothingEnabled;ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled;ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled;ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled;ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled;},/*** @private* @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}* or {@link fabric.StaticCanvas#overlayImage|overlayImage})* @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to* @param {Function} callback Callback to invoke when image is loaded and set as background or overlay* @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.*/__setBgOverlayImage: function(property, image, callback, options) {if (typeof image === 'string') {fabric.util.loadImage(image, function(img) {this[property] = new fabric.Image(img, options);callback && callback();}, this, options && options.crossOrigin);}else {options && image.setOptions(options);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|null)} color Object with pattern information, color value or null* @param {Function} [callback] Callback is invoked when color is set*/__setBgOverlayColor: function(property, color, callback) {if (color && color.source) {var _this = this;fabric.util.loadImage(color.source, function(img) {_this[property] = new fabric.Pattern({source: img,repeat: color.repeat,offsetX: color.offsetX,offsetY: color.offsetY});callback && callback();});}else {this[property] = color;callback && callback();}return this;},/*** @private*/_createCanvasElement: function() {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 = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;if (!this.lowerCanvasEl.style) {return;}this.lowerCanvasEl.width = this.width;this.lowerCanvasEl.height = this.height;this.lowerCanvasEl.style.width = this.width + 'px';this.lowerCanvasEl.style.height = this.height + 'px';this.viewportTransform = this.viewportTransform.slice();},/*** Creates a bottom canvas* @private* @param {HTMLElement} [canvasEl]*/_createLowerCanvas: function (canvasEl) {this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();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|String} value Value to set width to* @param {Object} [options] Options object* @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions* @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions* @return {fabric.Canvas} instance* @chainable true*/setWidth: function (value, options) {return this.setDimensions({ width: value }, options);},/*** Sets height of this canvas instance* @param {Number|String} value Value to set height to* @param {Object} [options] Options object* @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions* @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions* @return {fabric.Canvas} instance* @chainable true*/setHeight: function (value, options) {return this.setDimensions({ height: value }, options);},/*** Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em)* @param {Object} dimensions Object with width/height properties* @param {Number|String} [dimensions.width] Width of canvas element* @param {Number|String} [dimensions.height] Height of canvas element* @param {Object} [options] Options object* @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions* @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions* @return {fabric.Canvas} thisArg* @chainable*/setDimensions: function (dimensions, options) {var cssValue;options = options || {};for (var prop in dimensions) {cssValue = dimensions[prop];if (!options.cssOnly) {this._setBackstoreDimension(prop, dimensions[prop]);cssValue += 'px';}if (!options.backstoreOnly) {this._setCssDimension(prop, cssValue);}}if (!options.cssOnly) {this.renderAll();}this.calcOffset();return this;},/*** Helper for setting width/height* @private* @param {String} prop property (width|height)* @param {Number} value value to set property to* @return {fabric.Canvas} instance* @chainable true*/_setBackstoreDimension: function (prop, value) {this.lowerCanvasEl[prop] = value;if (this.upperCanvasEl) {this.upperCanvasEl[prop] = value;}if (this.cacheCanvasEl) {this.cacheCanvasEl[prop] = value;}this[prop] = value;return this;},/*** Helper for setting css width/height* @private* @param {String} prop property (width|height)* @param {String} value value to set property to* @return {fabric.Canvas} instance* @chainable true*/_setCssDimension: function (prop, value) {this.lowerCanvasEl.style[prop] = value;if (this.upperCanvasEl) {this.upperCanvasEl.style[prop] = value;}if (this.wrapperEl) {this.wrapperEl.style[prop] = value;}return this;},/*** Returns canvas zoom level* @return {Number}*/getZoom: function () {return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);},/*** Sets viewport transform of this canvas instance* @param {Array} vpt the transform in the form of context.transform* @return {fabric.Canvas} instance* @chainable true*/setViewportTransform: function (vpt) {var activeGroup = this.getActiveGroup();this.viewportTransform = vpt;this.renderAll();for (var i = 0, len = this._objects.length; i < len; i++) {this._objects[i].setCoords();}if (activeGroup) {activeGroup.setCoords();}return this;},/*** Sets zoom level of this canvas instance, zoom centered around point* @param {fabric.Point} point to zoom with respect to* @param {Number} value to set zoom to, less than 1 zooms out* @return {fabric.Canvas} instance* @chainable true*/zoomToPoint: function (point, value) {// TODO: just change the scale, preserve other transformationsvar before = point;point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform));this.viewportTransform[0] = value;this.viewportTransform[3] = value;var after = fabric.util.transformPoint(point, this.viewportTransform);this.viewportTransform[4] += before.x - after.x;this.viewportTransform[5] += before.y - after.y;this.renderAll();for (var i = 0, len = this._objects.length; i < len; i++) {this._objects[i].setCoords();}return this;},/*** Sets zoom level of this canvas instance* @param {Number} value to set zoom to, less than 1 zooms out* @return {fabric.Canvas} instance* @chainable true*/setZoom: function (value) {this.zoomToPoint(new fabric.Point(0, 0), value);return this;},/*** Pan viewport so as to place point at top left corner of canvas* @param {fabric.Point} point to move to* @return {fabric.Canvas} instance* @chainable true*/absolutePan: function (point) {this.viewportTransform[4] = -point.x;this.viewportTransform[5] = -point.y;this.renderAll();for (var i = 0, len = this._objects.length; i < len; i++) {this._objects[i].setCoords();}return this;},/*** Pans viewpoint relatively* @param {fabric.Point} point (position vector) to move by* @return {fabric.Canvas} instance* @chainable true*/relativePan: function (point) {return this.absolutePan(new fabric.Point(-point.x - this.viewportTransform[4],-point.y - this.viewportTransform[5]));},/*** Returns <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;}ctx.save();var v = this.viewportTransform;ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);if (this._shouldRenderObject(object)) {object.render(ctx);}ctx.restore();if (!this.controlsAboveOverlay) {object._renderControls(ctx);}},_shouldRenderObject: function(object) {if (!object) {return false;}return (object !== this.getActiveGroup() || !this.preserveObjectStacking);},/*** @private* @param {fabric.Object} obj Object that was added*/_onObjectAdded: function(obj) {this.stateful && obj.setupState();obj.canvas = this;obj.setCoords();this.fire('object:added', { target: obj });obj.fire('added');},/*** @private* @param {fabric.Object} obj Object that was removed*/_onObjectRemoved: function(obj) {// removing active object should fire "selection:cleared" eventsif (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'],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) {var i, length;// fast pathif (!activeGroup || this.preserveObjectStacking) {for (i = 0, length = this._objects.length; i < length; ++i) {this._draw(ctx, this._objects[i]);}}else {for (i = 0, length = this._objects.length; i < length; ++i) {if (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 replacevar 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._draw(ctx, this.backgroundImage);}},/*** @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._draw(ctx, this.overlayImage);}},/*** 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 objectif (this.selection && this._groupSelector) {this._drawSelection();}// delegate rendering to group selection if one exists// used for drawing selection borders/controlsvar activeGroup = this.getActiveGroup();if (activeGroup) {activeGroup.render(ctx);}this._renderOverlay(ctx);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 data = {objects: this._toObjects(methodName, propertiesToInclude)};extend(data, this.__serializeBgOverlay());fabric.util.populateWithProperties(this, data, propertiesToInclude);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;}//If the object is part of the current selection group, it should//be transformed appropriately//i.e. it should be serialised as it would appear if the selection group//were to be destroyed.var originalProperties = this._realizeGroupTransformOnObject(instance),object = instance[methodName](propertiesToInclude);if (!this.includeDefaultValues) {instance.includeDefaultValues = originalValue;}//Undo the damage we did by changing all of its propertiesthis._unwindGroupTransformOnObject(instance, originalProperties);return object;},/*** Realises an object's group transformation on it* @private* @param {fabric.Object} [instance] the object to transform (gets mutated)* @returns the original values of instance which were changed*/_realizeGroupTransformOnObject: function(instance) {var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width'];if (instance.group && instance.group === this.getActiveGroup()) {//Copy all the positionally relevant properties across nowvar originalValues = {};layoutProps.forEach(function(prop) {originalValues[prop] = instance[prop];});this.getActiveGroup().realizeTransform(instance);return originalValues;}else {return null;}},/** Restores the changed properties of instance* @private* @param {fabric.Object} [instance] the object to un-transform (gets mutated)* @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject*/_unwindGroupTransformOnObject: function(instance, originalValues) {if (originalValues) {instance.set(originalValues);}},/*** @private*/__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_ *//*** When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true,* a zoomed canvas will then produce zoomed SVG output.* @type Boolean* @default*/svgViewportTransformation: true,/*** Returns SVG representation of canvas* @function* @param {Object} [options] Options object for SVG output* @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included* @param {Object} [options.viewBox] SVG viewbox object* @param {Number} [options.viewBox.x] x-cooridnate of viewbox* @param {Number} [options.viewBox.y] y-coordinate of viewbox* @param {Number} [options.viewBox.width] Width of viewbox* @param {Number} [options.viewBox.height] Height of viewbox* @param {String} [options.encoding=UTF-8] Encoding of SVG output* @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation.* @return {String} SVG string* @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}* @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo}* @example <caption>Normal SVG output</caption>* var svg = canvas.toSVG();* @example <caption>SVG output without preamble (without <?xml ../>)</caption>* var svg = canvas.toSVG({suppressPreamble: true});* @example <caption>SVG output with viewBox attribute</caption>* var svg = canvas.toSVG({* viewBox: {* x: 100,* y: 100,* width: 200,* height: 300* }* });* @example <caption>SVG output with different encoding (default: UTF-8)</caption>* var svg = canvas.toSVG({encoding: 'ISO-8859-1'});* @example <caption>Modify SVG output with reviver function</caption>* var svg = canvas.toSVG(null, function(svg) {* return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', '');* });*/toSVG: function(options, reviver) {options || (options = { });var markup = [];this._setSVGPreamble(markup, options);this._setSVGHeader(markup, options);this._setSVGBgOverlayColor(markup, 'backgroundColor');this._setSVGBgOverlayImage(markup, 'backgroundImage');this._setSVGObjects(markup, reviver);this._setSVGBgOverlayColor(markup, 'overlayColor');this._setSVGBgOverlayImage(markup, 'overlayImage');markup.push('</svg>');return markup.join('');},/*** @private*/_setSVGPreamble: function(markup, options) {if (!options.suppressPreamble) {markup.push('<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ','"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n');}},/*** @private*/_setSVGHeader: function(markup, options) {var width, height, vpt;if (options.viewBox) {width = options.viewBox.width;height = options.viewBox.height;}else {width = this.width;height = this.height;if (!this.svgViewportTransformation) {vpt = this.viewportTransform;width /= vpt[0];height /= vpt[3];}}markup.push('<svg ','xmlns="http://www.w3.org/2000/svg" ','xmlns:xlink="http://www.w3.org/1999/xlink" ','version="1.1" ','width="', width, '" ','height="', height, '" ',(this.backgroundColor && !this.backgroundColor.toLive? 'style="background-color: ' + this.backgroundColor + '" ': null),(options.viewBox? 'viewBox="' +options.viewBox.x + ' ' +options.viewBox.y + ' ' +options.viewBox.width + ' ' +options.viewBox.height + '" ': null),'xml:space="preserve">','<desc>Created with Fabric.js ', fabric.version, '</desc>','<defs>',fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),'</defs>');},/*** @private*/_setSVGObjects: function(markup, reviver) {for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {var instance = objects[i],//If the object is in a selection group, simulate what would happen to that//object when the group is deselectedoriginalProperties = this._realizeGroupTransformOnObject(instance);markup.push(instance.toSVG(reviver));this._unwindGroupTransformOnObject(instance, originalProperties);}},/*** @private*/_setSVGBgOverlayImage: function(markup, property) {if (this[property] && this[property].toSVG) {markup.push(this[property].toSVG());}},/*** @private*/_setSVGBgOverlayColor: function(markup, property) {if (this[property] && this[property].source) {markup.push('<rect x="', this[property].offsetX, '" y="', this[property].offsetY, '" ','width="',(this[property].repeat === 'repeat-y' || this[property].repeat === 'no-repeat'? this[property].source.width: this.width),'" height="',(this[property].repeat === 'repeat-x' || this[property].repeat === 'no-repeat'? this[property].source.height: this.height),'" fill="url(#' + property + 'Pattern)"','></rect>');}else if (this[property] && property === 'overlayColor') {markup.push('<rect x="0" y="0" ','width="', this.width,'" height="', this.height,'" fill="', this[property], '"','></rect>');}},/* _TO_SVG_END_ *//*** Moves an object to the bottom of the stack of drawn objects* @param {fabric.Object} object Object to send to back* @return {fabric.Canvas} thisArg* @chainable*/sendToBack: function (object) {removeFromArray(this._objects, object);this._objects.unshift(object);return this.renderAll && this.renderAll();},/*** Moves an object to the top of the stack of drawn objects* @param {fabric.Object} object Object to send* @return {fabric.Canvas} thisArg* @chainable*/bringToFront: function (object) {removeFromArray(this._objects, object);this._objects.push(object);return this.renderAll && this.renderAll();},/*** Moves an object down in stack of drawn objects* @param {fabric.Object} object Object to send* @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object* @return {fabric.Canvas} thisArg* @chainable*/sendBackwards: function (object, intersecting) {var idx = this._objects.indexOf(object);// if object is not on the bottom of stackif (idx !== 0) {var newIdx = this._findNewLowerIndex(object, idx, intersecting);removeFromArray(this._objects, object);this._objects.splice(newIdx, 0, object);this.renderAll && this.renderAll();}return this;},/*** @private*/_findNewLowerIndex: function(object, idx, intersecting) {var newIdx;if (intersecting) {newIdx = idx;// traverse down the stack looking for the nearest intersecting objectfor (var i = idx - 1; i >= 0; --i) {var isIntersecting = object.intersectsWithObject(this._objects[i]) ||object.isContainedWithinObject(this._objects[i]) ||this._objects[i].isContainedWithinObject(object);if (isIntersecting) {newIdx = i;break;}}}else {newIdx = idx - 1;}return newIdx;},/*** Moves an object up in stack of drawn objects* @param {fabric.Object} object Object to send* @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object* @return {fabric.Canvas} thisArg* @chainable*/bringForward: function (object, intersecting) {var idx = this._objects.indexOf(object);// if object is not on top of stack (last item in an array)if (idx !== this._objects.length - 1) {var newIdx = this._findNewUpperIndex(object, idx, intersecting);removeFromArray(this._objects, object);this._objects.splice(newIdx, 0, object);this.renderAll && this.renderAll();}return this;},/*** @private*/_findNewUpperIndex: function(object, idx, intersecting) {var newIdx;if (intersecting) {newIdx = idx;// traverse up the stack looking for the nearest intersecting objectfor (var i = idx + 1; i < this._objects.length; ++i) {var isIntersecting = object.intersectsWithObject(this._objects[i]) ||object.isContainedWithinObject(this._objects[i]) ||this._objects[i].isContainedWithinObject(object);if (isIntersecting) {newIdx = i;break;}}}else {newIdx = idx + 1;}return newIdx;},/*** Moves an object to specified level in stack of drawn objects* @param {fabric.Object} object Object to send* @param {Number} index Position to move to* @return {fabric.Canvas} thisArg* @chainable*/moveTo: function (object, index) {removeFromArray(this._objects, object);this._objects.splice(index, 0, object);return this.renderAll && this.renderAll();},/*** Clears a canvas element and removes all event listeners* @return {fabric.Canvas} thisArg* @chainable*/dispose: function () {this.clear();this.interactive && this.removeListeners();return this;},/*** Returns a string representation of an instance* @return {String} string representation of an instance*/toString: function () {return '#<fabric.Canvas (' + this.complexity() + '): ' +'{ objects: ' + this.getObjects().length + ' }>';}});extend(fabric.StaticCanvas.prototype, fabric.Observable);extend(fabric.StaticCanvas.prototype, fabric.Collection);extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter);extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ {/*** @static* @type String* @default*/EMPTY_JSON: '{"objects": [], "background": "white"}',/*** Provides a way to check support of some of the canvas methods* (either those of HTMLCanvasElement itself, or rendering context)** @param {String} methodName Method to check support for;* Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash"* @return {Boolean | null} `true` if method is supported (or at least exists),* `null` if canvas element or context can not be initialized*/supports: function (methodName) {var el = fabric.util.createCanvasElement();if (!el || !el.getContext) {return null;}var ctx = el.getContext('2d');if (!ctx) {return null;}switch (methodName) {case 'getImageData':return typeof ctx.getImageData !== 'undefined';case 'setLineDash':return typeof ctx.setLineDash !== 'undefined';case 'toDataURL':return typeof el.toDataURL !== 'undefined';case 'toDataURLWithQuality':try {el.toDataURL('image/jpeg', 0);return true;}catch (e) { }return false;default:return null;}}});/*** Returns JSON representation of canvas* @function* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {String} JSON string* @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}* @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo}* @example <caption>JSON without additional properties</caption>* var json = canvas.toJSON();* @example <caption>JSON with additional properties included</caption>* var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']);* @example <caption>JSON without default values</caption>* canvas.includeDefaultValues = false;* var json = canvas.toJSON();*/fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;})();/*** BaseBrush class* @class fabric.BaseBrush* @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo}*/fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ {/*** Color of a brush* @type String* @default*/color: 'rgb(0, 0, 0)',/*** Width of a brush* @type Number* @default*/width: 1,/*** Shadow object representing shadow of this shape.* <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number),* "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12* @type fabric.Shadow* @default*/shadow: null,/*** Line endings style of a brush (one of "butt", "round", "square")* @type String* @default*/strokeLineCap: 'round',/*** Corner style of a brush (one of "bevil", "round", "miter")* @type String* @default*/strokeLineJoin: 'round',/*** Stroke Dash Array.* @type Array* @default*/strokeDashArray: null,/*** Sets shadow of an object* @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")* @return {fabric.Object} thisArg* @chainable*/setShadow: function(options) {this.shadow = new fabric.Shadow(options);return this;},/*** Sets brush styles* @private*/_setBrushStyles: function() {var ctx = this.canvas.contextTop;ctx.strokeStyle = this.color;ctx.lineWidth = this.width;ctx.lineCap = this.strokeLineCap;ctx.lineJoin = this.strokeLineJoin;if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) {ctx.setLineDash(this.strokeDashArray);}},/*** Sets brush shadow styles* @private*/_setShadow: function() {if (!this.shadow) {return;}var ctx = this.canvas.contextTop;ctx.shadowColor = this.shadow.color;ctx.shadowBlur = this.shadow.blur;ctx.shadowOffsetX = this.shadow.offsetX;ctx.shadowOffsetY = this.shadow.offsetY;},/*** Removes brush shadow styles* @private*/_resetShadow: function() {var ctx = this.canvas.contextTop;ctx.shadowColor = '';ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;}});(function() {/*** PencilBrush class* @class fabric.PencilBrush* @extends fabric.BaseBrush*/fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ {/*** Constructor* @param {fabric.Canvas} canvas* @return {fabric.PencilBrush} Instance of a pencil brush*/initialize: function(canvas) {this.canvas = canvas;this._points = [ ];},/*** Inovoked on mouse down* @param {Object} pointer*/onMouseDown: function(pointer) {this._prepareForDrawing(pointer);// capture coordinates immediately// this allows to draw dots (when movement never occurs)this._captureDrawingPath(pointer);this._render();},/*** Inovoked on mouse move* @param {Object} pointer*/onMouseMove: function(pointer) {this._captureDrawingPath(pointer);// redraw curve// clear top canvasthis.canvas.clearContext(this.canvas.contextTop);this._render();},/*** Invoked on mouse up*/onMouseUp: function() {this._finalizeAndAddPath();},/*** @private* @param {Object} pointer Actual mouse position related to the canvas.*/_prepareForDrawing: function(pointer) {var p = new fabric.Point(pointer.x, pointer.y);this._reset();this._addPoint(p);this.canvas.contextTop.moveTo(p.x, p.y);},/*** @private* @param {fabric.Point} point Point to be added to points array*/_addPoint: function(point) {this._points.push(point);},/*** Clear points array and set contextTop canvas style.* @private*/_reset: function() {this._points.length = 0;this._setBrushStyles();this._setShadow();},/*** @private* @param {Object} pointer Actual mouse position related to the canvas.*/_captureDrawingPath: function(pointer) {var pointerPoint = new fabric.Point(pointer.x, pointer.y);this._addPoint(pointerPoint);},/*** Draw a smooth path on the topCanvas using quadraticCurveTo* @private*/_render: function() {var ctx = this.canvas.contextTop,v = this.canvas.viewportTransform,p1 = this._points[0],p2 = this._points[1];ctx.save();ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);ctx.beginPath();//if we only have 2 points in the path and they are the same//it means that the user only clicked the canvas without moving the mouse//then we should be drawing a dot. A path isn't drawn between two identical dots//that's why we set them apart a bitif (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {p1.x -= 0.5;p2.x += 0.5;}ctx.moveTo(p1.x, p1.y);for (var i = 1, len = this._points.length; i < len; i++) {// we pick the point between pi + 1 & pi + 2 as the// end point and p1 as our control point.var midPoint = p1.midPointFrom(p2);ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);p1 = this._points[i];p2 = this._points[i + 1];}// Draw last line as a straight line while// we wait for the next point to be able to calculate// the bezier control pointctx.lineTo(p1.x, p1.y);ctx.stroke();ctx.restore();},/*** Converts points to SVG path* @param {Array} points Array of points* @param {Number} minX* @param {Number} minY* @return {String} SVG path*/convertPointsToSVGPath: function(points) {var path = [],p1 = new fabric.Point(points[0].x, points[0].y),p2 = new fabric.Point(points[1].x, points[1].y);path.push('M ', points[0].x, ' ', points[0].y, ' ');for (var i = 1, len = points.length; i < len; i++) {var midPoint = p1.midPointFrom(p2);// p1 is our bezier control point// midpoint is our endpoint// start point is p(i-1) value.path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');p1 = new fabric.Point(points[i].x, points[i].y);if ((i + 1) < points.length) {p2 = new fabric.Point(points[i + 1].x, points[i + 1].y);}}path.push('L ', p1.x, ' ', p1.y, ' ');return path;},/*** Creates fabric.Path object to add on canvas* @param {String} pathData Path data* @return {fabric.Path} Path to add on canvas*/createPath: function(pathData) {var path = new fabric.Path(pathData, {fill: null,stroke: this.color,strokeWidth: this.width,strokeLineCap: this.strokeLineCap,strokeLineJoin: this.strokeLineJoin,strokeDashArray: this.strokeDashArray,originX: 'center',originY: 'center'});if (this.shadow) {this.shadow.affectStroke = true;path.setShadow(this.shadow);}return path;},/*** On mouseup after drawing the path on contextTop canvas* we use the points captured to create an new fabric path object* and add it to the fabric canvas.*/_finalizeAndAddPath: function() {var ctx = this.canvas.contextTop;ctx.closePath();var pathData = this.convertPointsToSVGPath(this._points).join('');if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {// do not create 0 width/height paths, as they are// rendered inconsistently across browsers// Firefox 4, for example, renders a dot,// whereas Chrome 10 renders nothingthis.canvas.renderAll();return;}var path = this.createPath(pathData);this.canvas.add(path);path.setCoords();this.canvas.clearContext(this.canvas.contextTop);this._resetShadow();this.canvas.renderAll();// fire event 'path' createdthis.canvas.fire('path:created', { path: path });}});})();/*** CircleBrush class* @class fabric.CircleBrush*/fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ {/*** Width of a brush* @type Number* @default*/width: 10,/*** Constructor* @param {fabric.Canvas} canvas* @return {fabric.CircleBrush} Instance of a circle brush*/initialize: function(canvas) {this.canvas = canvas;this.points = [ ];},/*** Invoked inside on mouse down and mouse move* @param {Object} pointer*/drawDot: function(pointer) {var point = this.addPoint(pointer),ctx = this.canvas.contextTop,v = this.canvas.viewportTransform;ctx.save();ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);ctx.fillStyle = point.fill;ctx.beginPath();ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);ctx.closePath();ctx.fill();ctx.restore();},/*** Invoked on mouse down*/onMouseDown: function(pointer) {this.points.length = 0;this.canvas.clearContext(this.canvas.contextTop);this._setShadow();this.drawDot(pointer);},/*** Invoked on mouse move* @param {Object} pointer*/onMouseMove: function(pointer) {this.drawDot(pointer);},/*** Invoked on mouse up*/onMouseUp: function() {var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;this.canvas.renderOnAddRemove = false;var circles = [ ];for (var i = 0, len = this.points.length; i < len; i++) {var point = this.points[i],circle = new fabric.Circle({radius: point.radius,left: point.x,top: point.y,originX: 'center',originY: 'center',fill: point.fill});this.shadow && circle.setShadow(this.shadow);circles.push(circle);}var group = new fabric.Group(circles, { originX: 'center', originY: 'center' });group.canvas = this.canvas;this.canvas.add(group);this.canvas.fire('path:created', { path: group });this.canvas.clearContext(this.canvas.contextTop);this._resetShadow();this.canvas.renderOnAddRemove = originalRenderOnAddRemove;this.canvas.renderAll();},/*** @param {Object} pointer* @return {fabric.Point} Just added pointer point*/addPoint: function(pointer) {var pointerPoint = new fabric.Point(pointer.x, pointer.y),circleRadius = fabric.util.getRandomInt(Math.max(0, this.width - 20), this.width + 20) / 2,circleColor = new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0, 100) / 100).toRgba();pointerPoint.radius = circleRadius;pointerPoint.fill = circleColor;this.points.push(pointerPoint);return pointerPoint;}});/*** SprayBrush class* @class fabric.SprayBrush*/fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ {/*** Width of a spray* @type Number* @default*/width: 10,/*** Density of a spray (number of dots per chunk)* @type Number* @default*/density: 20,/*** Width of spray dots* @type Number* @default*/dotWidth: 1,/*** Width variance of spray dots* @type Number* @default*/dotWidthVariance: 1,/*** Whether opacity of a dot should be random* @type Boolean* @default*/randomOpacity: false,/*** Whether overlapping dots (rectangles) should be removed (for performance reasons)* @type Boolean* @default*/optimizeOverlapping: true,/*** Constructor* @param {fabric.Canvas} canvas* @return {fabric.SprayBrush} Instance of a spray brush*/initialize: function(canvas) {this.canvas = canvas;this.sprayChunks = [ ];},/*** Invoked on mouse down* @param {Object} pointer*/onMouseDown: function(pointer) {this.sprayChunks.length = 0;this.canvas.clearContext(this.canvas.contextTop);this._setShadow();this.addSprayChunk(pointer);this.render();},/*** Invoked on mouse move* @param {Object} pointer*/onMouseMove: function(pointer) {this.addSprayChunk(pointer);this.render();},/*** Invoked on mouse up*/onMouseUp: function() {var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;this.canvas.renderOnAddRemove = false;var rects = [ ];for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {var sprayChunk = this.sprayChunks[i];for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) {var rect = new fabric.Rect({width: sprayChunk[j].width,height: sprayChunk[j].width,left: sprayChunk[j].x + 1,top: sprayChunk[j].y + 1,originX: 'center',originY: 'center',fill: this.color});this.shadow && rect.setShadow(this.shadow);rects.push(rect);}}if (this.optimizeOverlapping) {rects = this._getOptimizedRects(rects);}var group = new fabric.Group(rects, { originX: 'center', originY: 'center' });group.canvas = this.canvas;this.canvas.add(group);this.canvas.fire('path:created', { path: group });this.canvas.clearContext(this.canvas.contextTop);this._resetShadow();this.canvas.renderOnAddRemove = originalRenderOnAddRemove;this.canvas.renderAll();},/*** @private* @param {Array} rects*/_getOptimizedRects: function(rects) {// avoid creating duplicate rects at the same coordinatesvar uniqueRects = { }, key;for (var i = 0, len = rects.length; i < len; i++) {key = rects[i].left + '' + rects[i].top;if (!uniqueRects[key]) {uniqueRects[key] = rects[i];}}var uniqueRectsArray = [ ];for (key in uniqueRects) {uniqueRectsArray.push(uniqueRects[key]);}return uniqueRectsArray;},/*** Renders brush*/render: function() {var ctx = this.canvas.contextTop;ctx.fillStyle = this.color;var v = this.canvas.viewportTransform;ctx.save();ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) {var point = this.sprayChunkPoints[i];if (typeof point.opacity !== 'undefined') {ctx.globalAlpha = point.opacity;}ctx.fillRect(point.x, point.y, point.width, point.width);}ctx.restore();},/*** @param {Object} pointer*/addSprayChunk: function(pointer) {this.sprayChunkPoints = [ ];var x, y, width, radius = this.width / 2;for (var i = 0; i < this.density; i++) {x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius);y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius);if (this.dotWidthVariance) {width = fabric.util.getRandomInt(// bottom clamp width to 1Math.max(1, this.dotWidth - this.dotWidthVariance),this.dotWidth + this.dotWidthVariance);}else {width = this.dotWidth;}var point = new fabric.Point(x, y);point.width = width;if (this.randomOpacity) {point.opacity = fabric.util.getRandomInt(0, 100) / 100;}this.sprayChunkPoints.push(point);}this.sprayChunks.push(this.sprayChunkPoints);}});/*** PatternBrush class* @class fabric.PatternBrush* @extends fabric.BaseBrush*/fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ {getPatternSrc: function() {var dotWidth = 20,dotDistance = 5,patternCanvas = fabric.document.createElement('canvas'),patternCtx = patternCanvas.getContext('2d');patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;patternCtx.fillStyle = this.color;patternCtx.beginPath();patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false);patternCtx.closePath();patternCtx.fill();return patternCanvas;},getPatternSrcFunction: function() {return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"');},/*** Creates "pattern" instance property*/getPattern: function() {return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');},/*** Sets brush styles*/_setBrushStyles: function() {this.callSuper('_setBrushStyles');this.canvas.contextTop.strokeStyle = this.getPattern();},/*** Creates path*/createPath: function(pathData) {var path = this.callSuper('createPath', pathData);path.stroke = new fabric.Pattern({source: this.source || this.getPatternSrcFunction()});return path;}});(function() {var getPointer = fabric.util.getPointer,degreesToRadians = fabric.util.degreesToRadians,radiansToDegrees = fabric.util.radiansToDegrees,atan2 = Math.atan2,abs = Math.abs,STROKE_OFFSET = 0.5;/*** Canvas class* @class fabric.Canvas* @extends fabric.StaticCanvas* @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas}* @see {@link fabric.Canvas#initialize} for constructor definition** @fires object:modified* @fires object:rotating* @fires object:scaling* @fires object:moving* @fires object:selected** @fires before:selection:cleared* @fires selection:cleared* @fires selection:created** @fires path:created* @fires mouse:down* @fires mouse:move* @fires mouse:up* @fires mouse:over* @fires mouse:out**/fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {/*** Constructor* @param {HTMLElement | String} el <canvas> element to initialize instance on* @param {Object} [options] Options object* @return {Object} thisArg*/initialize: function(el, options) {options || (options = { });this._initStatic(el, options);this._initInteractive();this._createCacheCanvas();fabric.Canvas.activeInstance = this;},/*** When true, objects can be transformed by one side (unproportionally)* @type Boolean* @default*/uniScaleTransform: false,/*** When true, objects use center point as the origin of scale transformation.* <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).* @since 1.3.4* @type Boolean* @default*/centeredScaling: false,/*** When true, objects use center point as the origin of rotate transformation.* <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).* @since 1.3.4* @type Boolean* @default*/centeredRotation: false,/*** Indicates that canvas is interactive. This property should not be changed.* @type Boolean* @default*/interactive: true,/*** Indicates whether group selection should be enabled* @type Boolean* @default*/selection: true,/*** Color of selection* @type String* @default*/selectionColor: 'rgba(100, 100, 255, 0.3)', // blue/*** Default dash array pattern* If not empty the selection border is dashed* @type Array*/selectionDashArray: [ ],/*** Color of the border of selection (usually slightly darker than color of selection itself)* @type String* @default*/selectionBorderColor: 'rgba(255, 255, 255, 0.3)',/*** Width of a line used in object/group selection* @type Number* @default*/selectionLineWidth: 1,/*** Default cursor value used when hovering over an object on canvas* @type String* @default*/hoverCursor: 'move',/*** Default cursor value used when moving an object on canvas* @type String* @default*/moveCursor: 'move',/*** Default cursor value used for the entire canvas* @type String* @default*/defaultCursor: 'default',/*** Cursor value used during free drawing* @type String* @default*/freeDrawingCursor: 'crosshair',/*** Cursor value used for rotation point* @type String* @default*/rotationCursor: 'crosshair',/*** Default element class that's given to wrapper (div) element of canvas* @type String* @default*/containerClass: 'canvas-container',/*** When true, object detection happens on per-pixel basis rather than on per-bounding-box* @type Boolean* @default*/perPixelTargetFind: false,/*** Number of pixels around target pixel to tolerate (consider active) during object detection* @type Number* @default*/targetFindTolerance: 0,/*** When true, target detection is skipped when hovering over canvas. This can be used to improve performance.* @type Boolean* @default*/skipTargetFind: false,/*** @private*/_initInteractive: function() {this._currentTransform = null;this._groupSelector = null;this._initWrapperElement();this._createUpperCanvas();this._initEventListeners();this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);this.calcOffset();},/*** Resets the current transform to its original values and chooses the type of resizing based on the event* @private* @param {Event} e Event object fired on mousemove*/_resetCurrentTransform: function(e) {var t = this._currentTransform;t.target.set({scaleX: t.original.scaleX,scaleY: t.original.scaleY,left: t.original.left,top: t.original.top});if (this._shouldCenterTransform(e, t.target)) {if (t.action === 'rotate') {this._setOriginToCenter(t.target);}else {if (t.originX !== 'center') {if (t.originX === 'right') {t.mouseXSign = -1;}else {t.mouseXSign = 1;}}if (t.originY !== 'center') {if (t.originY === 'bottom') {t.mouseYSign = -1;}else {t.mouseYSign = 1;}}t.originX = 'center';t.originY = 'center';}}else {t.originX = t.original.originX;t.originY = t.original.originY;}},/*** Checks if point is contained within an area of given object* @param {Event} e Event object* @param {fabric.Object} target Object to test against* @return {Boolean} true if point is contained within an area of given object*/containsPoint: function (e, target) {var pointer = this.getPointer(e, true),xy = this._normalizePointer(target, pointer);// http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html// http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.htmlreturn (target.containsPoint(xy) || target._findTargetCorner(pointer));},/*** @private*/_normalizePointer: function (object, pointer) {var activeGroup = this.getActiveGroup(),x = pointer.x,y = pointer.y,isObjectInGroup = (activeGroup &&object.type !== 'group' &&activeGroup.contains(object)),lt;if (isObjectInGroup) {lt = new fabric.Point(activeGroup.left, activeGroup.top);lt = fabric.util.transformPoint(lt, this.viewportTransform, true);x -= lt.x;y -= lt.y;}return { x: x, y: y };},/*** Returns true if object is transparent at a certain location* @param {fabric.Object} target Object to check* @param {Number} x Left coordinate* @param {Number} y Top coordinate* @return {Boolean}*/isTargetTransparent: function (target, x, y) {var hasBorders = target.hasBorders,transparentCorners = target.transparentCorners;target.hasBorders = target.transparentCorners = false;this._draw(this.contextCache, target);target.hasBorders = hasBorders;target.transparentCorners = transparentCorners;var isTransparent = fabric.util.isTransparent(this.contextCache, x, y, this.targetFindTolerance);this.clearContext(this.contextCache);return isTransparent;},/*** @private* @param {Event} e Event object* @param {fabric.Object} target*/_shouldClearSelection: function (e, target) {var activeGroup = this.getActiveGroup(),activeObject = this.getActiveObject();return (!target||(target &&activeGroup &&!activeGroup.contains(target) &&activeGroup !== target &&!e.shiftKey)||(target && !target.evented)||(target &&!target.selectable &&activeObject &&activeObject !== target));},/*** @private* @param {Event} e Event object* @param {fabric.Object} target*/_shouldCenterTransform: function (e, target) {if (!target) {return;}var t = this._currentTransform,centerTransform;if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') {centerTransform = this.centeredScaling || target.centeredScaling;}else if (t.action === 'rotate') {centerTransform = this.centeredRotation || target.centeredRotation;}return centerTransform ? !e.altKey : e.altKey;},/*** @private*/_getOriginFromCorner: function(target, corner) {var origin = {x: target.originX,y: target.originY};if (corner === 'ml' || corner === 'tl' || corner === 'bl') {origin.x = 'right';}else if (corner === 'mr' || corner === 'tr' || corner === 'br') {origin.x = 'left';}if (corner === 'tl' || corner === 'mt' || corner === 'tr') {origin.y = 'bottom';}else if (corner === 'bl' || corner === 'mb' || corner === 'br') {origin.y = 'top';}return origin;},/*** @private*/_getActionFromCorner: function(target, corner) {var action = 'drag';if (corner) {action = (corner === 'ml' || corner === 'mr')? 'scaleX': (corner === 'mt' || corner === 'mb')? 'scaleY': corner === 'mtr'? 'rotate': 'scale';}return action;},/*** @private* @param {Event} e Event object* @param {fabric.Object} target*/_setupCurrentTransform: function (e, target) {if (!target) {return;}var pointer = this.getPointer(e),corner = target._findTargetCorner(this.getPointer(e, true)),action = this._getActionFromCorner(target, corner),origin = this._getOriginFromCorner(target, corner);this._currentTransform = {target: target,action: action,scaleX: target.scaleX,scaleY: target.scaleY,offsetX: pointer.x - target.left,offsetY: pointer.y - target.top,originX: origin.x,originY: origin.y,ex: pointer.x,ey: pointer.y,left: target.left,top: target.top,theta: degreesToRadians(target.angle),width: target.width * target.scaleX,mouseXSign: 1,mouseYSign: 1};this._currentTransform.original = {left: target.left,top: target.top,scaleX: target.scaleX,scaleY: target.scaleY,originX: origin.x,originY: origin.y};this._resetCurrentTransform(e);},/*** Translates object by "setting" its left/top* @private* @param {Number} x pointer's x coordinate* @param {Number} y pointer's y coordinate*/_translateObject: function (x, y) {var target = this._currentTransform.target;if (!target.get('lockMovementX')) {target.set('left', x - this._currentTransform.offsetX);}if (!target.get('lockMovementY')) {target.set('top', y - this._currentTransform.offsetY);}},/*** Scales object by invoking its scaleX/scaleY methods* @private* @param {Number} x pointer's x coordinate* @param {Number} y pointer's y coordinate* @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object.* When not provided, an object is scaled by both dimensions equally*/_scaleObject: function (x, y, by) {var t = this._currentTransform,target = t.target,lockScalingX = target.get('lockScalingX'),lockScalingY = target.get('lockScalingY'),lockScalingFlip = target.get('lockScalingFlip');if (lockScalingX && lockScalingY) {return;}// Get the constraint pointvar constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY),localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY);this._setLocalMouse(localMouse, t);// Actually scale the objectthis._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip);// Make sure the constraints applytarget.setPositionByOrigin(constraintPosition, t.originX, t.originY);},/*** @private*/_setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) {var target = transform.target, forbidScalingX = false, forbidScalingY = false,strokeWidth = target.stroke ? target.strokeWidth : 0;transform.newScaleX = localMouse.x / (target.width + strokeWidth / 2);transform.newScaleY = localMouse.y / (target.height + strokeWidth / 2);if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) {forbidScalingX = true;}if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) {forbidScalingY = true;}if (by === 'equally' && !lockScalingX && !lockScalingY) {forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform);}else if (!by) {forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX);forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY);}else if (by === 'x' && !target.get('lockUniScaling')) {forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX);}else if (by === 'y' && !target.get('lockUniScaling')) {forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY);}forbidScalingX || forbidScalingY || this._flipObject(transform, by);},/*** @private*/_scaleObjectEqually: function(localMouse, target, transform) {var dist = localMouse.y + localMouse.x,strokeWidth = target.stroke ? target.strokeWidth : 0,lastDist = (target.height + (strokeWidth / 2)) * transform.original.scaleY +(target.width + (strokeWidth / 2)) * transform.original.scaleX;// We use transform.scaleX/Y instead of target.scaleX/Y// because the object may have a min scale and we'll loose the proportionstransform.newScaleX = transform.original.scaleX * dist / lastDist;transform.newScaleY = transform.original.scaleY * dist / lastDist;target.set('scaleX', transform.newScaleX);target.set('scaleY', transform.newScaleY);},/*** @private*/_flipObject: function(transform, by) {if (transform.newScaleX < 0 && by !== 'y') {if (transform.originX === 'left') {transform.originX = 'right';}else if (transform.originX === 'right') {transform.originX = 'left';}}if (transform.newScaleY < 0 && by !== 'x') {if (transform.originY === 'top') {transform.originY = 'bottom';}else if (transform.originY === 'bottom') {transform.originY = 'top';}}},/*** @private*/_setLocalMouse: function(localMouse, t) {var target = t.target;if (t.originX === 'right') {localMouse.x *= -1;}else if (t.originX === 'center') {localMouse.x *= t.mouseXSign * 2;if (localMouse.x < 0) {t.mouseXSign = -t.mouseXSign;}}if (t.originY === 'bottom') {localMouse.y *= -1;}else if (t.originY === 'center') {localMouse.y *= t.mouseYSign * 2;if (localMouse.y < 0) {t.mouseYSign = -t.mouseYSign;}}// adjust the mouse coordinates when dealing with paddingif (abs(localMouse.x) > target.padding) {if (localMouse.x < 0) {localMouse.x += target.padding;}else {localMouse.x -= target.padding;}}else { // mouse is within the padding, set to 0localMouse.x = 0;}if (abs(localMouse.y) > target.padding) {if (localMouse.y < 0) {localMouse.y += target.padding;}else {localMouse.y -= target.padding;}}else {localMouse.y = 0;}},/*** Rotates object by invoking its rotate method* @private* @param {Number} x pointer's x coordinate* @param {Number} y pointer's y coordinate*/_rotateObject: function (x, y) {var t = this._currentTransform;if (t.target.get('lockRotation')) {return;}var lastAngle = atan2(t.ey - t.top, t.ex - t.left),curAngle = atan2(y - t.top, x - t.left),angle = radiansToDegrees(curAngle - lastAngle + t.theta);// normalize angle to positive valueif (angle < 0) {angle = 360 + angle;}t.target.angle = angle % 360;},/*** Set the cursor type of the canvas element* @param {String} value Cursor type of the canvas element.* @see http://www.w3.org/TR/css3-ui/#cursor*/setCursor: function (value) {this.upperCanvasEl.style.cursor = value;},/*** @private*/_resetObjectTransform: function (target) {target.scaleX = 1;target.scaleY = 1;target.setAngle(0);},/*** @private*/_drawSelection: function () {var ctx = this.contextTop,groupSelector = this._groupSelector,left = groupSelector.left,top = groupSelector.top,aleft = abs(left),atop = abs(top);ctx.fillStyle = this.selectionColor;ctx.fillRect(groupSelector.ex - ((left > 0) ? 0 : -left),groupSelector.ey - ((top > 0) ? 0 : -top),aleft,atop);ctx.lineWidth = this.selectionLineWidth;ctx.strokeStyle = this.selectionBorderColor;// selection borderif (this.selectionDashArray.length > 1) {var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft),py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop);ctx.beginPath();fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray);fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray);fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray);fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray);ctx.closePath();ctx.stroke();}else {ctx.strokeRect(groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),aleft,atop);}},/*** @private*/_isLastRenderedObject: function(e) {return (this.controlsAboveOverlay &&this.lastRenderedObjectWithControlsAboveOverlay &&this.lastRenderedObjectWithControlsAboveOverlay.visible &&this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) &&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true)));},/*** Method that determines what object we are clicking on* @param {Event} e mouse event* @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through*/findTarget: function (e, skipGroup) {if (this.skipTargetFind) {return;}if (this._isLastRenderedObject(e)) {return this.lastRenderedObjectWithControlsAboveOverlay;}// first check current group (if one exists)var activeGroup = this.getActiveGroup();if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {return activeGroup;}var target = this._searchPossibleTargets(e);this._fireOverOutEvents(target);return target;},/*** @private*/_fireOverOutEvents: function(target) {if (target) {if (this._hoveredTarget !== target) {this.fire('mouse:over', { target: target });target.fire('mouseover');if (this._hoveredTarget) {this.fire('mouse:out', { target: this._hoveredTarget });this._hoveredTarget.fire('mouseout');}this._hoveredTarget = target;}}else if (this._hoveredTarget) {this.fire('mouse:out', { target: this._hoveredTarget });this._hoveredTarget.fire('mouseout');this._hoveredTarget = null;}},/*** @private*/_checkTarget: function(e, obj, pointer) {if (obj &&obj.visible &&obj.evented &&this.containsPoint(e, obj)){if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);if (!isTransparent) {return true;}}else {return true;}}},/*** @private*/_searchPossibleTargets: function(e) {// Cache all targets where their bounding box contains point.var target,pointer = this.getPointer(e, true),i = this._objects.length;// Do not check for currently grouped objects, since we check the parent group itself.while (i--) {if (!this._objects[i].group && this._checkTarget(e, this._objects[i], pointer)){this.relatedTarget = this._objects[i];target = this._objects[i];break;}}return target;},/*** Returns pointer coordinates relative to canvas.* @param {Event} e* @return {Object} object with "x" and "y" number values*/getPointer: function (e, ignoreZoom, upperCanvasEl) {if (!upperCanvasEl) {upperCanvasEl = this.upperCanvasEl;}var pointer = getPointer(e, upperCanvasEl),bounds = upperCanvasEl.getBoundingClientRect(),boundsWidth = bounds.width || 0,boundsHeight = bounds.height || 0,cssScale;if (!boundsWidth || !boundsHeight ) {if ('top' in bounds && 'bottom' in bounds) {boundsHeight = Math.abs( bounds.top - bounds.bottom );}if ('right' in bounds && 'left' in bounds) {boundsWidth = Math.abs( bounds.right - bounds.left );}}this.calcOffset();pointer.x = pointer.x - this._offset.left;pointer.y = pointer.y - this._offset.top;if (!ignoreZoom) {pointer = fabric.util.transformPoint(pointer,fabric.util.invertTransform(this.viewportTransform));}if (boundsWidth === 0 || boundsHeight === 0) {// If bounds are not available (i.e. not visible), do not apply scale.cssScale = { width: 1, height: 1 };}else {cssScale = {width: upperCanvasEl.width / boundsWidth,height: upperCanvasEl.height / boundsHeight};}return {x: pointer.x * cssScale.width,y: pointer.y * cssScale.height};},/*** @private* @throws {CANVAS_INIT_ERROR} If canvas can not be initialized*/_createUpperCanvas: function () {var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, '');this.upperCanvasEl = this._createCanvasElement();fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);this.wrapperEl.appendChild(this.upperCanvasEl);this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);this._applyCanvasStyle(this.upperCanvasEl);this.contextTop = this.upperCanvasEl.getContext('2d');},/*** @private*/_createCacheCanvas: function () {this.cacheCanvasEl = this._createCanvasElement();this.cacheCanvasEl.setAttribute('width', this.width);this.cacheCanvasEl.setAttribute('height', this.height);this.contextCache = this.cacheCanvasEl.getContext('2d');},/*** @private*/_initWrapperElement: function () {this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {'class': this.containerClass});fabric.util.setStyle(this.wrapperEl, {width: this.getWidth() + 'px',height: this.getHeight() + 'px',position: 'relative'});fabric.util.makeElementUnselectable(this.wrapperEl);},/*** @private* @param {HTMLElement} element canvas element to apply styles on*/_applyCanvasStyle: function (element) {var width = this.getWidth() || element.width,height = this.getHeight() || element.height;fabric.util.setStyle(element, {position: 'absolute',width: width + 'px',height: height + 'px',left: 0,top: 0});element.width = width;element.height = height;fabric.util.makeElementUnselectable(element);},/*** Copys the the entire inline style from one element (fromEl) to another (toEl)* @private* @param {Element} fromEl Element style is copied from* @param {Element} toEl Element copied style is applied to*/_copyCanvasStyle: function (fromEl, toEl) {toEl.style.cssText = fromEl.style.cssText;},/*** Returns context of canvas where object selection is drawn* @return {CanvasRenderingContext2D}*/getSelectionContext: function() {return this.contextTop;},/*** Returns <canvas> element on which object selection is drawn* @return {HTMLCanvasElement}*/getSelectionElement: function () {return this.upperCanvasEl;},/*** @private* @param {Object} object*/_setActiveObject: function(object) {if (this._activeObject) {this._activeObject.set('active', false);}this._activeObject = object;object.set('active', true);},/*** Sets given object as the only active object on canvas* @param {fabric.Object} object Object to set as an active one* @param {Event} [e] Event (passed along when firing "object:selected")* @return {fabric.Canvas} thisArg* @chainable*/setActiveObject: function (object, e) {this._setActiveObject(object);this.renderAll();this.fire('object:selected', { target: object, e: e });object.fire('selected', { e: e });return this;},/*** Returns currently active object* @return {fabric.Object} active object*/getActiveObject: function () {return this._activeObject;},/*** @private*/_discardActiveObject: function() {if (this._activeObject) {this._activeObject.set('active', false);}this._activeObject = null;},/*** Discards currently active object* @return {fabric.Canvas} thisArg* @chainable*/discardActiveObject: function (e) {this._discardActiveObject();this.renderAll();this.fire('selection:cleared', { e: e });return this;},/*** @private* @param {fabric.Group} group*/_setActiveGroup: function(group) {this._activeGroup = group;if (group) {group.set('active', true);}},/*** Sets active group to a speicified one* @param {fabric.Group} group Group to set as a current one* @return {fabric.Canvas} thisArg* @chainable*/setActiveGroup: function (group, e) {this._setActiveGroup(group);if (group) {this.fire('object:selected', { target: group, e: e });group.fire('selected', { e: e });}return this;},/*** Returns currently active group* @return {fabric.Group} Current group*/getActiveGroup: function () {return this._activeGroup;},/*** @private*/_discardActiveGroup: function() {var g = this.getActiveGroup();if (g) {g.destroy();}this.setActiveGroup(null);},/*** Discards currently active group* @return {fabric.Canvas} thisArg*/discardActiveGroup: function (e) {this._discardActiveGroup();this.fire('selection:cleared', { e: e });return this;},/*** Deactivates all objects on canvas, removing any active group or object* @return {fabric.Canvas} thisArg*/deactivateAll: function () {var allObjects = this.getObjects(),i = 0,len = allObjects.length;for ( ; i < len; i++) {allObjects[i].set('active', false);}this._discardActiveGroup();this._discardActiveObject();return this;},/*** Deactivates all objects and dispatches appropriate events* @return {fabric.Canvas} thisArg*/deactivateAllWithDispatch: function (e) {var activeObject = this.getActiveGroup() || this.getActiveObject();if (activeObject) {this.fire('before:selection:cleared', { target: activeObject, e: e });}this.deactivateAll();if (activeObject) {this.fire('selection:cleared', { e: e });}return this;},/*** Draws objects' controls (borders/controls)* @param {CanvasRenderingContext2D} ctx Context to render controls on*/drawControls: function(ctx) {var activeGroup = this.getActiveGroup();if (activeGroup) {this._drawGroupControls(ctx, activeGroup);}else {this._drawObjectsControls(ctx);}},/*** @private*/_drawGroupControls: function(ctx, activeGroup) {activeGroup._renderControls(ctx);},/*** @private*/_drawObjectsControls: function(ctx) {for (var i = 0, len = this._objects.length; i < len; ++i) {if (!this._objects[i] || !this._objects[i].active) {continue;}this._objects[i]._renderControls(ctx);this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];}}});// copying static properties manually to work around Opera's bug,// where "prototype" property is enumerable and overrides existing prototypefor (var prop in fabric.StaticCanvas) {if (prop !== 'prototype') {fabric.Canvas[prop] = fabric.StaticCanvas[prop];}}if (fabric.isTouchSupported) {/** @ignore */fabric.Canvas.prototype._setCursorFromEvent = function() { };}/*** @class fabric.Element* @alias fabric.Canvas* @deprecated Use {@link fabric.Canvas} instead.* @constructor*/fabric.Element = fabric.Canvas;})();(function() {var cursorOffset = {mt: 0, // ntr: 1, // nemr: 2, // ebr: 3, // semb: 4, // sbl: 5, // swml: 6, // wtl: 7 // nw},addListener = fabric.util.addListener,removeListener = fabric.util.removeListener;fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {/*** Map of cursor style values for each of the object controls* @private*/cursorMap: ['n-resize','ne-resize','e-resize','se-resize','s-resize','sw-resize','w-resize','nw-resize'],/*** Adds mouse listeners to canvas* @private*/_initEventListeners: function () {this._bindEvents();addListener(fabric.window, 'resize', this._onResize);// mouse eventsaddListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);addListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel);// touch eventsaddListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);if (typeof eventjs !== 'undefined' && 'add' in eventjs) {eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture);eventjs.add(this.upperCanvasEl, 'drag', this._onDrag);eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange);eventjs.add(this.upperCanvasEl, 'shake', this._onShake);eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress);}},/*** @private*/_bindEvents: function() {this._onMouseDown = this._onMouseDown.bind(this);this._onMouseMove = this._onMouseMove.bind(this);this._onMouseUp = this._onMouseUp.bind(this);this._onResize = this._onResize.bind(this);this._onGesture = this._onGesture.bind(this);this._onDrag = this._onDrag.bind(this);this._onShake = this._onShake.bind(this);this._onLongPress = this._onLongPress.bind(this);this._onOrientationChange = this._onOrientationChange.bind(this);this._onMouseWheel = this._onMouseWheel.bind(this);},/*** Removes all event listeners*/removeListeners: function() {removeListener(fabric.window, 'resize', this._onResize);removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel);removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);if (typeof eventjs !== 'undefined' && 'remove' in eventjs) {eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture);eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag);eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange);eventjs.remove(this.upperCanvasEl, 'shake', this._onShake);eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress);}},/*** @private* @param {Event} [e] Event object fired on Event.js gesture* @param {Event} [self] Inner Event object*/_onGesture: function(e, self) {this.__onTransformGesture && this.__onTransformGesture(e, self);},/*** @private* @param {Event} [e] Event object fired on Event.js drag* @param {Event} [self] Inner Event object*/_onDrag: function(e, self) {this.__onDrag && this.__onDrag(e, self);},/*** @private* @param {Event} [e] Event object fired on Event.js wheel event* @param {Event} [self] Inner Event object*/_onMouseWheel: function(e, self) {this.__onMouseWheel && this.__onMouseWheel(e, self);},/*** @private* @param {Event} [e] Event object fired on Event.js orientation change* @param {Event} [self] Inner Event object*/_onOrientationChange: function(e, self) {this.__onOrientationChange && this.__onOrientationChange(e, self);},/*** @private* @param {Event} [e] Event object fired on Event.js shake* @param {Event} [self] Inner Event object*/_onShake: function(e, self) {this.__onShake && this.__onShake(e, self);},/*** @private* @param {Event} [e] Event object fired on Event.js shake* @param {Event} [self] Inner Event object*/_onLongPress: function(e, self) {this.__onLongPress && this.__onLongPress(e, self);},/*** @private* @param {Event} e Event object fired on mousedown*/_onMouseDown: function (e) {this.__onMouseDown(e);addListener(fabric.document, 'touchend', this._onMouseUp);addListener(fabric.document, 'touchmove', this._onMouseMove);removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);if (e.type === 'touchstart') {// Unbind mousedown to prevent double triggers from touch devicesremoveListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);}else {addListener(fabric.document, 'mouseup', this._onMouseUp);addListener(fabric.document, 'mousemove', this._onMouseMove);}},/*** @private* @param {Event} e Event object fired on mouseup*/_onMouseUp: function (e) {this.__onMouseUp(e);removeListener(fabric.document, 'mouseup', this._onMouseUp);removeListener(fabric.document, 'touchend', this._onMouseUp);removeListener(fabric.document, 'mousemove', this._onMouseMove);removeListener(fabric.document, 'touchmove', this._onMouseMove);addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);if (e.type === 'touchend') {// Wait 400ms before rebinding mousedown to prevent double triggers// from touch devicesvar _this = this;setTimeout(function() {addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown);}, 400);}},/*** @private* @param {Event} e Event object fired on mousemove*/_onMouseMove: function (e) {!this.allowTouchScrolling && e.preventDefault && e.preventDefault();this.__onMouseMove(e);},/*** @private*/_onResize: function () {this.calcOffset();},/*** Decides whether the canvas should be redrawn in mouseup and mousedown events.* @private* @param {Object} target* @param {Object} pointer*/_shouldRender: function(target, pointer) {var activeObject = this.getActiveGroup() || this.getActiveObject();return !!((target && (target.isMoving ||target !== activeObject))||(!target && !!activeObject)||(!target && !activeObject && !this._groupSelector)||(pointer &&this._previousPointer &&this.selection && (pointer.x !== this._previousPointer.x ||pointer.y !== this._previousPointer.y)));},/*** Method that defines the actions when mouse is released on canvas.* The method resets the currentTransform parameters, store the image corner* position in the image object and render the canvas on top.* @private* @param {Event} e Event object fired on mouseup*/__onMouseUp: function (e) {var target;if (this.isDrawingMode && this._isCurrentlyDrawing) {this._onMouseUpInDrawingMode(e);return;}if (this._currentTransform) {this._finalizeCurrentTransform();target = this._currentTransform.target;}else {target = this.findTarget(e, true);}var shouldRender = this._shouldRender(target, this.getPointer(e));this._maybeGroupObjects(e);if (target) {target.isMoving = false;}shouldRender && this.renderAll();this._handleCursorAndEvent(e, target);},_handleCursorAndEvent: function(e, target) {this._setCursorFromEvent(e, target);// TODO: why are we doing this?var _this = this;setTimeout(function () {_this._setCursorFromEvent(e, target);}, 50);this.fire('mouse:up', { target: target, e: e });target && target.fire('mouseup', { e: e });},/*** @private*/_finalizeCurrentTransform: function() {var transform = this._currentTransform,target = transform.target;if (target._scaling) {target._scaling = false;}target.setCoords();// only fire :modified event if target coordinates were changed during mousedown-mouseupif (this.stateful && target.hasStateChanged()) {this.fire('object:modified', { target: target });target.fire('modified');}this._restoreOriginXY(target);},/*** @private* @param {Object} target Object to restore*/_restoreOriginXY: function(target) {if (this._previousOriginX && this._previousOriginY) {var originPoint = target.translateToOriginPoint(target.getCenterPoint(),this._previousOriginX,this._previousOriginY);target.originX = this._previousOriginX;target.originY = this._previousOriginY;target.left = originPoint.x;target.top = originPoint.y;this._previousOriginX = null;this._previousOriginY = null;}},/*** @private* @param {Event} e Event object fired on mousedown*/_onMouseDownInDrawingMode: function(e) {this._isCurrentlyDrawing = true;this.discardActiveObject(e).renderAll();if (this.clipTo) {fabric.util.clipContext(this, this.contextTop);}var ivt = fabric.util.invertTransform(this.viewportTransform),pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);this.freeDrawingBrush.onMouseDown(pointer);this.fire('mouse:down', { e: e });var target = this.findTarget(e);if (typeof target !== 'undefined') {target.fire('mousedown', { e: e, target: target });}},/*** @private* @param {Event} e Event object fired on mousemove*/_onMouseMoveInDrawingMode: function(e) {if (this._isCurrentlyDrawing) {var ivt = fabric.util.invertTransform(this.viewportTransform),pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);this.freeDrawingBrush.onMouseMove(pointer);}this.setCursor(this.freeDrawingCursor);this.fire('mouse:move', { e: e });var target = this.findTarget(e);if (typeof target !== 'undefined') {target.fire('mousemove', { e: e, target: target });}},/*** @private* @param {Event} e Event object fired on mouseup*/_onMouseUpInDrawingMode: function(e) {this._isCurrentlyDrawing = false;if (this.clipTo) {this.contextTop.restore();}this.freeDrawingBrush.onMouseUp();this.fire('mouse:up', { e: e });var target = this.findTarget(e);if (typeof target !== 'undefined') {target.fire('mouseup', { e: e, target: target });}},/*** Method that defines the actions when mouse is clic ked on canvas.* The method inits the currentTransform parameters and renders all the* canvas so the current image can be placed on the top canvas and the rest* in on the container one.* @private* @param {Event} e Event object fired on mousedown*/__onMouseDown: function (e) {// accept only left clicksvar isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;if (!isLeftClick && !fabric.isTouchSupported) {return;}if (this.isDrawingMode) {this._onMouseDownInDrawingMode(e);return;}// ignore if some object is being transformed at this momentif (this._currentTransform) {return;}var target = this.findTarget(e),pointer = this.getPointer(e, true);// save pointer for check in __onMouseUp eventthis._previousPointer = pointer;var shouldRender = this._shouldRender(target, pointer),shouldGroup = this._shouldGroup(e, target);if (this._shouldClearSelection(e, target)) {this._clearSelection(e, target, pointer);}else if (shouldGroup) {this._handleGrouping(e, target);target = this.getActiveGroup();}if (target && target.selectable && !shouldGroup) {this._beforeTransform(e, target);this._setupCurrentTransform(e, target);}// we must renderAll so that active image is placed on the top canvasshouldRender && this.renderAll();this.fire('mouse:down', { target: target, e: e });target && target.fire('mousedown', { e: e });},/*** @private*/_beforeTransform: function(e, target) {this.stateful && target.saveState();// determine if it's a drag or rotate caseif (target._findTargetCorner(this.getPointer(e))) {this.onBeforeScaleRotate(target);}if (target !== this.getActiveGroup() && target !== this.getActiveObject()) {this.deactivateAll();this.setActiveObject(target, e);}},/*** @private*/_clearSelection: function(e, target, pointer) {this.deactivateAllWithDispatch(e);if (target && target.selectable) {this.setActiveObject(target, e);}else if (this.selection) {this._groupSelector = {ex: pointer.x,ey: pointer.y,top: 0,left: 0};}},/*** @private* @param {Object} target Object for that origin is set to center*/_setOriginToCenter: function(target) {this._previousOriginX = this._currentTransform.target.originX;this._previousOriginY = this._currentTransform.target.originY;var center = target.getCenterPoint();target.originX = 'center';target.originY = 'center';target.left = center.x;target.top = center.y;this._currentTransform.left = target.left;this._currentTransform.top = target.top;},/*** @private* @param {Object} target Object for that center is set to origin*/_setCenterToOrigin: function(target) {var originPoint = target.translateToOriginPoint(target.getCenterPoint(),this._previousOriginX,this._previousOriginY);target.originX = this._previousOriginX;target.originY = this._previousOriginY;target.left = originPoint.x;target.top = originPoint.y;this._previousOriginX = null;this._previousOriginY = null;},/*** Method that defines the actions when mouse is hovering the canvas.* The currentTransform parameter will definde whether the user is rotating/scaling/translating* an image or neither of them (only hovering). A group selection is also possible and would cancel* all any other type of action.* In case of an image transformation only the top canvas will be rendered.* @private* @param {Event} e Event object fired on mousemove*/__onMouseMove: function (e) {var target, pointer;if (this.isDrawingMode) {this._onMouseMoveInDrawingMode(e);return;}if (typeof e.touches !== 'undefined' && e.touches.length > 1) {return;}var groupSelector = this._groupSelector;// We initially clicked in an empty area, so we draw a box for multiple selectionif (groupSelector) {pointer = this.getPointer(e, true);groupSelector.left = pointer.x - groupSelector.ex;groupSelector.top = pointer.y - groupSelector.ey;this.renderTop();}else if (!this._currentTransform) {target = this.findTarget(e);if (!target || target && !target.selectable) {this.setCursor(this.defaultCursor);}else {this._setCursorFromEvent(e, target);}}else {this._transformObject(e);}this.fire('mouse:move', { target: target, e: e });target && target.fire('mousemove', { e: e });},/*** @private* @param {Event} e Event fired on mousemove*/_transformObject: function(e) {var pointer = this.getPointer(e),transform = this._currentTransform;transform.reset = false,transform.target.isMoving = true;this._beforeScaleTransform(e, transform);this._performTransformAction(e, transform, pointer);this.renderAll();},/*** @private*/_performTransformAction: function(e, transform, pointer) {var x = pointer.x,y = pointer.y,target = transform.target,action = transform.action;if (action === 'rotate') {this._rotateObject(x, y);this._fire('rotating', target, e);}else if (action === 'scale') {this._onScale(e, transform, x, y);this._fire('scaling', target, e);}else if (action === 'scaleX') {this._scaleObject(x, y, 'x');this._fire('scaling', target, e);}else if (action === 'scaleY') {this._scaleObject(x, y, 'y');this._fire('scaling', target, e);}else {this._translateObject(x, y);this._fire('moving', target, e);this.setCursor(this.moveCursor);}},/*** @private*/_fire: function(eventName, target, e) {this.fire('object:' + eventName, { target: target, e: e });target.fire(eventName, { e: e });},/*** @private*/_beforeScaleTransform: function(e, transform) {if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') {var centerTransform = this._shouldCenterTransform(e, transform.target);// Switch from a normal resize to center-basedif ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) ||// Switch from center-based resize to normal one(!centerTransform && transform.originX === 'center' && transform.originY === 'center')) {this._resetCurrentTransform(e);transform.reset = true;}}},/*** @private*/_onScale: function(e, transform, x, y) {// rotate object only if shift key is not pressed// and if it is not a group we are transformingif ((e.shiftKey || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) {transform.currentAction = 'scale';this._scaleObject(x, y);}else {// Switch from a normal resize to proportionalif (!transform.reset && transform.currentAction === 'scale') {this._resetCurrentTransform(e, transform.target);}transform.currentAction = 'scaleEqually';this._scaleObject(x, y, 'equally');}},/*** Sets the cursor depending on where the canvas is being hovered.* Note: very buggy in Opera* @param {Event} e Event object* @param {Object} target Object that the mouse is hovering, if so.*/_setCursorFromEvent: function (e, target) {if (!target || !target.selectable) {this.setCursor(this.defaultCursor);return false;}else {var activeGroup = this.getActiveGroup(),// only show proper corner when group selection is not activecorner = target._findTargetCorner&& (!activeGroup || !activeGroup.contains(target))&& target._findTargetCorner(this.getPointer(e, true));if (!corner) {this.setCursor(target.hoverCursor || this.hoverCursor);}else {this._setCornerCursor(corner, target);}}return true;},/*** @private*/_setCornerCursor: function(corner, target) {if (corner in cursorOffset) {this.setCursor(this._getRotatedCornerCursor(corner, target));}else if (corner === 'mtr' && target.hasRotatingPoint) {this.setCursor(this.rotationCursor);}else {this.setCursor(this.defaultCursor);return false;}},/*** @private*/_getRotatedCornerCursor: function(corner, target) {var n = Math.round((target.getAngle() % 360) / 45);if (n < 0) {n += 8; // full circle ahead}n += cursorOffset[corner];// normalize n to be from 0 to 7n %= 8;return this.cursorMap[n];}});})();(function() {var min = Math.min,max = Math.max;fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {/*** @private* @param {Event} e Event object* @param {fabric.Object} target* @return {Boolean}*/_shouldGroup: function(e, target) {var activeObject = this.getActiveObject();return e.shiftKey &&(this.getActiveGroup() || (activeObject && activeObject !== target))&& this.selection;},/*** @private* @param {Event} e Event object* @param {fabric.Object} target*/_handleGrouping: function (e, target) {if (target === this.getActiveGroup()) {// if it's a group, find target again, this time skipping grouptarget = this.findTarget(e, true);// if even object is not found, bail outif (!target || target.isType('group')) {return;}}if (this.getActiveGroup()) {this._updateActiveGroup(target, e);}else {this._createActiveGroup(target, e);}if (this._activeGroup) {this._activeGroup.saveCoords();}},/*** @private*/_updateActiveGroup: function(target, e) {var activeGroup = this.getActiveGroup();if (activeGroup.contains(target)) {activeGroup.removeWithUpdate(target);this._resetObjectTransform(activeGroup);target.set('active', false);if (activeGroup.size() === 1) {// remove group alltogether if after removal it only contains 1 objectthis.discardActiveGroup(e);// activate last remaining objectthis.setActiveObject(activeGroup.item(0));return;}}else {activeGroup.addWithUpdate(target);this._resetObjectTransform(activeGroup);}this.fire('selection:created', { target: activeGroup, e: e });activeGroup.set('active', true);},/*** @private*/_createActiveGroup: function(target, e) {if (this._activeObject && target !== this._activeObject) {var group = this._createGroup(target);group.addWithUpdate();this.setActiveGroup(group);this._activeObject = null;this.fire('selection:created', { target: group, e: e });}target.set('active', true);},/*** @private* @param {Object} target*/_createGroup: function(target) {var objects = this.getObjects(),isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target),groupObjects = isActiveLower? [ this._activeObject, target ]: [ target, this._activeObject ];return new fabric.Group(groupObjects, {canvas: this});},/*** @private* @param {Event} e mouse event*/_groupSelectedObjects: function (e) {var group = this._collectObjects();// do not create group for 1 element onlyif (group.length === 1) {this.setActiveObject(group[0], e);}else if (group.length > 1) {group = new fabric.Group(group.reverse(), {canvas: this});group.addWithUpdate();this.setActiveGroup(group, e);group.saveCoords();this.fire('selection:created', { target: group });this.renderAll();}},/*** @private*/_collectObjects: function() {var group = [ ],currentObject,x1 = this._groupSelector.ex,y1 = this._groupSelector.ey,x2 = x1 + this._groupSelector.left,y2 = y1 + this._groupSelector.top,selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),isClick = x1 === x2 && y1 === y2;for (var i = this._objects.length; i--; ) {currentObject = this._objects[i];if (!currentObject || !currentObject.selectable || !currentObject.visible) {continue;}if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||currentObject.containsPoint(selectionX1Y1) ||currentObject.containsPoint(selectionX2Y2)) {currentObject.set('active', true);group.push(currentObject);// only add one object if it's a clickif (isClick) {break;}}}return group;},/*** @private*/_maybeGroupObjects: function(e) {if (this.selection && this._groupSelector) {this._groupSelectedObjects(e);}var activeGroup = this.getActiveGroup();if (activeGroup) {activeGroup.setObjectsCoords().setCoords();activeGroup.isMoving = false;this.setCursor(this.defaultCursor);}// clear selection and current transformationthis._groupSelector = null;this._currentTransform = null;}});})();fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {/*** Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately* @param {Object} [options] Options object* @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"* @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.* @param {Number} [options.multiplier=1] Multiplier to scale by* @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14* @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14* @param {Number} [options.width] Cropping width. Introduced in v1.2.14* @param {Number} [options.height] Cropping height. Introduced in v1.2.14* @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format* @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo}* @example <caption>Generate jpeg dataURL with lower quality</caption>* var dataURL = canvas.toDataURL({* format: 'jpeg',* quality: 0.8* });* @example <caption>Generate cropped png dataURL (clipping of canvas)</caption>* var dataURL = canvas.toDataURL({* format: 'png',* left: 100,* top: 100,* width: 200,* height: 200* });* @example <caption>Generate double scaled png dataURL</caption>* var dataURL = canvas.toDataURL({* format: 'png',* multiplier: 2* });*/toDataURL: function (options) {options || (options = { });var format = options.format || 'png',quality = options.quality || 1,multiplier = options.multiplier || 1,cropping = {left: options.left,top: options.top,width: options.width,height: options.height};if (multiplier !== 1) {return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier);}else {return this.__toDataURL(format, quality, cropping);}},/*** @private*/__toDataURL: function(format, quality, cropping) {this.renderAll(true);var canvasEl = this.upperCanvasEl || this.lowerCanvasEl,croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping);// to avoid common confusion https://github.com/kangax/fabric.js/issues/806if (format === 'jpg') {format = 'jpeg';}var data = (fabric.StaticCanvas.supports('toDataURLWithQuality'))? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality): (croppedCanvasEl || canvasEl).toDataURL('image/' + format);this.contextTop && this.clearContext(this.contextTop);this.renderAll();if (croppedCanvasEl) {croppedCanvasEl = null;}return data;},/*** @private*/__getCroppedCanvas: function(canvasEl, cropping) {var croppedCanvasEl,croppedCtx,shouldCrop = 'left' in cropping ||'top' in cropping ||'width' in cropping ||'height' in cropping;if (shouldCrop) {croppedCanvasEl = fabric.util.createCanvasElement();croppedCtx = croppedCanvasEl.getContext('2d');croppedCanvasEl.width = cropping.width || this.width;croppedCanvasEl.height = cropping.height || this.height;croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0);}return croppedCanvasEl;},/*** @private*/__toDataURLWithMultiplier: function(format, quality, cropping, multiplier) {var origWidth = this.getWidth(),origHeight = this.getHeight(),scaledWidth = origWidth * multiplier,scaledHeight = origHeight * multiplier,activeObject = this.getActiveObject(),activeGroup = this.getActiveGroup(),ctx = this.contextTop || this.contextContainer;if (multiplier > 1) {this.setWidth(scaledWidth).setHeight(scaledHeight);}ctx.scale(multiplier, multiplier);if (cropping.left) {cropping.left *= multiplier;}if (cropping.top) {cropping.top *= multiplier;}if (cropping.width) {cropping.width *= multiplier;}else if (multiplier < 1) {cropping.width = scaledWidth;}if (cropping.height) {cropping.height *= multiplier;}else if (multiplier < 1) {cropping.height = scaledHeight;}if (activeGroup) {// not removing group due to complications with restoring it with correct state afterwordsthis._tempRemoveBordersControlsFromGroup(activeGroup);}else if (activeObject && this.deactivateAll) {this.deactivateAll();}this.renderAll(true);var data = this.__toDataURL(format, quality, cropping);// restoring width, height for `renderAll` to draw// background properly (while context is scaled)this.width = origWidth;this.height = origHeight;ctx.scale(1 / multiplier, 1 / multiplier);this.setWidth(origWidth).setHeight(origHeight);if (activeGroup) {this._restoreBordersControlsOnGroup(activeGroup);}else if (activeObject && this.setActiveObject) {this.setActiveObject(activeObject);}this.contextTop && this.clearContext(this.contextTop);this.renderAll();return data;},/*** Exports canvas element to a dataurl image (allowing to change image size via multiplier).* @deprecated since 1.0.13* @param {String} format (png|jpeg)* @param {Number} multiplier* @param {Number} quality (0..1)* @return {String}*/toDataURLWithMultiplier: function (format, multiplier, quality) {return this.toDataURL({format: format,multiplier: multiplier,quality: quality});},/*** @private*/_tempRemoveBordersControlsFromGroup: function(group) {group.origHasControls = group.hasControls;group.origBorderColor = group.borderColor;group.hasControls = true;group.borderColor = 'rgba(0,0,0,0)';group.forEachObject(function(o) {o.origBorderColor = o.borderColor;o.borderColor = 'rgba(0,0,0,0)';});},/*** @private*/_restoreBordersControlsOnGroup: function(group) {group.hideControls = group.origHideControls;group.borderColor = group.origBorderColor;group.forEachObject(function(o) {o.borderColor = o.origBorderColor;delete o.origBorderColor;});}});fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {/*** Populates canvas with data from the specified dataless JSON.* JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON}* @deprecated since 1.2.2* @param {String|Object} json JSON string or object* @param {Function} callback Callback, invoked when json is parsed* and corresponding objects (e.g: {@link fabric.Image})* are initialized* @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.* @return {fabric.Canvas} instance* @chainable* @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization}*/loadFromDatalessJSON: function (json, callback, reviver) {return this.loadFromJSON(json, callback, reviver);},/*** Populates canvas with data from the specified JSON.* JSON format must conform to the one of {@link fabric.Canvas#toJSON}* @param {String|Object} json JSON string or object* @param {Function} callback Callback, invoked when json is parsed* and corresponding objects (e.g: {@link fabric.Image})* are initialized* @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.* @return {fabric.Canvas} instance* @chainable* @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization}* @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo}* @example <caption>loadFromJSON</caption>* canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));* @example <caption>loadFromJSON with reviver</caption>* canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) {* // `o` = json object* // `object` = fabric.Object instance* // ... do some stuff ...* });*/loadFromJSON: function (json, callback, reviver) {if (!json) {return;}// serialize if it wasn't alreadyvar serialized = (typeof json === 'string')? JSON.parse(json): json;this.clear();var _this = this;this._enlivenObjects(serialized.objects, function () {_this._setBgOverlay(serialized, callback);}, reviver);return this;},/*** @private* @param {Object} serialized Object with background and overlay information* @param {Function} callback Invoked after all background and overlay images/patterns loaded*/_setBgOverlay: function(serialized, callback) {var _this = this,loaded = {backgroundColor: false,overlayColor: false,backgroundImage: false,overlayImage: false};if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) {callback && callback();return;}var cbIfLoaded = function () {if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) {_this.renderAll();callback && callback();}};this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded);this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded);this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded);this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded);cbIfLoaded();},/*** @private* @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor)* @param {(Object|String)} value Value to set* @param {Object} loaded Set loaded property to true if property is set* @param {Object} callback Callback function to invoke after property is set*/__setBgOverlay: function(property, value, loaded, callback) {var _this = this;if (!value) {loaded[property] = true;return;}if (property === 'backgroundImage' || property === 'overlayImage') {fabric.Image.fromObject(value, function(img) {_this[property] = img;loaded[property] = true;callback && callback();});}else {this['set' + fabric.util.string.capitalize(property, true)](value, function() {loaded[property] = true;callback && callback();});}},/*** @private* @param {Array} objects* @param {Function} callback* @param {Function} [reviver]*/_enlivenObjects: function (objects, callback, reviver) {var _this = this;if (!objects || objects.length === 0) {callback && callback();return;}var renderOnAddRemove = this.renderOnAddRemove;this.renderOnAddRemove = false;fabric.util.enlivenObjects(objects, function(enlivenedObjects) {enlivenedObjects.forEach(function(obj, index) {_this.insertAt(obj, index, true);});_this.renderOnAddRemove = renderOnAddRemove;callback && callback();}, null, reviver);},/*** @private* @param {String} format* @param {Function} callback*/_toDataURL: function (format, callback) {this.clone(function (clone) {callback(clone.toDataURL(format));});},/*** @private* @param {String} format* @param {Number} multiplier* @param {Function} callback*/_toDataURLWithMultiplier: function (format, multiplier, callback) {this.clone(function (clone) {callback(clone.toDataURLWithMultiplier(format, multiplier));});},/*** Clones canvas instance* @param {Object} [callback] Receives cloned instance as a first argument* @param {Array} [properties] Array of properties to include in the cloned canvas and children*/clone: function (callback, properties) {var data = JSON.stringify(this.toJSON(properties));this.cloneWithoutData(function(clone) {clone.loadFromJSON(data, function() {callback && callback(clone);});});},/*** Clones canvas instance without cloning existing data.* This essentially copies canvas dimensions, clipping properties, etc.* but leaves data empty (so that you can populate it with your own)* @param {Object} [callback] Receives cloned instance as a first argument*/cloneWithoutData: function(callback) {var el = fabric.document.createElement('canvas');el.width = this.getWidth();el.height = this.getHeight();var clone = new fabric.Canvas(el);clone.clipTo = this.clipTo;if (this.backgroundImage) {clone.setBackgroundImage(this.backgroundImage.src, function() {clone.renderAll();callback && callback(clone);});clone.backgroundImageOpacity = this.backgroundImageOpacity;clone.backgroundImageStretch = this.backgroundImageStretch;}else {callback && callback(clone);}}});(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend,toFixed = fabric.util.toFixed,capitalize = fabric.util.string.capitalize,degreesToRadians = fabric.util.degreesToRadians,supportsLineDash = fabric.StaticCanvas.supports('setLineDash');if (fabric.Object) {return;}/*** Root object class from which all 2d shape classes inherit from* @class fabric.Object* @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects}* @see {@link fabric.Object#initialize} for constructor definition** @fires added* @fires removed** @fires selected* @fires modified* @fires rotating* @fires scaling* @fires moving** @fires mousedown* @fires mouseup*/fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ {/*** Retrieves object's {@link fabric.Object#clipTo|clipping function}* @method getClipTo* @memberOf fabric.Object.prototype* @return {Function}*//*** Sets object's {@link fabric.Object#clipTo|clipping function}* @method setClipTo* @memberOf fabric.Object.prototype* @param {Function} clipTo Clipping function* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix}* @method getTransformMatrix* @memberOf fabric.Object.prototype* @return {Array} transformMatrix*//*** Sets object's {@link fabric.Object#transformMatrix|transformMatrix}* @method setTransformMatrix* @memberOf fabric.Object.prototype* @param {Array} transformMatrix* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#visible|visible} state* @method getVisible* @memberOf fabric.Object.prototype* @return {Boolean} True if visible*//*** Sets object's {@link fabric.Object#visible|visible} state* @method setVisible* @memberOf fabric.Object.prototype* @param {Boolean} value visible value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#shadow|shadow}* @method getShadow* @memberOf fabric.Object.prototype* @return {Object} Shadow instance*//*** Retrieves object's {@link fabric.Object#stroke|stroke}* @method getStroke* @memberOf fabric.Object.prototype* @return {String} stroke value*//*** Sets object's {@link fabric.Object#stroke|stroke}* @method setStroke* @memberOf fabric.Object.prototype* @param {String} value stroke value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth}* @method getStrokeWidth* @memberOf fabric.Object.prototype* @return {Number} strokeWidth value*//*** Sets object's {@link fabric.Object#strokeWidth|strokeWidth}* @method setStrokeWidth* @memberOf fabric.Object.prototype* @param {Number} value strokeWidth value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#originX|originX}* @method getOriginX* @memberOf fabric.Object.prototype* @return {String} originX value*//*** Sets object's {@link fabric.Object#originX|originX}* @method setOriginX* @memberOf fabric.Object.prototype* @param {String} value originX value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#originY|originY}* @method getOriginY* @memberOf fabric.Object.prototype* @return {String} originY value*//*** Sets object's {@link fabric.Object#originY|originY}* @method setOriginY* @memberOf fabric.Object.prototype* @param {String} value originY value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#fill|fill}* @method getFill* @memberOf fabric.Object.prototype* @return {String} Fill value*//*** Sets object's {@link fabric.Object#fill|fill}* @method setFill* @memberOf fabric.Object.prototype* @param {String} value Fill value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#opacity|opacity}* @method getOpacity* @memberOf fabric.Object.prototype* @return {Number} Opacity value (0-1)*//*** Sets object's {@link fabric.Object#opacity|opacity}* @method setOpacity* @memberOf fabric.Object.prototype* @param {Number} value Opacity value (0-1)* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#angle|angle} (in degrees)* @method getAngle* @memberOf fabric.Object.prototype* @return {Number}*//*** Retrieves object's {@link fabric.Object#top|top position}* @method getTop* @memberOf fabric.Object.prototype* @return {Number} Top value (in pixels)*//*** Sets object's {@link fabric.Object#top|top position}* @method setTop* @memberOf fabric.Object.prototype* @param {Number} value Top value (in pixels)* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#left|left position}* @method getLeft* @memberOf fabric.Object.prototype* @return {Number} Left value (in pixels)*//*** Sets object's {@link fabric.Object#left|left position}* @method setLeft* @memberOf fabric.Object.prototype* @param {Number} value Left value (in pixels)* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#scaleX|scaleX} value* @method getScaleX* @memberOf fabric.Object.prototype* @return {Number} scaleX value*//*** Sets object's {@link fabric.Object#scaleX|scaleX} value* @method setScaleX* @memberOf fabric.Object.prototype* @param {Number} value scaleX value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#scaleY|scaleY} value* @method getScaleY* @memberOf fabric.Object.prototype* @return {Number} scaleY value*//*** Sets object's {@link fabric.Object#scaleY|scaleY} value* @method setScaleY* @memberOf fabric.Object.prototype* @param {Number} value scaleY value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#flipX|flipX} value* @method getFlipX* @memberOf fabric.Object.prototype* @return {Boolean} flipX value*//*** Sets object's {@link fabric.Object#flipX|flipX} value* @method setFlipX* @memberOf fabric.Object.prototype* @param {Boolean} value flipX value* @return {fabric.Object} thisArg* @chainable*//*** Retrieves object's {@link fabric.Object#flipY|flipY} value* @method getFlipY* @memberOf fabric.Object.prototype* @return {Boolean} flipY value*//*** Sets object's {@link fabric.Object#flipY|flipY} value* @method setFlipY* @memberOf fabric.Object.prototype* @param {Boolean} value flipY value* @return {fabric.Object} thisArg* @chainable*//*** Type of an object (rect, circle, path, etc.).* Note that this property is meant to be read-only and not meant to be modified.* If you modify, certain parts of Fabric (such as JSON loading) won't work correctly.* @type String* @default*/type: 'object',/*** Horizontal origin of transformation of an object (one of "left", "right", "center")* See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups* @type String* @default*/originX: 'left',/*** Vertical origin of transformation of an object (one of "top", "bottom", "center")* See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups* @type String* @default*/originY: 'top',/*** Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom}* @type Number* @default*/top: 0,/*** Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right}* @type Number* @default*/left: 0,/*** Object width* @type Number* @default*/width: 0,/*** Object height* @type Number* @default*/height: 0,/*** Object scale factor (horizontal)* @type Number* @default*/scaleX: 1,/*** Object scale factor (vertical)* @type Number* @default*/scaleY: 1,/*** When true, an object is rendered as flipped horizontally* @type Boolean* @default*/flipX: false,/*** When true, an object is rendered as flipped vertically* @type Boolean* @default*/flipY: false,/*** Opacity of an object* @type Number* @default*/opacity: 1,/*** Angle of rotation of an object (in degrees)* @type Number* @default*/angle: 0,/*** Size of object's controlling corners (in pixels)* @type Number* @default*/cornerSize: 12,/*** When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill)* @type Boolean* @default*/transparentCorners: true,/*** Default cursor value used when hovering over this object on canvas* @type String* @default*/hoverCursor: null,/*** Padding between object and its controlling borders (in pixels)* @type Number* @default*/padding: 0,/*** Color of controlling borders of an object (when it's active)* @type String* @default*/borderColor: 'rgba(102,153,255,0.75)',/*** Color of controlling corners of an object (when it's active)* @type String* @default*/cornerColor: 'rgba(102,153,255,0.5)',/*** When true, this object will use center point as the origin of transformation* when being scaled via the controls.* <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).* @since 1.3.4* @type Boolean* @default*/centeredScaling: false,/*** When true, this object will use center point as the origin of transformation* when being rotated via the controls.* <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).* @since 1.3.4* @type Boolean* @default*/centeredRotation: true,/*** Color of object's fill* @type String* @default*/fill: 'rgb(0,0,0)',/*** Fill rule used to fill an object* accepted values are nonzero, evenodd* <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead)* @type String* @default*/fillRule: 'nonzero',/*** Composite rule used for canvas globalCompositeOperation* @type String* @default*/globalCompositeOperation: 'source-over',/*** Background color of an object. Only works with text objects at the moment.* @type String* @default*/backgroundColor: '',/*** When defined, an object is rendered via stroke and this property specifies its color* @type String* @default*/stroke: null,/*** Width of a stroke used to render this object* @type Number* @default*/strokeWidth: 1,/*** Array specifying dash pattern of an object's stroke (stroke must be defined)* @type Array*/strokeDashArray: null,/*** Line endings style of an object's stroke (one of "butt", "round", "square")* @type String* @default*/strokeLineCap: 'butt',/*** Corner style of an object's stroke (one of "bevil", "round", "miter")* @type String* @default*/strokeLineJoin: 'miter',/*** Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke* @type Number* @default*/strokeMiterLimit: 10,/*** Shadow object representing shadow of this shape* @type fabric.Shadow* @default*/shadow: null,/*** Opacity of object's controlling borders when object is active and moving* @type Number* @default*/borderOpacityWhenMoving: 0.4,/*** Scale factor of object's controlling borders* @type Number* @default*/borderScaleFactor: 1,/*** Transform matrix (similar to SVG's transform matrix)* @type Array*/transformMatrix: null,/*** Minimum allowed scale value of an object* @type Number* @default*/minScaleLimit: 0.01,/*** When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection).* But events still fire on it.* @type Boolean* @default*/selectable: true,/*** When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4* @type Boolean* @default*/evented: true,/*** When set to `false`, an object is not rendered on canvas* @type Boolean* @default*/visible: true,/*** When set to `false`, object's controls are not displayed and can not be used to manipulate object* @type Boolean* @default*/hasControls: true,/*** When set to `false`, object's controlling borders are not rendered* @type Boolean* @default*/hasBorders: true,/*** When set to `false`, object's controlling rotating point will not be visible or selectable* @type Boolean* @default*/hasRotatingPoint: true,/*** Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`)* @type Number* @default*/rotatingPointOffset: 40,/*** When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box* @type Boolean* @default*/perPixelTargetFind: false,/*** When `false`, default object's values are not included in its serialization* @type Boolean* @default*/includeDefaultValues: true,/*** Function that determines clipping of an object (context is passed as a first argument)* Note that context origin is at the object's center point (not left/top corner)* @type Function*/clipTo: null,/*** When `true`, object horizontal movement is locked* @type Boolean* @default*/lockMovementX: false,/*** When `true`, object vertical movement is locked* @type Boolean* @default*/lockMovementY: false,/*** When `true`, object rotation is locked* @type Boolean* @default*/lockRotation: false,/*** When `true`, object horizontal scaling is locked* @type Boolean* @default*/lockScalingX: false,/*** When `true`, object vertical scaling is locked* @type Boolean* @default*/lockScalingY: false,/*** When `true`, object non-uniform scaling is locked* @type Boolean* @default*/lockUniScaling: false,/*** When `true`, object cannot be flipped by scaling into negative values* @type Boolean* @default*/lockScalingFlip: false,/*** List of properties to consider when checking if state* of an object is changed (fabric.Object#hasStateChanged)* as well as for history (undo/redo) purposes* @type Array*/stateProperties: ('top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor').split(' '),/*** Constructor* @param {Object} [options] Options object*/initialize: function(options) {if (options) {this.setOptions(options);}},/*** @private* @param {Object} [options] Options object*/_initGradient: function(options) {if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) {this.set('fill', new fabric.Gradient(options.fill));}},/*** @private* @param {Object} [options] Options object*/_initPattern: function(options) {if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) {this.set('fill', new fabric.Pattern(options.fill));}if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) {this.set('stroke', new fabric.Pattern(options.stroke));}},/*** @private* @param {Object} [options] Options object*/_initClipping: function(options) {if (!options.clipTo || typeof options.clipTo !== 'string') {return;}var functionBody = fabric.util.getFunctionBody(options.clipTo);if (typeof functionBody !== 'undefined') {this.clipTo = new Function('ctx', functionBody);}},/*** Sets object's properties from options* @param {Object} [options] Options object*/setOptions: function(options) {for (var prop in options) {this.set(prop, options[prop]);}this._initGradient(options);this._initPattern(options);this._initClipping(options);},/*** Transforms context when rendering an object* @param {CanvasRenderingContext2D} ctx Context* @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node*/transform: function(ctx, fromLeft) {var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint();ctx.translate(center.x, center.y);ctx.rotate(degreesToRadians(this.angle));ctx.scale(this.scaleX * (this.flipX ? -1 : 1),this.scaleY * (this.flipY ? -1 : 1));},/*** Returns an object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} Object representation of an instance*/toObject: function(propertiesToInclude) {var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,object = {type: this.type,originX: this.originX,originY: this.originY,left: toFixed(this.left, NUM_FRACTION_DIGITS),top: toFixed(this.top, NUM_FRACTION_DIGITS),width: toFixed(this.width, NUM_FRACTION_DIGITS),height: toFixed(this.height, NUM_FRACTION_DIGITS),fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill,stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke,strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS),strokeDashArray: this.strokeDashArray,strokeLineCap: this.strokeLineCap,strokeLineJoin: this.strokeLineJoin,strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS),scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS),scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS),angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS),flipX: this.flipX,flipY: this.flipY,opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS),shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow,visible: this.visible,clipTo: this.clipTo && String(this.clipTo),backgroundColor: this.backgroundColor,fillRule: this.fillRule,globalCompositeOperation: this.globalCompositeOperation};if (!this.includeDefaultValues) {object = this._removeDefaultValues(object);}fabric.util.populateWithProperties(this, object, propertiesToInclude);return object;},/*** Returns (dataless) object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} Object representation of an instance*/toDatalessObject: function(propertiesToInclude) {// will be overwritten by subclassesreturn this.toObject(propertiesToInclude);},/*** @private* @param {Object} object*/_removeDefaultValues: function(object) {var prototype = fabric.util.getKlass(object.type).prototype,stateProperties = prototype.stateProperties;stateProperties.forEach(function(prop) {if (object[prop] === prototype[prop]) {delete object[prop];}});return object;},/*** Returns a string representation of an instance* @return {String}*/toString: function() {return '#<fabric.' + capitalize(this.type) + '>';},/*** Basic getter* @param {String} property Property name* @return {Any} value of a property*/get: function(property) {return this[property];},/*** @private*/_setObject: function(obj) {for (var prop in obj) {this._set(prop, obj[prop]);}},/*** Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.* @param {String|Object} key Property name or object (if object, iterate over the object properties)* @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)* @return {fabric.Object} thisArg* @chainable*/set: function(key, value) {if (typeof key === 'object') {this._setObject(key);}else {if (typeof value === 'function' && key !== 'clipTo') {this._set(key, value(this.get(key)));}else {this._set(key, value);}}return this;},/*** @private* @param {String} key* @param {Any} value* @return {fabric.Object} thisArg*/_set: function(key, value) {var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY');if (shouldConstrainValue) {value = this._constrainScale(value);}if (key === 'scaleX' && value < 0) {this.flipX = !this.flipX;value *= -1;}else if (key === 'scaleY' && value < 0) {this.flipY = !this.flipY;value *= -1;}else if (key === 'width' || key === 'height') {this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2);}else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) {value = new fabric.Shadow(value);}this[key] = value;return this;},/*** Toggles specified property from `true` to `false` or from `false` to `true`* @param {String} property Property to toggle* @return {fabric.Object} thisArg* @chainable*/toggle: function(property) {var value = this.get(property);if (typeof value === 'boolean') {this.set(property, !value);}return this;},/*** Sets sourcePath of an object* @param {String} value Value to set sourcePath to* @return {fabric.Object} thisArg* @chainable*/setSourcePath: function(value) {this.sourcePath = value;return this;},/*** Retrieves viewportTransform from Object's canvas if possible* @method getViewportTransform* @memberOf fabric.Object.prototype* @return {Boolean} flipY value // TODO*/getViewportTransform: function() {if (this.canvas && this.canvas.viewportTransform) {return this.canvas.viewportTransform;}return [1, 0, 0, 1, 0, 0];},/*** Renders an object on a specified context* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Boolean} [noTransform] When true, context is not transformed*/render: function(ctx, noTransform) {// do not render if width/height are zeros or object is not visibleif ((this.width === 0 && this.height === 0) || !this.visible) {return;}ctx.save();//setup fill rule for current objectthis._setupCompositeOperation(ctx);if (!noTransform) {this.transform(ctx);}this._setStrokeStyles(ctx);this._setFillStyles(ctx);if (this.transformMatrix) {ctx.transform.apply(ctx, this.transformMatrix);}this._setOpacity(ctx);this._setShadow(ctx);this.clipTo && fabric.util.clipContext(this, ctx);this._render(ctx, noTransform);this.clipTo && ctx.restore();this._removeShadow(ctx);this._restoreCompositeOperation(ctx);ctx.restore();},/* @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_setOpacity: function(ctx) {if (this.group) {this.group._setOpacity(ctx);}ctx.globalAlpha *= this.opacity;},_setStrokeStyles: function(ctx) {if (this.stroke) {ctx.lineWidth = this.strokeWidth;ctx.lineCap = this.strokeLineCap;ctx.lineJoin = this.strokeLineJoin;ctx.miterLimit = this.strokeMiterLimit;ctx.strokeStyle = this.stroke.toLive? this.stroke.toLive(ctx, this): this.stroke;}},_setFillStyles: function(ctx) {if (this.fill) {ctx.fillStyle = this.fill.toLive? this.fill.toLive(ctx, this): this.fill;}},/*** Renders controls and borders for the object* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Boolean} [noTransform] When true, context is not transformed*/_renderControls: function(ctx, noTransform) {if (!this.active || noTransform) {return;}var vpt = this.getViewportTransform();ctx.save();var center;if (this.group) {center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt);ctx.translate(center.x, center.y);ctx.rotate(degreesToRadians(this.group.angle));}center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group);if (this.group) {center.x *= this.group.scaleX;center.y *= this.group.scaleY;}ctx.translate(center.x, center.y);ctx.rotate(degreesToRadians(this.angle));this.drawBorders(ctx);this.drawControls(ctx);ctx.restore();},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_setShadow: function(ctx) {if (!this.shadow) {return;}var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1,multY = (this.canvas && this.canvas.viewportTransform[3]) || 1;ctx.shadowColor = this.shadow.color;ctx.shadowBlur = this.shadow.blur * (multX + multY) * (this.scaleX + this.scaleY) / 4;ctx.shadowOffsetX = this.shadow.offsetX * multX * this.scaleX;ctx.shadowOffsetY = this.shadow.offsetY * multY * this.scaleY;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_removeShadow: function(ctx) {if (!this.shadow) {return;}ctx.shadowColor = '';ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderFill: function(ctx) {if (!this.fill) {return;}ctx.save();if (this.fill.gradientTransform) {var g = this.fill.gradientTransform;ctx.transform.apply(ctx, g);}if (this.fill.toLive) {ctx.translate(-this.width / 2 + this.fill.offsetX || 0,-this.height / 2 + this.fill.offsetY || 0);}if (this.fillRule === 'evenodd') {ctx.fill('evenodd');}else {ctx.fill();}ctx.restore();if (this.shadow && !this.shadow.affectStroke) {this._removeShadow(ctx);}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderStroke: function(ctx) {if (!this.stroke || this.strokeWidth === 0) {return;}ctx.save();if (this.strokeDashArray) {// Spec requires the concatenation of two copies the dash list when the number of elements is oddif (1 & this.strokeDashArray.length) {this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);}if (supportsLineDash) {ctx.setLineDash(this.strokeDashArray);this._stroke && this._stroke(ctx);}else {this._renderDashedStroke && this._renderDashedStroke(ctx);}ctx.stroke();}else {if (this.stroke.gradientTransform) {var g = this.stroke.gradientTransform;ctx.transform.apply(ctx, g);}this._stroke ? this._stroke(ctx) : ctx.stroke();}this._removeShadow(ctx);ctx.restore();},/*** Clones an instance* @param {Function} callback Callback is invoked with a clone as a first argument* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {fabric.Object} clone of an instance*/clone: function(callback, propertiesToInclude) {if (this.constructor.fromObject) {return this.constructor.fromObject(this.toObject(propertiesToInclude), callback);}return new fabric.Object(this.toObject(propertiesToInclude));},/*** Creates an instance of fabric.Image out of an object* @param {Function} callback callback, invoked with an instance as a first argument* @return {fabric.Object} thisArg*/cloneAsImage: function(callback) {var dataUrl = this.toDataURL();fabric.util.loadImage(dataUrl, function(img) {if (callback) {callback(new fabric.Image(img));}});return this;},/*** Converts an object into a data-url-like string* @param {Object} options Options object* @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"* @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.* @param {Number} [options.multiplier=1] Multiplier to scale by* @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14* @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14* @param {Number} [options.width] Cropping width. Introduced in v1.2.14* @param {Number} [options.height] Cropping height. Introduced in v1.2.14* @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format*/toDataURL: function(options) {options || (options = { });var el = fabric.util.createCanvasElement(),boundingRect = this.getBoundingRect();el.width = boundingRect.width;el.height = boundingRect.height;fabric.util.wrapElement(el, 'div');var canvas = new fabric.StaticCanvas(el);// to avoid common confusion https://github.com/kangax/fabric.js/issues/806if (options.format === 'jpg') {options.format = 'jpeg';}if (options.format === 'jpeg') {canvas.backgroundColor = '#fff';}var origParams = {active: this.get('active'),left: this.getLeft(),top: this.getTop()};this.set('active', false);this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center');var originalCanvas = this.canvas;canvas.add(this);var data = canvas.toDataURL(options);this.set(origParams).setCoords();this.canvas = originalCanvas;canvas.dispose();canvas = null;return data;},/*** Returns true if specified type is identical to the type of an instance* @param {String} type Type to check against* @return {Boolean}*/isType: function(type) {return this.type === type;},/*** Returns complexity of an instance* @return {Number} complexity of this instance*/complexity: function() {return 0;},/*** Returns a JSON representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} JSON*/toJSON: function(propertiesToInclude) {// delegate, not aliasreturn this.toObject(propertiesToInclude);},/*** Sets gradient (fill or stroke) of an object* <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0* @param {String} property Property name 'stroke' or 'fill'* @param {Object} [options] Options object* @param {String} [options.type] Type of gradient 'radial' or 'linear'* @param {Number} [options.x1=0] x-coordinate of start point* @param {Number} [options.y1=0] y-coordinate of start point* @param {Number} [options.x2=0] x-coordinate of end point* @param {Number} [options.y2=0] y-coordinate of end point* @param {Number} [options.r1=0] Radius of start point (only for radial gradients)* @param {Number} [options.r2=0] Radius of end point (only for radial gradients)* @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'}* @return {fabric.Object} thisArg* @chainable* @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo}* @example <caption>Set linear gradient</caption>* object.setGradient('fill', {* type: 'linear',* x1: -object.width / 2,* y1: 0,* x2: object.width / 2,* y2: 0,* colorStops: {* 0: 'red',* 0.5: '#005555',* 1: 'rgba(0,0,255,0.5)'* }* });* canvas.renderAll();* @example <caption>Set radial gradient</caption>* object.setGradient('fill', {* type: 'radial',* x1: 0,* y1: 0,* x2: 0,* y2: 0,* r1: object.width / 2,* r2: 10,* colorStops: {* 0: 'red',* 0.5: '#005555',* 1: 'rgba(0,0,255,0.5)'* }* });* canvas.renderAll();*/setGradient: function(property, options) {options || (options = { });var gradient = { colorStops: [] };gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear');gradient.coords = {x1: options.x1,y1: options.y1,x2: options.x2,y2: options.y2};if (options.r1 || options.r2) {gradient.coords.r1 = options.r1;gradient.coords.r2 = options.r2;}for (var position in options.colorStops) {var color = new fabric.Color(options.colorStops[position]);gradient.colorStops.push({offset: position,color: color.toRgb(),opacity: color.getAlpha()});}return this.set(property, fabric.Gradient.forObject(this, gradient));},/*** Sets pattern fill of an object* @param {Object} options Options object* @param {(String|HTMLImageElement)} options.source Pattern source* @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)* @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner* @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner* @return {fabric.Object} thisArg* @chainable* @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo}* @example <caption>Set pattern</caption>* fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) {* object.setPatternFill({* source: img,* repeat: 'repeat'* });* canvas.renderAll();* });*/setPatternFill: function(options) {return this.set('fill', new fabric.Pattern(options));},/*** Sets {@link fabric.Object#shadow|shadow} of an object* @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")* @param {String} [options.color=rgb(0,0,0)] Shadow color* @param {Number} [options.blur=0] Shadow blur* @param {Number} [options.offsetX=0] Shadow horizontal offset* @param {Number} [options.offsetY=0] Shadow vertical offset* @return {fabric.Object} thisArg* @chainable* @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo}* @example <caption>Set shadow with string notation</caption>* object.setShadow('2px 2px 10px rgba(0,0,0,0.2)');* canvas.renderAll();* @example <caption>Set shadow with object notation</caption>* object.setShadow({* color: 'red',* blur: 10,* offsetX: 20,* offsetY: 20* });* canvas.renderAll();*/setShadow: function(options) {return this.set('shadow', options ? new fabric.Shadow(options) : null);},/*** Sets "color" of an instance (alias of `set('fill', …)`)* @param {String} color Color value* @return {fabric.Object} thisArg* @chainable*/setColor: function(color) {this.set('fill', color);return this;},/*** Sets "angle" of an instance* @param {Number} angle Angle value (in degrees)* @return {fabric.Object} thisArg* @chainable*/setAngle: function(angle) {var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation;if (shouldCenterOrigin) {this._setOriginToCenter();}this.set('angle', angle);if (shouldCenterOrigin) {this._resetOrigin();}return this;},/*** Centers object horizontally on canvas to which it was added last.* You might need to call `setCoords` on an object after centering, to update controls area.* @return {fabric.Object} thisArg* @chainable*/centerH: function () {this.canvas.centerObjectH(this);return this;},/*** Centers object vertically on canvas to which it was added last.* You might need to call `setCoords` on an object after centering, to update controls area.* @return {fabric.Object} thisArg* @chainable*/centerV: function () {this.canvas.centerObjectV(this);return this;},/*** Centers object vertically and horizontally on canvas to which is was added last* You might need to call `setCoords` on an object after centering, to update controls area.* @return {fabric.Object} thisArg* @chainable*/center: function () {this.canvas.centerObject(this);return this;},/*** Removes object from canvas to which it was added last* @return {fabric.Object} thisArg* @chainable*/remove: function() {this.canvas.remove(this);return this;},/*** Returns coordinates of a pointer relative to an object* @param {Event} e Event to operate upon* @param {Object} [pointer] Pointer to operate upon (instead of event)* @return {Object} Coordinates of a pointer (x, y)*/getLocalPointer: function(e, pointer) {pointer = pointer || this.canvas.getPointer(e);var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top');return {x: pointer.x - objectLeftTop.x,y: pointer.y - objectLeftTop.y};},/*** Sets canvas globalCompositeOperation for specific object* custom composition operation for the particular object can be specifed using globalCompositeOperation property* @param {CanvasRenderingContext2D} ctx Rendering canvas context*/_setupCompositeOperation: function (ctx) {if (this.globalCompositeOperation) {this._prevGlobalCompositeOperation = ctx.globalCompositeOperation;ctx.globalCompositeOperation = this.globalCompositeOperation;}},/*** Restores previously saved canvas globalCompositeOperation after obeject rendering* @param {CanvasRenderingContext2D} ctx Rendering canvas context*/_restoreCompositeOperation: function (ctx) {if (this.globalCompositeOperation && this._prevGlobalCompositeOperation) {ctx.globalCompositeOperation = this._prevGlobalCompositeOperation;}}});fabric.util.createAccessors(fabric.Object);/*** Alias for {@link fabric.Object.prototype.setAngle}* @alias rotate -> setAngle* @memberof fabric.Object*/fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;extend(fabric.Object.prototype, fabric.Observable);/*** Defines the number of fraction digits to use when serializing object values.* You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc.* @static* @memberof fabric.Object* @constant* @type Number*/fabric.Object.NUM_FRACTION_DIGITS = 2;/*** Unique id used internally when creating SVG elements* @static* @memberof fabric.Object* @type Number*/fabric.Object.__uid = 0;})(typeof exports !== 'undefined' ? exports : this);(function() {var degreesToRadians = fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** Translates the coordinates from origin to center coordinates (based on the object's dimensions)* @param {fabric.Point} point The point which corresponds to the originX and originY params* @param {String} originX Horizontal origin: 'left', 'center' or 'right'* @param {String} originY Vertical origin: 'top', 'center' or 'bottom'* @return {fabric.Point}*/translateToCenterPoint: function(point, originX, originY) {var cx = point.x,cy = point.y,strokeWidth = this.stroke ? this.strokeWidth : 0;if (originX === 'left') {cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;}else if (originX === 'right') {cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;}if (originY === 'top') {cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;}else if (originY === 'bottom') {cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;}// Apply the reverse rotation to the point (it's already scaled properly)return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle));},/*** Translates the coordinates from center to origin coordinates (based on the object's dimensions)* @param {fabric.Point} center The point which corresponds to center of the object* @param {String} originX Horizontal origin: 'left', 'center' or 'right'* @param {String} originY Vertical origin: 'top', 'center' or 'bottom'* @return {fabric.Point}*/translateToOriginPoint: function(center, originX, originY) {var x = center.x,y = center.y,strokeWidth = this.stroke ? this.strokeWidth : 0;// Get the point coordinatesif (originX === 'left') {x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;}else if (originX === 'right') {x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;}if (originY === 'top') {y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;}else if (originY === 'bottom') {y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;}// Apply the rotation to the point (it's already scaled properly)return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle));},/*** Returns the real center coordinates of the object* @return {fabric.Point}*/getCenterPoint: function() {var leftTop = new fabric.Point(this.left, this.top);return this.translateToCenterPoint(leftTop, this.originX, this.originY);},/*** Returns the coordinates of the object based on center coordinates* @param {fabric.Point} point The point which corresponds to the originX and originY params* @return {fabric.Point}*/// getOriginPoint: function(center) {// return this.translateToOriginPoint(center, this.originX, this.originY);// },/*** Returns the coordinates of the object as if it has a different origin* @param {String} originX Horizontal origin: 'left', 'center' or 'right'* @param {String} originY Vertical origin: 'top', 'center' or 'bottom'* @return {fabric.Point}*/getPointByOrigin: function(originX, originY) {var center = this.getCenterPoint();return this.translateToOriginPoint(center, originX, originY);},/*** Returns the point in local coordinates* @param {fabric.Point} point The point relative to the global coordinate system* @param {String} originX Horizontal origin: 'left', 'center' or 'right'* @param {String} originY Vertical origin: 'top', 'center' or 'bottom'* @return {fabric.Point}*/toLocalPoint: function(point, originX, originY) {var center = this.getCenterPoint(),strokeWidth = this.stroke ? this.strokeWidth : 0,x, y;if (originX && originY) {if (originX === 'left') {x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;}else if (originX === 'right') {x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;}else {x = center.x;}if (originY === 'top') {y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;}else if (originY === 'bottom') {y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;}else {y = center.y;}}else {x = this.left;y = this.top;}return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x, y));},/*** Returns the point in global coordinates* @param {fabric.Point} The point relative to the local coordinate system* @return {fabric.Point}*/// toGlobalPoint: function(point) {// return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top));// },/*** Sets the position of the object taking into consideration the object's origin* @param {fabric.Point} pos The new position of the object* @param {String} originX Horizontal origin: 'left', 'center' or 'right'* @param {String} originY Vertical origin: 'top', 'center' or 'bottom'* @return {void}*/setPositionByOrigin: function(pos, originX, originY) {var center = this.translateToCenterPoint(pos, originX, originY),position = this.translateToOriginPoint(center, this.originX, this.originY);this.set('left', position.x);this.set('top', position.y);},/*** @param {String} to One of 'left', 'center', 'right'*/adjustPosition: function(to) {var angle = degreesToRadians(this.angle),hypotHalf = this.getWidth() / 2,xHalf = Math.cos(angle) * hypotHalf,yHalf = Math.sin(angle) * hypotHalf,hypotFull = this.getWidth(),xFull = Math.cos(angle) * hypotFull,yFull = Math.sin(angle) * hypotFull;if (this.originX === 'center' && to === 'left' ||this.originX === 'right' && to === 'center') {// move half leftthis.left -= xHalf;this.top -= yHalf;}else if (this.originX === 'left' && to === 'center' ||this.originX === 'center' && to === 'right') {// move half rightthis.left += xHalf;this.top += yHalf;}else if (this.originX === 'left' && to === 'right') {// move full rightthis.left += xFull;this.top += yFull;}else if (this.originX === 'right' && to === 'left') {// move full leftthis.left -= xFull;this.top -= yFull;}this.setCoords();this.originX = to;},/*** Sets the origin/position of the object to it's center point* @private* @return {void}*/_setOriginToCenter: function() {this._originalOriginX = this.originX;this._originalOriginY = this.originY;var center = this.getCenterPoint();this.originX = 'center';this.originY = 'center';this.left = center.x;this.top = center.y;},/*** Resets the origin/position of the object to it's original origin* @private* @return {void}*/_resetOrigin: function() {var originPoint = this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX = this._originalOriginX;this.originY = this._originalOriginY;this.left = originPoint.x;this.top = originPoint.y;this._originalOriginX = null;this._originalOriginY = null;},/*** @private*/_getLeftTopCoords: function() {return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center');}});})();(function() {var degreesToRadians = fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** Object containing coordinates of object's controls* @type Object* @default*/oCoords: null,/*** Checks if object intersects with an area formed by 2 points* @param {Object} pointTL top-left point of area* @param {Object} pointBR bottom-right point of area* @return {Boolean} true if object intersects with an area formed by 2 points*/intersectsWithRect: function(pointTL, pointBR) {var oCoords = this.oCoords,tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),br = new fabric.Point(oCoords.br.x, oCoords.br.y),intersection = fabric.Intersection.intersectPolygonRectangle([tl, tr, br, bl],pointTL,pointBR);return intersection.status === 'Intersection';},/*** Checks if object intersects with another object* @param {Object} other Object to test* @return {Boolean} true if object intersects with another object*/intersectsWithObject: function(other) {// extracts coordsfunction getCoords(oCoords) {return {tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y),tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y),bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y),br: new fabric.Point(oCoords.br.x, oCoords.br.y)};}var thisCoords = getCoords(this.oCoords),otherCoords = getCoords(other.oCoords),intersection = fabric.Intersection.intersectPolygonPolygon([thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl],[otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl]);return intersection.status === 'Intersection';},/*** Checks if object is fully contained within area of another object* @param {Object} other Object to test* @return {Boolean} true if object is fully contained within area of another object*/isContainedWithinObject: function(other) {var boundingRect = other.getBoundingRect(),point1 = new fabric.Point(boundingRect.left, boundingRect.top),point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height);return this.isContainedWithinRect(point1, point2);},/*** Checks if object is fully contained within area formed by 2 points* @param {Object} pointTL top-left point of area* @param {Object} pointBR bottom-right point of area* @return {Boolean} true if object is fully contained within area formed by 2 points*/isContainedWithinRect: function(pointTL, pointBR) {var boundingRect = this.getBoundingRect();return (boundingRect.left >= pointTL.x &&boundingRect.left + boundingRect.width <= pointBR.x &&boundingRect.top >= pointTL.y &&boundingRect.top + boundingRect.height <= pointBR.y);},/*** Checks if point is inside the object* @param {fabric.Point} point Point to check against* @return {Boolean} true if point is inside the object*/containsPoint: function(point) {var lines = this._getImageLines(this.oCoords),xPoints = this._findCrossPoints(point, lines);// if xPoints is odd then point is inside the objectreturn (xPoints !== 0 && xPoints % 2 === 1);},/*** Method that returns an object with the object edges in it, given the coordinates of the corners* @private* @param {Object} oCoords Coordinates of the object corners*/_getImageLines: function(oCoords) {return {topline: {o: oCoords.tl,d: oCoords.tr},rightline: {o: oCoords.tr,d: oCoords.br},bottomline: {o: oCoords.br,d: oCoords.bl},leftline: {o: oCoords.bl,d: oCoords.tl}};},/*** Helper method to determine how many cross points are between the 4 object edges* and the horizontal line determined by a point on canvas* @private* @param {fabric.Point} point Point to check* @param {Object} oCoords Coordinates of the object being evaluated*/_findCrossPoints: function(point, oCoords) {var b1, b2, a1, a2, xi, yi,xcount = 0,iLine;for (var lineKey in oCoords) {iLine = oCoords[lineKey];// optimisation 1: line below point. no crossif ((iLine.o.y < point.y) && (iLine.d.y < point.y)) {continue;}// optimisation 2: line above point. no crossif ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) {continue;}// optimisation 3: vertical line caseif ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) {xi = iLine.o.x;yi = point.y;}// calculate the intersection pointelse {b1 = 0;b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x);a1 = point.y - b1 * point.x;a2 = iLine.o.y - b2 * iLine.o.x;xi = - (a1 - a2) / (b1 - b2);yi = a1 + b1 * xi;}// dont count xi < point.x casesif (xi >= point.x) {xcount += 1;}// optimisation 4: specific for square imagesif (xcount === 2) {break;}}return xcount;},/*** Returns width of an object's bounding rectangle* @deprecated since 1.0.4* @return {Number} width value*/getBoundingRectWidth: function() {return this.getBoundingRect().width;},/*** Returns height of an object's bounding rectangle* @deprecated since 1.0.4* @return {Number} height value*/getBoundingRectHeight: function() {return this.getBoundingRect().height;},/*** Returns coordinates of object's bounding rectangle (left, top, width, height)* @return {Object} Object with left, top, width, height properties*/getBoundingRect: function() {this.oCoords || this.setCoords();var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x],minX = fabric.util.array.min(xCoords),maxX = fabric.util.array.max(xCoords),width = Math.abs(minX - maxX),yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y],minY = fabric.util.array.min(yCoords),maxY = fabric.util.array.max(yCoords),height = Math.abs(minY - maxY);return {left: minX,top: minY,width: width,height: height};},/*** Returns width of an object* @return {Number} width value*/getWidth: function() {return this.width * this.scaleX;},/*** Returns height of an object* @return {Number} height value*/getHeight: function() {return this.height * this.scaleY;},/*** Makes sure the scale is valid and modifies it if necessary* @private* @param {Number} value* @return {Number}*/_constrainScale: function(value) {if (Math.abs(value) < this.minScaleLimit) {if (value < 0) {return -this.minScaleLimit;}else {return this.minScaleLimit;}}return value;},/*** Scales an object (equally by x and y)* @param {Number} value Scale factor* @return {fabric.Object} thisArg* @chainable*/scale: function(value) {value = this._constrainScale(value);if (value < 0) {this.flipX = !this.flipX;this.flipY = !this.flipY;value *= -1;}this.scaleX = value;this.scaleY = value;this.setCoords();return this;},/*** Scales an object to a given width, with respect to bounding box (scaling by x/y equally)* @param {Number} value New width value* @return {fabric.Object} thisArg* @chainable*/scaleToWidth: function(value) {// adjust to bounding rect factor so that rotated shapes would fit as wellvar boundingRectFactor = this.getBoundingRectWidth() / this.getWidth();return this.scale(value / this.width / boundingRectFactor);},/*** Scales an object to a given height, with respect to bounding box (scaling by x/y equally)* @param {Number} value New height value* @return {fabric.Object} thisArg* @chainable*/scaleToHeight: function(value) {// adjust to bounding rect factor so that rotated shapes would fit as wellvar boundingRectFactor = this.getBoundingRectHeight() / this.getHeight();return this.scale(value / this.height / boundingRectFactor);},/*** Sets corner position coordinates based on current angle, width and height* See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords* @return {fabric.Object} thisArg* @chainable*/setCoords: function() {var theta = degreesToRadians(this.angle),vpt = this.getViewportTransform(),f = function (p) {return fabric.util.transformPoint(p, vpt);},p = this._calculateCurrentDimensions(false),currentWidth = p.x, currentHeight = p.y;// If width is negative, make postive. Fixes path selection issueif (currentWidth < 0) {currentWidth = Math.abs(currentWidth);}var _hypotenuse = Math.sqrt(Math.pow(currentWidth / 2, 2) +Math.pow(currentHeight / 2, 2)),_angle = Math.atan(isFinite(currentHeight / currentWidth)? currentHeight / currentWidth: 0),// offset added for rotate and scale actionsoffsetX = Math.cos(_angle + theta) * _hypotenuse,offsetY = Math.sin(_angle + theta) * _hypotenuse,sinTh = Math.sin(theta),cosTh = Math.cos(theta),coords = this.getCenterPoint(),wh = new fabric.Point(currentWidth, currentHeight),_tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY),_tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)),bl = f(new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh))),br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))),tl = f(_tl),tr = f(_tr),ml = new fabric.Point((tl.x + bl.x)/2, (tl.y + bl.y)/2),mt = new fabric.Point((tr.x + tl.x)/2, (tr.y + tl.y)/2),mr = new fabric.Point((br.x + tr.x)/2, (br.y + tr.y)/2),mb = new fabric.Point((br.x + bl.x)/2, (br.y + bl.y)/2),mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset);// debugging/* setTimeout(function() {canvas.contextTop.fillStyle = 'green';canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);canvas.contextTop.fillRect(br.x, br.y, 3, 3);canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);}, 50); */this.oCoords = {// cornerstl: tl, tr: tr, br: br, bl: bl,// middleml: ml, mt: mt, mr: mr, mb: mb,// rotating pointmtr: mtr};// set coordinates of the draggable boxes in the corners used to scale/rotate the imagethis._setCornerCoords && this._setCornerCoords();return this;}});})();fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** Moves an object to the bottom of the stack of drawn objects* @return {fabric.Object} thisArg* @chainable*/sendToBack: function() {if (this.group) {fabric.StaticCanvas.prototype.sendToBack.call(this.group, this);}else {this.canvas.sendToBack(this);}return this;},/*** Moves an object to the top of the stack of drawn objects* @return {fabric.Object} thisArg* @chainable*/bringToFront: function() {if (this.group) {fabric.StaticCanvas.prototype.bringToFront.call(this.group, this);}else {this.canvas.bringToFront(this);}return this;},/*** Moves an object down in stack of drawn objects* @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object* @return {fabric.Object} thisArg* @chainable*/sendBackwards: function(intersecting) {if (this.group) {fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting);}else {this.canvas.sendBackwards(this, intersecting);}return this;},/*** Moves an object up in stack of drawn objects* @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object* @return {fabric.Object} thisArg* @chainable*/bringForward: function(intersecting) {if (this.group) {fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting);}else {this.canvas.bringForward(this, intersecting);}return this;},/*** Moves an object to specified level in stack of drawn objects* @param {Number} index New position of object* @return {fabric.Object} thisArg* @chainable*/moveTo: function(index) {if (this.group) {fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index);}else {this.canvas.moveTo(this, index);}return this;}});/* _TO_SVG_START_ */fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** Returns styles-string for svg-export* @return {String}*/getSvgStyles: function() {var fill = this.fill? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill): 'none',fillRule = this.fillRule,stroke = this.stroke? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke): 'none',strokeWidth = this.strokeWidth ? this.strokeWidth : '0',strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '',strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt',strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter',strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4',opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1',visibility = this.visible ? '' : ' visibility: hidden;',filter = this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : '';return ['stroke: ', stroke, '; ','stroke-width: ', strokeWidth, '; ','stroke-dasharray: ', strokeDashArray, '; ','stroke-linecap: ', strokeLineCap, '; ','stroke-linejoin: ', strokeLineJoin, '; ','stroke-miterlimit: ', strokeMiterLimit, '; ','fill: ', fill, '; ','fill-rule: ', fillRule, '; ','opacity: ', opacity, ';',filter,visibility].join('');},/*** Returns transform-string for svg-export* @return {String}*/getSvgTransform: function() {if (this.group && this.group.type === 'path-group') {return '';}var toFixed = fabric.util.toFixed,angle = this.getAngle(),vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0],center = fabric.util.transformPoint(this.getCenterPoint(), vpt),NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,translatePart = this.type === 'path-group' ? '' : 'translate(' +toFixed(center.x, NUM_FRACTION_DIGITS) +' ' +toFixed(center.y, NUM_FRACTION_DIGITS) +')',anglePart = angle !== 0? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')'): '',scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1)? '' :(' scale(' +toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) +' ' +toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) +')'),addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0,flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '',addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0,flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : '';return [translatePart, anglePart, scalePart, flipXPart, flipYPart].join('');},/*** Returns transform-string for svg-export from the transform matrix of single elements* @return {String}*/getSvgTransformMatrix: function() {return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : '';},/*** @private*/_createBaseSVGMarkup: function() {var markup = [ ];if (this.fill && this.fill.toLive) {markup.push(this.fill.toSVG(this, false));}if (this.stroke && this.stroke.toLive) {markup.push(this.stroke.toSVG(this, false));}if (this.shadow) {markup.push(this.shadow.toSVG(this));}return markup;}});/* _TO_SVG_END_ *//*Depends on `stateProperties`*/fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** Returns true if object state (one of its state properties) was changed* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called*/hasStateChanged: function() {return this.stateProperties.some(function(prop) {return this.get(prop) !== this.originalState[prop];}, this);},/*** Saves state of an object* @param {Object} [options] Object with additional `stateProperties` array to include when saving state* @return {fabric.Object} thisArg*/saveState: function(options) {this.stateProperties.forEach(function(prop) {this.originalState[prop] = this.get(prop);}, this);if (options && options.stateProperties) {options.stateProperties.forEach(function(prop) {this.originalState[prop] = this.get(prop);}, this);}return this;},/*** Setups state of an object* @return {fabric.Object} thisArg*/setupState: function() {this.originalState = { };this.saveState();return this;}});(function() {var degreesToRadians = fabric.util.degreesToRadians,//jscs:disable requireCamelCaseOrUpperCaseIdentifiersisVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; };//jscs:enable requireCamelCaseOrUpperCaseIdentifiersfabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** The object interactivity controls.* @private*/_controlsVisibility: null,/*** Determines which corner has been clicked* @private* @param {Object} pointer The pointer indicating the mouse position* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found*/_findTargetCorner: function(pointer) {if (!this.hasControls || !this.active) {return false;}var ex = pointer.x,ey = pointer.y,xPoints,lines;for (var i in this.oCoords) {if (!this.isControlVisible(i)) {continue;}if (i === 'mtr' && !this.hasRotatingPoint) {continue;}if (this.get('lockUniScaling') &&(i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {continue;}lines = this._getImageLines(this.oCoords[i].corner);// debugging// canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);// canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);// canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);// canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);// canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);// canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);// canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);// canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);xPoints = this._findCrossPoints({ x: ex, y: ey }, lines);if (xPoints !== 0 && xPoints % 2 === 1) {this.__corner = i;return i;}}return false;},/*** Sets the coordinates of the draggable boxes in the corners of* the image used to scale/rotate it.* @private*/_setCornerCoords: function() {var coords = this.oCoords,newTheta = degreesToRadians(45 - this.angle),cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),x, y;for (var point in coords) {x = coords[point].x;y = coords[point].y;coords[point].corner = {tl: {x: x - sinHalfOffset,y: y - cosHalfOffset},tr: {x: x + cosHalfOffset,y: y - sinHalfOffset},bl: {x: x - cosHalfOffset,y: y + sinHalfOffset},br: {x: x + sinHalfOffset,y: y + cosHalfOffset}};}},_calculateCurrentDimensions: function(shouldTransform) {var vpt = this.getViewportTransform(),strokeWidth = this.strokeWidth,w = this.width,h = this.height,capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square',vLine = this.type === 'line' && this.width === 0,hLine = this.type === 'line' && this.height === 0,sLine = vLine || hLine,strokeW = (capped && hLine) || !sLine,strokeH = (capped && vLine) || !sLine;if (vLine) {w = strokeWidth;}else if (hLine) {h = strokeWidth;}if (strokeW) {w += (w < 0 ? -strokeWidth : strokeWidth);}if (strokeH) {h += (h < 0 ? -strokeWidth : strokeWidth);}w = w * this.scaleX + 2 * this.padding;h = h * this.scaleY + 2 * this.padding;if (shouldTransform) {return fabric.util.transformPoint(new fabric.Point(w, h), vpt, true);}return { x: w, y: h };},/*** Draws borders of an object's bounding box.* Requires public properties: width, height* Requires public options: padding, borderColor* @param {CanvasRenderingContext2D} ctx Context to draw on* @return {fabric.Object} thisArg* @chainable*/drawBorders: function(ctx) {if (!this.hasBorders) {return this;}ctx.save();ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;ctx.strokeStyle = this.borderColor;ctx.lineWidth = 1 / this.borderScaleFactor;var wh = this._calculateCurrentDimensions(true),width = wh.x,height = wh.y;if (this.group) {width = width * this.group.scaleX;height = height * this.group.scaleY;}ctx.strokeRect(~~(-(width / 2)) - 0.5, // offset needed to make lines look sharper~~(-(height / 2)) - 0.5,~~(width) + 1, // double offset needed to make lines look sharper~~(height) + 1);if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) {var rotateHeight = -height / 2;ctx.beginPath();ctx.moveTo(0, rotateHeight);ctx.lineTo(0, rotateHeight - this.rotatingPointOffset);ctx.closePath();ctx.stroke();}ctx.restore();return this;},/*** Draws corners of an object's bounding box.* Requires public properties: width, height* Requires public options: cornerSize, padding* @param {CanvasRenderingContext2D} ctx Context to draw on* @return {fabric.Object} thisArg* @chainable*/drawControls: function(ctx) {if (!this.hasControls) {return this;}var wh = this._calculateCurrentDimensions(true),width = wh.x,height = wh.y,left = -(width / 2),top = -(height / 2),scaleOffset = this.cornerSize / 2,methodName = this.transparentCorners ? 'strokeRect' : 'fillRect';ctx.save();ctx.lineWidth = 1;ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;ctx.strokeStyle = ctx.fillStyle = this.cornerColor;// top-leftthis._drawControl('tl', ctx, methodName,left - scaleOffset,top - scaleOffset);// top-rightthis._drawControl('tr', ctx, methodName,left + width - scaleOffset,top - scaleOffset);// bottom-leftthis._drawControl('bl', ctx, methodName,left - scaleOffset,top + height - scaleOffset);// bottom-rightthis._drawControl('br', ctx, methodName,left + width - scaleOffset,top + height - scaleOffset);if (!this.get('lockUniScaling')) {// middle-topthis._drawControl('mt', ctx, methodName,left + width/2 - scaleOffset,top - scaleOffset);// middle-bottomthis._drawControl('mb', ctx, methodName,left + width/2 - scaleOffset,top + height - scaleOffset);// middle-rightthis._drawControl('mr', ctx, methodName,left + width - scaleOffset,top + height/2 - scaleOffset);// middle-leftthis._drawControl('ml', ctx, methodName,left - scaleOffset,top + height/2 - scaleOffset);}// middle-top-rotateif (this.hasRotatingPoint) {this._drawControl('mtr', ctx, methodName,left + width/2 - scaleOffset,top - this.rotatingPointOffset - scaleOffset);}ctx.restore();return this;},/*** @private*/_drawControl: function(control, ctx, methodName, left, top) {if (!this.isControlVisible(control)) {return;}var size = this.cornerSize;isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size);ctx[methodName](left, top, size, size);},/*** Returns true if the specified control is visible, false otherwise.* @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.* @returns {Boolean} true if the specified control is visible, false otherwise*/isControlVisible: function(controlName) {return this._getControlsVisibility()[controlName];},/*** Sets the visibility of the specified control.* @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.* @param {Boolean} visible true to set the specified control visible, false otherwise* @return {fabric.Object} thisArg* @chainable*/setControlVisible: function(controlName, visible) {this._getControlsVisibility()[controlName] = visible;return this;},/*** Sets the visibility state of object controls.* @param {Object} [options] Options object* @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it* @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it* @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it* @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it* @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it* @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it* @param {Boolean} [options.tl] true to enable the top-left control, false to disable it* @param {Boolean} [options.tr] true to enable the top-right control, false to disable it* @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it* @return {fabric.Object} thisArg* @chainable*/setControlsVisibility: function(options) {options || (options = { });for (var p in options) {this.setControlVisible(p, options[p]);}return this;},/*** Returns the instance of the control visibility set for this object.* @private* @returns {Object}*/_getControlsVisibility: function() {if (!this._controlsVisibility) {this._controlsVisibility = {tl: true,tr: true,br: true,bl: true,ml: true,mt: true,mr: true,mb: true,mtr: true};}return this._controlsVisibility;}});})();fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {/*** Animation duration (in ms) for fx* methods* @type Number* @default*/FX_DURATION: 500,/*** Centers object horizontally with animation.* @param {fabric.Object} object Object to center* @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties* @param {Function} [callbacks.onComplete] Invoked on completion* @param {Function} [callbacks.onChange] Invoked on every step of animation* @return {fabric.Canvas} thisArg* @chainable*/fxCenterObjectH: function (object, callbacks) {callbacks = callbacks || { };var empty = function() { },onComplete = callbacks.onComplete || empty,onChange = callbacks.onChange || empty,_this = this;fabric.util.animate({startValue: object.get('left'),endValue: this.getCenter().left,duration: this.FX_DURATION,onChange: function(value) {object.set('left', value);_this.renderAll();onChange();},onComplete: function() {object.setCoords();onComplete();}});return this;},/*** Centers object vertically with animation.* @param {fabric.Object} object Object to center* @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties* @param {Function} [callbacks.onComplete] Invoked on completion* @param {Function} [callbacks.onChange] Invoked on every step of animation* @return {fabric.Canvas} thisArg* @chainable*/fxCenterObjectV: function (object, callbacks) {callbacks = callbacks || { };var empty = function() { },onComplete = callbacks.onComplete || empty,onChange = callbacks.onChange || empty,_this = this;fabric.util.animate({startValue: object.get('top'),endValue: this.getCenter().top,duration: this.FX_DURATION,onChange: function(value) {object.set('top', value);_this.renderAll();onChange();},onComplete: function() {object.setCoords();onComplete();}});return this;},/*** Same as `fabric.Canvas#remove` but animated* @param {fabric.Object} object Object to remove* @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties* @param {Function} [callbacks.onComplete] Invoked on completion* @param {Function} [callbacks.onChange] Invoked on every step of animation* @return {fabric.Canvas} thisArg* @chainable*/fxRemove: function (object, callbacks) {callbacks = callbacks || { };var empty = function() { },onComplete = callbacks.onComplete || empty,onChange = callbacks.onChange || empty,_this = this;fabric.util.animate({startValue: object.get('opacity'),endValue: 0,duration: this.FX_DURATION,onStart: function() {object.set('active', false);},onChange: function(value) {object.set('opacity', value);_this.renderAll();onChange();},onComplete: function () {_this.remove(object);onComplete();}});return this;}});fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** Animates object's properties* @param {String|Object} property Property to animate (if string) or properties to animate (if object)* @param {Number|Object} value Value to animate property to (if string was given first) or options object* @return {fabric.Object} thisArg* @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation}* @chainable** As object — multiple properties** object.animate({ left: ..., top: ... });* object.animate({ left: ..., top: ... }, { duration: ... });** As string — one property** object.animate('left', ...);* object.animate('left', { duration: ... });**/animate: function() {if (arguments[0] && typeof arguments[0] === 'object') {var propsToAnimate = [ ], prop, skipCallbacks;for (prop in arguments[0]) {propsToAnimate.push(prop);}for (var i = 0, len = propsToAnimate.length; i < len; i++) {prop = propsToAnimate[i];skipCallbacks = i !== len - 1;this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);}}else {this._animate.apply(this, arguments);}return this;},/*** @private* @param {String} property Property to animate* @param {String} to Value to animate to* @param {Object} [options] Options object* @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked*/_animate: function(property, to, options, skipCallbacks) {var _this = this, propPair;to = to.toString();if (!options) {options = { };}else {options = fabric.util.object.clone(options);}if (~property.indexOf('.')) {propPair = property.split('.');}var currentValue = propPair? this.get(propPair[0])[propPair[1]]: this.get(property);if (!('from' in options)) {options.from = currentValue;}if (~to.indexOf('=')) {to = currentValue + parseFloat(to.replace('=', ''));}else {to = parseFloat(to);}fabric.util.animate({startValue: options.from,endValue: to,byValue: options.by,easing: options.easing,duration: options.duration,abort: options.abort && function() {return options.abort.call(_this);},onChange: function(value) {if (propPair) {_this[propPair[0]][propPair[1]] = value;}else {_this.set(property, value);}if (skipCallbacks) {return;}options.onChange && options.onChange();},onComplete: function() {if (skipCallbacks) {return;}_this.setCoords();options.onComplete && options.onComplete();}});}});(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend,coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 },supportsLineDash = fabric.StaticCanvas.supports('setLineDash');if (fabric.Line) {fabric.warn('fabric.Line is already defined');return;}/*** Line class* @class fabric.Line* @extends fabric.Object* @see {@link fabric.Line#initialize} for constructor definition*/fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ {/*** Type of an object* @type String* @default*/type: 'line',/*** x value or first line edge* @type Number* @default*/x1: 0,/*** y value or first line edge* @type Number* @default*/y1: 0,/*** x value or second line edge* @type Number* @default*/x2: 0,/*** y value or second line edge* @type Number* @default*/y2: 0,/*** Constructor* @param {Array} [points] Array of points* @param {Object} [options] Options object* @return {fabric.Line} thisArg*/initialize: function(points, options) {options = options || { };if (!points) {points = [0, 0, 0, 0];}this.callSuper('initialize', options);this.set('x1', points[0]);this.set('y1', points[1]);this.set('x2', points[2]);this.set('y2', points[3]);this._setWidthHeight(options);},/*** @private* @param {Object} [options] Options*/_setWidthHeight: function(options) {options || (options = { });this.width = Math.abs(this.x2 - this.x1);this.height = Math.abs(this.y2 - this.y1);this.left = 'left' in options? options.left: this._getLeftToOriginX();this.top = 'top' in options? options.top: this._getTopToOriginY();},/*** @private* @param {String} key* @param {Any} value*/_set: function(key, value) {this.callSuper('_set', key, value);if (typeof coordProps[key] !== 'undefined') {this._setWidthHeight();}return this;},/*** @private* @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line.*/_getLeftToOriginX: makeEdgeToOriginGetter({ // property namesorigin: 'originX',axis1: 'x1',axis2: 'x2',dimension: 'width'},{ // possible values of originnearest: 'left',center: 'center',farthest: 'right'}),/*** @private* @return {Number} topToOriginY Distance from top edge of canvas to originY of Line.*/_getTopToOriginY: makeEdgeToOriginGetter({ // property namesorigin: 'originY',axis1: 'y1',axis2: 'y2',dimension: 'height'},{ // possible values of originnearest: 'top',center: 'center',farthest: 'bottom'}),/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx, noTransform) {ctx.beginPath();if (noTransform) {// Line coords are distances from left-top of canvas to origin of line.// To render line in a path-group, we need to translate them to// distances from center of path-group to center of line.var cp = this.getCenterPoint();ctx.translate(cp.x - this.strokeWidth / 2,cp.y - this.strokeWidth / 2);}if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) {// move from center (of virtual box) to its left/top corner// we can't assume x1, y1 is top left and x2, y2 is bottom rightvar p = this.calcLinePoints();ctx.moveTo(p.x1, p.y1);ctx.lineTo(p.x2, p.y2);}ctx.lineWidth = this.strokeWidth;// TODO: test this// make sure setting "fill" changes color of a line// (by copying fillStyle to strokeStyle, since line is stroked, not filled)var origStrokeStyle = ctx.strokeStyle;ctx.strokeStyle = this.stroke || ctx.fillStyle;this.stroke && this._renderStroke(ctx);ctx.strokeStyle = origStrokeStyle;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderDashedStroke: function(ctx) {var p = this.calcLinePoints();ctx.beginPath();fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray);ctx.closePath();},/*** Returns object representation of an instance* @methd toObject* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints());},/*** Recalculates line points given width and height* @private*/calcLinePoints: function() {var xMult = this.x1 <= this.x2 ? -1 : 1,yMult = this.y1 <= this.y2 ? -1 : 1,x1 = (xMult * this.width * 0.5),y1 = (yMult * this.height * 0.5),x2 = (xMult * this.width * -0.5),y2 = (yMult * this.height * -0.5);return {x1: x1,x2: x2,y1: y1,y2: y2};},/* _TO_SVG_START_ *//*** Returns SVG representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = this._createBaseSVGMarkup(),p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 };if (!(this.group && this.group.type === 'path-group')) {p = this.calcLinePoints();}markup.push('<line ','x1="', p.x1,'" y1="', p.y1,'" x2="', p.x2,'" y2="', p.y2,'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(),this.getSvgTransformMatrix(),'"/>\n');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** Returns complexity of an instance* @return {Number} complexity*/complexity: function() {return 1;}});/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement})* @static* @memberOf fabric.Line* @see http://www.w3.org/TR/SVG/shapes.html#LineElement*/fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' '));/*** Returns fabric.Line instance from an SVG element* @static* @memberOf fabric.Line* @param {SVGElement} element Element to parse* @param {Object} [options] Options object* @return {fabric.Line} instance of fabric.Line*/fabric.Line.fromElement = function(element, options) {var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES),points = [parsedAttributes.x1 || 0,parsedAttributes.y1 || 0,parsedAttributes.x2 || 0,parsedAttributes.y2 || 0];return new fabric.Line(points, extend(parsedAttributes, options));};/* _FROM_SVG_END_ *//*** Returns fabric.Line instance from an object representation* @static* @memberOf fabric.Line* @param {Object} object Object to create an instance from* @return {fabric.Line} instance of fabric.Line*/fabric.Line.fromObject = function(object) {var points = [object.x1, object.y1, object.x2, object.y2];return new fabric.Line(points, object);};/*** Produces a function that calculates distance from canvas edge to Line origin.*/function makeEdgeToOriginGetter(propertyNames, originValues) {var origin = propertyNames.origin,axis1 = propertyNames.axis1,axis2 = propertyNames.axis2,dimension = propertyNames.dimension,nearest = originValues.nearest,center = originValues.center,farthest = originValues.farthest;return function() {switch (this.get(origin)) {case nearest:return Math.min(this.get(axis1), this.get(axis2));case center:return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension));case farthest:return Math.max(this.get(axis1), this.get(axis2));}};}})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),pi = Math.PI,extend = fabric.util.object.extend;if (fabric.Circle) {fabric.warn('fabric.Circle is already defined.');return;}/*** Circle class* @class fabric.Circle* @extends fabric.Object* @see {@link fabric.Circle#initialize} for constructor definition*/fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ {/*** Type of an object* @type String* @default*/type: 'circle',/*** Radius of this circle* @type Number* @default*/radius: 0,/*** Start angle of the circle, moving clockwise* @type Number* @default 0*/startAngle: 0,/*** End angle of the circle* @type Number* @default 2Pi*/endAngle: pi * 2,/*** Constructor* @param {Object} [options] Options object* @return {fabric.Circle} thisArg*/initialize: function(options) {options = options || { };this.callSuper('initialize', options);this.set('radius', options.radius || 0);this.startAngle = options.startAngle || this.startAngle;this.endAngle = options.endAngle || this.endAngle;},/*** @private* @param {String} key* @param {Any} value* @return {fabric.Circle} thisArg*/_set: function(key, value) {this.callSuper('_set', key, value);if (key === 'radius') {this.setRadius(value);}return this;},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {return extend(this.callSuper('toObject', propertiesToInclude), {radius: this.get('radius'),startAngle: this.startAngle,endAngle: this.endAngle});},/* _TO_SVG_START_ *//*** Returns svg representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = this._createBaseSVGMarkup(), x = 0, y = 0,angle = (this.endAngle - this.startAngle) % ( 2 * pi);if (angle === 0) {if (this.group && this.group.type === 'path-group') {x = this.left + this.radius;y = this.top + this.radius;}markup.push('<circle ','cx="' + x + '" cy="' + y + '" ','r="', this.radius,'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(),' ', this.getSvgTransformMatrix(),'"/>\n');}else {var startX = Math.cos(this.startAngle) * this.radius,startY = Math.sin(this.startAngle) * this.radius,endX = Math.cos(this.endAngle) * this.radius,endY = Math.sin(this.endAngle) * this.radius,largeFlag = angle > pi ? '1' : '0';markup.push('<path d="M ' + startX + ' ' + startY,' A ' + this.radius + ' ' + this.radius,' 0 ', + largeFlag + ' 1', ' ' + endX + ' ' + endY,'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(),' ', this.getSvgTransformMatrix(),'"/>\n');}return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** @private* @param {CanvasRenderingContext2D} ctx context to render on* @param {Boolean} [noTransform] When true, context is not transformed*/_render: function(ctx, noTransform) {ctx.beginPath();ctx.arc(noTransform ? this.left + this.radius : 0,noTransform ? this.top + this.radius : 0,this.radius,this.startAngle,this.endAngle, false);this._renderFill(ctx);this._renderStroke(ctx);},/*** Returns horizontal radius of an object (according to how an object is scaled)* @return {Number}*/getRadiusX: function() {return this.get('radius') * this.get('scaleX');},/*** Returns vertical radius of an object (according to how an object is scaled)* @return {Number}*/getRadiusY: function() {return this.get('radius') * this.get('scaleY');},/*** Sets radius of an object (and updates width accordingly)* @return {Number}*/setRadius: function(value) {this.radius = value;this.set('width', value * 2).set('height', value * 2);},/*** Returns complexity of an instance* @return {Number} complexity of this instance*/complexity: function() {return 1;}});/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})* @static* @memberOf fabric.Circle* @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement*/fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' '));/*** Returns {@link fabric.Circle} instance from an SVG element* @static* @memberOf fabric.Circle* @param {SVGElement} element Element to parse* @param {Object} [options] Options object* @throws {Error} If value of `r` attribute is missing or invalid* @return {fabric.Circle} Instance of fabric.Circle*/fabric.Circle.fromElement = function(element, options) {options || (options = { });var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES);if (!isValidRadius(parsedAttributes)) {throw new Error('value of `r` attribute is required and can not be negative');}parsedAttributes.left = parsedAttributes.left || 0;parsedAttributes.top = parsedAttributes.top || 0;var obj = new fabric.Circle(extend(parsedAttributes, options));obj.left -= obj.radius;obj.top -= obj.radius;return obj;};/*** @private*/function isValidRadius(attributes) {return (('radius' in attributes) && (attributes.radius >= 0));}/* _FROM_SVG_END_ *//*** Returns {@link fabric.Circle} instance from an object representation* @static* @memberOf fabric.Circle* @param {Object} object Object to create an instance from* @return {Object} Instance of fabric.Circle*/fabric.Circle.fromObject = function(object) {return new fabric.Circle(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { });if (fabric.Triangle) {fabric.warn('fabric.Triangle is already defined');return;}/*** Triangle class* @class fabric.Triangle* @extends fabric.Object* @return {fabric.Triangle} thisArg* @see {@link fabric.Triangle#initialize} for constructor definition*/fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ {/*** Type of an object* @type String* @default*/type: 'triangle',/*** Constructor* @param {Object} [options] Options object* @return {Object} thisArg*/initialize: function(options) {options = options || { };this.callSuper('initialize', options);this.set('width', options.width || 100).set('height', options.height || 100);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx) {var widthBy2 = this.width / 2,heightBy2 = this.height / 2;ctx.beginPath();ctx.moveTo(-widthBy2, heightBy2);ctx.lineTo(0, -heightBy2);ctx.lineTo(widthBy2, heightBy2);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderDashedStroke: function(ctx) {var widthBy2 = this.width / 2,heightBy2 = this.height / 2;ctx.beginPath();fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);ctx.closePath();},/* _TO_SVG_START_ *//*** Returns SVG representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = this._createBaseSVGMarkup(),widthBy2 = this.width / 2,heightBy2 = this.height / 2,points = [-widthBy2 + ' ' + heightBy2,'0 ' + -heightBy2,widthBy2 + ' ' + heightBy2].join(',');markup.push('<polygon ','points="', points,'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(),'"/>');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** Returns complexity of an instance* @return {Number} complexity of this instance*/complexity: function() {return 1;}});/*** Returns fabric.Triangle instance from an object representation* @static* @memberOf fabric.Triangle* @param {Object} object Object to create an instance from* @return {Object} instance of Canvas.Triangle*/fabric.Triangle.fromObject = function(object) {return new fabric.Triangle(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),piBy2 = Math.PI * 2,extend = fabric.util.object.extend;if (fabric.Ellipse) {fabric.warn('fabric.Ellipse is already defined.');return;}/*** Ellipse class* @class fabric.Ellipse* @extends fabric.Object* @return {fabric.Ellipse} thisArg* @see {@link fabric.Ellipse#initialize} for constructor definition*/fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ {/*** Type of an object* @type String* @default*/type: 'ellipse',/*** Horizontal radius* @type Number* @default*/rx: 0,/*** Vertical radius* @type Number* @default*/ry: 0,/*** Constructor* @param {Object} [options] Options object* @return {fabric.Ellipse} thisArg*/initialize: function(options) {options = options || { };this.callSuper('initialize', options);this.set('rx', options.rx || 0);this.set('ry', options.ry || 0);},/*** @private* @param {String} key* @param {Any} value* @return {fabric.Ellipse} thisArg*/_set: function(key, value) {this.callSuper('_set', key, value);switch (key) {case 'rx':this.rx = value;this.set('width', value * 2);break;case 'ry':this.ry = value;this.set('height', value * 2);break;}return this;},/*** Returns horizontal radius of an object (according to how an object is scaled)* @return {Number}*/getRx: function() {return this.get('rx') * this.get('scaleX');},/*** Returns Vertical radius of an object (according to how an object is scaled)* @return {Number}*/getRy: function() {return this.get('ry') * this.get('scaleY');},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {return extend(this.callSuper('toObject', propertiesToInclude), {rx: this.get('rx'),ry: this.get('ry')});},/* _TO_SVG_START_ *//*** Returns svg representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = this._createBaseSVGMarkup(), x = 0, y = 0;if (this.group && this.group.type === 'path-group') {x = this.left + this.rx;y = this.top + this.ry;}markup.push('<ellipse ','cx="', x, '" cy="', y, '" ','rx="', this.rx,'" ry="', this.ry,'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(),this.getSvgTransformMatrix(),'"/>\n');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** @private* @param {CanvasRenderingContext2D} ctx context to render on* @param {Boolean} [noTransform] When true, context is not transformed*/_render: function(ctx, noTransform) {ctx.beginPath();ctx.save();ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0);ctx.arc(noTransform ? this.left + this.rx : 0,noTransform ? (this.top + this.ry) * this.rx/this.ry : 0,this.rx,0,piBy2,false);ctx.restore();this._renderFill(ctx);this._renderStroke(ctx);},/*** Returns complexity of an instance* @return {Number} complexity*/complexity: function() {return 1;}});/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement})* @static* @memberOf fabric.Ellipse* @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement*/fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' '));/*** Returns {@link fabric.Ellipse} instance from an SVG element* @static* @memberOf fabric.Ellipse* @param {SVGElement} element Element to parse* @param {Object} [options] Options object* @return {fabric.Ellipse}*/fabric.Ellipse.fromElement = function(element, options) {options || (options = { });var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES);parsedAttributes.left = parsedAttributes.left || 0;parsedAttributes.top = parsedAttributes.top || 0;var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));ellipse.top -= ellipse.ry;ellipse.left -= ellipse.rx;return ellipse;};/* _FROM_SVG_END_ *//*** Returns {@link fabric.Ellipse} instance from an object representation* @static* @memberOf fabric.Ellipse* @param {Object} object Object to create an instance from* @return {fabric.Ellipse}*/fabric.Ellipse.fromObject = function(object) {return new fabric.Ellipse(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;if (fabric.Rect) {console.warn('fabric.Rect is already defined');return;}var stateProperties = fabric.Object.prototype.stateProperties.concat();stateProperties.push('rx', 'ry', 'x', 'y');/*** Rectangle class* @class fabric.Rect* @extends fabric.Object* @return {fabric.Rect} thisArg* @see {@link fabric.Rect#initialize} for constructor definition*/fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ {/*** List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged})* as well as for history (undo/redo) purposes* @type Array*/stateProperties: stateProperties,/*** Type of an object* @type String* @default*/type: 'rect',/*** Horizontal border radius* @type Number* @default*/rx: 0,/*** Vertical border radius* @type Number* @default*/ry: 0,/*** Used to specify dash pattern for stroke on this object* @type Array*/strokeDashArray: null,/*** Constructor* @param {Object} [options] Options object* @return {Object} thisArg*/initialize: function(options) {options = options || { };this.callSuper('initialize', options);this._initRxRy();},/*** Initializes rx/ry attributes* @private*/_initRxRy: function() {if (this.rx && !this.ry) {this.ry = this.rx;}else if (this.ry && !this.rx) {this.rx = this.ry;}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx, noTransform) {// optimize 1x1 case (used in spray brush)if (this.width === 1 && this.height === 1) {ctx.fillRect(0, 0, 1, 1);return;}var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0,ry = this.ry ? Math.min(this.ry, this.height / 2) : 0,w = this.width,h = this.height,x = noTransform ? this.left : -this.width / 2,y = noTransform ? this.top : -this.height / 2,isRounded = rx !== 0 || ry !== 0,k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */;ctx.beginPath();ctx.moveTo(x + rx, y);ctx.lineTo(x + w - rx, y);isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);ctx.lineTo(x + w, y + h - ry);isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h);ctx.lineTo(x + rx, y + h);isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry);ctx.lineTo(x, y + ry);isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderDashedStroke: function(ctx) {var x = -this.width / 2,y = -this.height / 2,w = this.width,h = this.height;ctx.beginPath();fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);ctx.closePath();},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {var object = extend(this.callSuper('toObject', propertiesToInclude), {rx: this.get('rx') || 0,ry: this.get('ry') || 0});if (!this.includeDefaultValues) {this._removeDefaultValues(object);}return object;},/* _TO_SVG_START_ *//*** Returns svg representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top;if (!(this.group && this.group.type === 'path-group')) {x = -this.width / 2;y = -this.height / 2;}markup.push('<rect ','x="', x, '" y="', y,'" rx="', this.get('rx'), '" ry="', this.get('ry'),'" width="', this.width, '" height="', this.height,'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(),this.getSvgTransformMatrix(),'"/>\n');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** Returns complexity of an instance* @return {Number} complexity*/complexity: function() {return 1;}});/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`)* @static* @memberOf fabric.Rect* @see: http://www.w3.org/TR/SVG/shapes.html#RectElement*/fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' '));/*** Returns {@link fabric.Rect} instance from an SVG element* @static* @memberOf fabric.Rect* @param {SVGElement} element Element to parse* @param {Object} [options] Options object* @return {fabric.Rect} Instance of fabric.Rect*/fabric.Rect.fromElement = function(element, options) {if (!element) {return null;}options = options || { };var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES);parsedAttributes.left = parsedAttributes.left || 0;parsedAttributes.top = parsedAttributes.top || 0;var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));rect.visible = rect.width > 0 && rect.height > 0;return rect;};/* _FROM_SVG_END_ *//*** Returns {@link fabric.Rect} instance from an object representation* @static* @memberOf fabric.Rect* @param {Object} object Object to create an instance from* @return {Object} instance of fabric.Rect*/fabric.Rect.fromObject = function(object) {return new fabric.Rect(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { });if (fabric.Polyline) {fabric.warn('fabric.Polyline is already defined');return;}/*** Polyline class* @class fabric.Polyline* @extends fabric.Object* @see {@link fabric.Polyline#initialize} for constructor definition*/fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ {/*** Type of an object* @type String* @default*/type: 'polyline',/*** Points array* @type Array* @default*/points: null,/*** Minimum X from points values, necessary to offset points* @type Number* @default*/minX: 0,/*** Minimum Y from points values, necessary to offset points* @type Number* @default*/minY: 0,/*** Constructor* @param {Array} points Array of points (where each point is an object with x and y)* @param {Object} [options] Options object* @param {Boolean} [skipOffset] Whether points offsetting should be skipped* @return {fabric.Polyline} thisArg* @example* var poly = new fabric.Polyline([* { x: 10, y: 10 },* { x: 50, y: 30 },* { x: 40, y: 70 },* { x: 60, y: 50 },* { x: 100, y: 150 },* { x: 40, y: 100 }* ], {* stroke: 'red',* left: 100,* top: 100* });*/initialize: function(points, options) {return fabric.Polygon.prototype.initialize.call(this, points, options);},/*** @private*/_calcDimensions: function() {return fabric.Polygon.prototype._calcDimensions.call(this);},/*** @private*/_applyPointOffset: function() {return fabric.Polygon.prototype._applyPointOffset.call(this);},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} Object representation of an instance*/toObject: function(propertiesToInclude) {return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude);},/* _TO_SVG_START_ *//*** Returns SVG representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {return fabric.Polygon.prototype.toSVG.call(this, reviver);},/* _TO_SVG_END_ *//*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx) {if (!fabric.Polygon.prototype.commonRender.call(this, ctx)) {return;}this._renderFill(ctx);this._renderStroke(ctx);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderDashedStroke: function(ctx) {var p1, p2;ctx.beginPath();for (var i = 0, len = this.points.length; i < len; i++) {p1 = this.points[i];p2 = this.points[i + 1] || p1;fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);}},/*** Returns complexity of an instance* @return {Number} complexity of this instance*/complexity: function() {return this.get('points').length;}});/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement})* @static* @memberOf fabric.Polyline* @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement*/fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();/*** Returns fabric.Polyline instance from an SVG element* @static* @memberOf fabric.Polyline* @param {SVGElement} element Element to parse* @param {Object} [options] Options object* @return {fabric.Polyline} Instance of fabric.Polyline*/fabric.Polyline.fromElement = function(element, options) {if (!element) {return null;}options || (options = { });var points = fabric.parsePointsAttribute(element.getAttribute('points')),parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES);return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));};/* _FROM_SVG_END_ *//*** Returns fabric.Polyline instance from an object representation* @static* @memberOf fabric.Polyline* @param {Object} object Object to create an instance from* @return {fabric.Polyline} Instance of fabric.Polyline*/fabric.Polyline.fromObject = function(object) {var points = object.points;return new fabric.Polyline(points, object, true);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend,min = fabric.util.array.min,max = fabric.util.array.max,toFixed = fabric.util.toFixed;if (fabric.Polygon) {fabric.warn('fabric.Polygon is already defined');return;}/*** Polygon class* @class fabric.Polygon* @extends fabric.Object* @see {@link fabric.Polygon#initialize} for constructor definition*/fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ {/*** Type of an object* @type String* @default*/type: 'polygon',/*** Points array* @type Array* @default*/points: null,/*** Minimum X from points values, necessary to offset points* @type Number* @default*/minX: 0,/*** Minimum Y from points values, necessary to offset points* @type Number* @default*/minY: 0,/*** Constructor* @param {Array} points Array of points* @param {Object} [options] Options object* @return {fabric.Polygon} thisArg*/initialize: function(points, options) {options = options || { };this.points = points || [ ];this.callSuper('initialize', options);this._calcDimensions();if (!('top' in options)) {this.top = this.minY;}if (!('left' in options)) {this.left = this.minX;}},/*** @private*/_calcDimensions: function() {var points = this.points,minX = min(points, 'x'),minY = min(points, 'y'),maxX = max(points, 'x'),maxY = max(points, 'y');this.width = (maxX - minX) || 0;this.height = (maxY - minY) || 0;this.minX = minX || 0,this.minY = minY || 0;},/*** @private*/_applyPointOffset: function() {// change points to offset polygon into a bounding box// executed one timethis.points.forEach(function(p) {p.x -= (this.minX + this.width / 2);p.y -= (this.minY + this.height / 2);}, this);},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} Object representation of an instance*/toObject: function(propertiesToInclude) {return extend(this.callSuper('toObject', propertiesToInclude), {points: this.points.concat()});},/* _TO_SVG_START_ *//*** Returns svg representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var points = [],markup = this._createBaseSVGMarkup();for (var i = 0, len = this.points.length; i < len; i++) {points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');}markup.push('<', this.type, ' ','points="', points.join(''),'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(),' ', this.getSvgTransformMatrix(),'"/>\n');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx) {if (!this.commonRender(ctx)) {return;}this._renderFill(ctx);if (this.stroke || this.strokeDashArray) {ctx.closePath();this._renderStroke(ctx);}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/commonRender: function(ctx) {var point, len = this.points.length;if (!len || isNaN(this.points[len - 1].y)) {// do not draw if no points or odd points// NaN comes from parseFloat of a empty string in parserreturn false;}ctx.beginPath();if (this._applyPointOffset) {if (!(this.group && this.group.type === 'path-group')) {this._applyPointOffset();}this._applyPointOffset = null;}ctx.moveTo(this.points[0].x, this.points[0].y);for (var i = 0; i < len; i++) {point = this.points[i];ctx.lineTo(point.x, point.y);}return true;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderDashedStroke: function(ctx) {fabric.Polyline.prototype._renderDashedStroke.call(this, ctx);ctx.closePath();},/*** Returns complexity of an instance* @return {Number} complexity of this instance*/complexity: function() {return this.points.length;}});/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`)* @static* @memberOf fabric.Polygon* @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement*/fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();/*** Returns {@link fabric.Polygon} instance from an SVG element* @static* @memberOf fabric.Polygon* @param {SVGElement} element Element to parse* @param {Object} [options] Options object* @return {fabric.Polygon} Instance of fabric.Polygon*/fabric.Polygon.fromElement = function(element, options) {if (!element) {return null;}options || (options = { });var points = fabric.parsePointsAttribute(element.getAttribute('points')),parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES);return new fabric.Polygon(points, extend(parsedAttributes, options));};/* _FROM_SVG_END_ *//*** Returns fabric.Polygon instance from an object representation* @static* @memberOf fabric.Polygon* @param {Object} object Object to create an instance from* @return {fabric.Polygon} Instance of fabric.Polygon*/fabric.Polygon.fromObject = function(object) {return new fabric.Polygon(object.points, object, true);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),min = fabric.util.array.min,max = fabric.util.array.max,extend = fabric.util.object.extend,_toString = Object.prototype.toString,drawArc = fabric.util.drawArc,commandLengths = {m: 2,l: 2,h: 1,v: 1,c: 6,s: 4,q: 4,t: 2,a: 7},repeatedCommands = {m: 'l',M: 'L'};if (fabric.Path) {fabric.warn('fabric.Path is already defined');return;}/*** Path class* @class fabric.Path* @extends fabric.Object* @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup}* @see {@link fabric.Path#initialize} for constructor definition*/fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ {/*** Type of an object* @type String* @default*/type: 'path',/*** Array of path points* @type Array* @default*/path: null,/*** Minimum X from points values, necessary to offset points* @type Number* @default*/minX: 0,/*** Minimum Y from points values, necessary to offset points* @type Number* @default*/minY: 0,/*** Constructor* @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)* @param {Object} [options] Options object* @return {fabric.Path} thisArg*/initialize: function(path, options) {options = options || { };this.setOptions(options);if (!path) {throw new Error('`path` argument is required');}var fromArray = _toString.call(path) === '[object Array]';this.path = fromArray? path// one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values): path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);if (!this.path) {return;}if (!fromArray) {this.path = this._parsePath();}this._setPositionDimensions();if (options.sourcePath) {this.setSourcePath(options.sourcePath);}},/*** @private*/_setPositionDimensions: function() {var calcDim = this._parseDimensions();this.minX = calcDim.left;this.minY = calcDim.top;this.width = calcDim.width;this.height = calcDim.height;calcDim.left += this.originX === 'center'? this.width / 2: this.originX === 'right'? this.width: 0;calcDim.top += this.originY === 'center'? this.height / 2: this.originY === 'bottom'? this.height: 0;this.top = this.top || calcDim.top;this.left = this.left || calcDim.left;this.pathOffset = this.pathOffset || {x: this.minX + this.width / 2,y: this.minY + this.height / 2};},/*** @private* @param {CanvasRenderingContext2D} ctx context to render path on*/_render: function(ctx) {var current, // current instructionprevious = null,subpathStartX = 0,subpathStartY = 0,x = 0, // current xy = 0, // current ycontrolX = 0, // current control point xcontrolY = 0, // current control point ytempX,tempY,l = -this.pathOffset.x,t = -this.pathOffset.y;if (this.group && this.group.type === 'path-group') {l = 0;t = 0;}ctx.beginPath();for (var i = 0, len = this.path.length; i < len; ++i) {current = this.path[i];switch (current[0]) { // first lettercase 'l': // lineto, relativex += current[1];y += current[2];ctx.lineTo(x + l, y + t);break;case 'L': // lineto, absolutex = current[1];y = current[2];ctx.lineTo(x + l, y + t);break;case 'h': // horizontal lineto, relativex += current[1];ctx.lineTo(x + l, y + t);break;case 'H': // horizontal lineto, absolutex = current[1];ctx.lineTo(x + l, y + t);break;case 'v': // vertical lineto, relativey += current[1];ctx.lineTo(x + l, y + t);break;case 'V': // verical lineto, absolutey = current[1];ctx.lineTo(x + l, y + t);break;case 'm': // moveTo, relativex += current[1];y += current[2];subpathStartX = x;subpathStartY = y;ctx.moveTo(x + l, y + t);break;case 'M': // moveTo, absolutex = current[1];y = current[2];subpathStartX = x;subpathStartY = y;ctx.moveTo(x + l, y + t);break;case 'c': // bezierCurveTo, relativetempX = x + current[5];tempY = y + current[6];controlX = x + current[3];controlY = y + current[4];ctx.bezierCurveTo(x + current[1] + l, // x1y + current[2] + t, // y1controlX + l, // x2controlY + t, // y2tempX + l,tempY + t);x = tempX;y = tempY;break;case 'C': // bezierCurveTo, absolutex = current[5];y = current[6];controlX = current[3];controlY = current[4];ctx.bezierCurveTo(current[1] + l,current[2] + t,controlX + l,controlY + t,x + l,y + t);break;case 's': // shorthand cubic bezierCurveTo, relative// transform to absolute x,ytempX = x + current[3];tempY = y + current[4];if (previous[0].match(/[CcSs]/) === null) {// If there is no previous command or if the previous command was not a C, c, S, or s,// the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointscontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}ctx.bezierCurveTo(controlX + l,controlY + t,x + current[1] + l,y + current[2] + t,tempX + l,tempY + t);// set control point to 2nd one of this command// "... the first control point is assumed to be// the reflection of the second control point on// the previous command relative to the current point."controlX = x + current[1];controlY = y + current[2];x = tempX;y = tempY;break;case 'S': // shorthand cubic bezierCurveTo, absolutetempX = current[3];tempY = current[4];if (previous[0].match(/[CcSs]/) === null) {// If there is no previous command or if the previous command was not a C, c, S, or s,// the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointscontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}ctx.bezierCurveTo(controlX + l,controlY + t,current[1] + l,current[2] + t,tempX + l,tempY + t);x = tempX;y = tempY;// set control point to 2nd one of this command// "... the first control point is assumed to be// the reflection of the second control point on// the previous command relative to the current point."controlX = current[1];controlY = current[2];break;case 'q': // quadraticCurveTo, relative// transform to absolute x,ytempX = x + current[3];tempY = y + current[4];controlX = x + current[1];controlY = y + current[2];ctx.quadraticCurveTo(controlX + l,controlY + t,tempX + l,tempY + t);x = tempX;y = tempY;break;case 'Q': // quadraticCurveTo, absolutetempX = current[3];tempY = current[4];ctx.quadraticCurveTo(current[1] + l,current[2] + t,tempX + l,tempY + t);x = tempX;y = tempY;controlX = current[1];controlY = current[2];break;case 't': // shorthand quadraticCurveTo, relative// transform to absolute x,ytempX = x + current[1];tempY = y + current[2];if (previous[0].match(/[QqTt]/) === null) {// If there is no previous command or if the previous command was not a Q, q, T or t,// assume the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointcontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}ctx.quadraticCurveTo(controlX + l,controlY + t,tempX + l,tempY + t);x = tempX;y = tempY;break;case 'T':tempX = current[1];tempY = current[2];if (previous[0].match(/[QqTt]/) === null) {// If there is no previous command or if the previous command was not a Q, q, T or t,// assume the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointcontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}ctx.quadraticCurveTo(controlX + l,controlY + t,tempX + l,tempY + t);x = tempX;y = tempY;break;case 'a':// TODO: optimize thisdrawArc(ctx, x + l, y + t, [current[1],current[2],current[3],current[4],current[5],current[6] + x + l,current[7] + y + t]);x += current[6];y += current[7];break;case 'A':// TODO: optimize thisdrawArc(ctx, x + l, y + t, [current[1],current[2],current[3],current[4],current[5],current[6] + l,current[7] + t]);x = current[6];y = current[7];break;case 'z':case 'Z':x = subpathStartX;y = subpathStartY;ctx.closePath();break;}previous = current;}this._renderFill(ctx);this._renderStroke(ctx);},/*** Returns string representation of an instance* @return {String} string representation of an instance*/toString: function() {return '#<fabric.Path (' + this.complexity() +'): { "top": ' + this.top + ', "left": ' + this.left + ' }>';},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {var o = extend(this.callSuper('toObject', propertiesToInclude), {path: this.path.map(function(item) { return item.slice() }),pathOffset: this.pathOffset});if (this.sourcePath) {o.sourcePath = this.sourcePath;}if (this.transformMatrix) {o.transformMatrix = this.transformMatrix;}return o;},/*** Returns dataless object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toDatalessObject: function(propertiesToInclude) {var o = this.toObject(propertiesToInclude);if (this.sourcePath) {o.path = this.sourcePath;}delete o.sourcePath;return o;},/* _TO_SVG_START_ *//*** Returns svg representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var chunks = [],markup = this._createBaseSVGMarkup(), addTransform = '';for (var i = 0, len = this.path.length; i < len; i++) {chunks.push(this.path[i].join(' '));}var path = chunks.join(' ');if (!(this.group && this.group.type === 'path-group')) {addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';}markup.push(//jscs:disable validateIndentation'<path ','d="', path,'" style="', this.getSvgStyles(),'" transform="', this.getSvgTransform(), addTransform,this.getSvgTransformMatrix(), '" stroke-linecap="round" ','/>\n'//jscs:enable validateIndentation);return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** Returns number representation of an instance complexity* @return {Number} complexity of this instance*/complexity: function() {return this.path.length;},/*** @private*/_parsePath: function() {var result = [ ],coords = [ ],currentPath,parsed,re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,match,coordsStr;for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) {currentPath = this.path[i];coordsStr = currentPath.slice(1).trim();coords.length = 0;while ((match = re.exec(coordsStr))) {coords.push(match[0]);}coordsParsed = [ currentPath.charAt(0) ];for (var j = 0, jlen = coords.length; j < jlen; j++) {parsed = parseFloat(coords[j]);if (!isNaN(parsed)) {coordsParsed.push(parsed);}}var command = coordsParsed[0],commandLength = commandLengths[command.toLowerCase()],repeatedCommand = repeatedCommands[command] || command;if (coordsParsed.length - 1 > commandLength) {for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) {result.push([ command ].concat(coordsParsed.slice(k, k + commandLength)));command = repeatedCommand;}}else {result.push(coordsParsed);}}return result;},/*** @private*/_parseDimensions: function() {var aX = [],aY = [],current, // current instructionprevious = null,subpathStartX = 0,subpathStartY = 0,x = 0, // current xy = 0, // current ycontrolX = 0, // current control point xcontrolY = 0, // current control point ytempX,tempY,bounds;for (var i = 0, len = this.path.length; i < len; ++i) {current = this.path[i];switch (current[0]) { // first lettercase 'l': // lineto, relativex += current[1];y += current[2];bounds = [ ];break;case 'L': // lineto, absolutex = current[1];y = current[2];bounds = [ ];break;case 'h': // horizontal lineto, relativex += current[1];bounds = [ ];break;case 'H': // horizontal lineto, absolutex = current[1];bounds = [ ];break;case 'v': // vertical lineto, relativey += current[1];bounds = [ ];break;case 'V': // verical lineto, absolutey = current[1];bounds = [ ];break;case 'm': // moveTo, relativex += current[1];y += current[2];subpathStartX = x;subpathStartY = y;bounds = [ ];break;case 'M': // moveTo, absolutex = current[1];y = current[2];subpathStartX = x;subpathStartY = y;bounds = [ ];break;case 'c': // bezierCurveTo, relativetempX = x + current[5];tempY = y + current[6];controlX = x + current[3];controlY = y + current[4];bounds = fabric.util.getBoundsOfCurve(x, y,x + current[1], // x1y + current[2], // y1controlX, // x2controlY, // y2tempX,tempY);x = tempX;y = tempY;break;case 'C': // bezierCurveTo, absolutex = current[5];y = current[6];controlX = current[3];controlY = current[4];bounds = fabric.util.getBoundsOfCurve(x, y,current[1],current[2],controlX,controlY,x,y);break;case 's': // shorthand cubic bezierCurveTo, relative// transform to absolute x,ytempX = x + current[3];tempY = y + current[4];if (previous[0].match(/[CcSs]/) === null) {// If there is no previous command or if the previous command was not a C, c, S, or s,// the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointscontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}bounds = fabric.util.getBoundsOfCurve(x, y,controlX,controlY,x + current[1],y + current[2],tempX,tempY);// set control point to 2nd one of this command// "... the first control point is assumed to be// the reflection of the second control point on// the previous command relative to the current point."controlX = x + current[1];controlY = y + current[2];x = tempX;y = tempY;break;case 'S': // shorthand cubic bezierCurveTo, absolutetempX = current[3];tempY = current[4];if (previous[0].match(/[CcSs]/) === null) {// If there is no previous command or if the previous command was not a C, c, S, or s,// the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointscontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}bounds = fabric.util.getBoundsOfCurve(x, y,controlX,controlY,current[1],current[2],tempX,tempY);x = tempX;y = tempY;// set control point to 2nd one of this command// "... the first control point is assumed to be// the reflection of the second control point on// the previous command relative to the current point."controlX = current[1];controlY = current[2];break;case 'q': // quadraticCurveTo, relative// transform to absolute x,ytempX = x + current[3];tempY = y + current[4];controlX = x + current[1];controlY = y + current[2];bounds = fabric.util.getBoundsOfCurve(x, y,controlX,controlY,controlX,controlY,tempX,tempY);x = tempX;y = tempY;break;case 'Q': // quadraticCurveTo, absolutecontrolX = current[1];controlY = current[2];bounds = fabric.util.getBoundsOfCurve(x, y,controlX,controlY,controlX,controlY,current[3],current[4]);x = current[3];y = current[4];break;case 't': // shorthand quadraticCurveTo, relative// transform to absolute x,ytempX = x + current[1];tempY = y + current[2];if (previous[0].match(/[QqTt]/) === null) {// If there is no previous command or if the previous command was not a Q, q, T or t,// assume the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointcontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}bounds = fabric.util.getBoundsOfCurve(x, y,controlX,controlY,controlX,controlY,tempX,tempY);x = tempX;y = tempY;break;case 'T':tempX = current[1];tempY = current[2];if (previous[0].match(/[QqTt]/) === null) {// If there is no previous command or if the previous command was not a Q, q, T or t,// assume the control point is coincident with the current pointcontrolX = x;controlY = y;}else {// calculate reflection of previous control pointcontrolX = 2 * x - controlX;controlY = 2 * y - controlY;}bounds = fabric.util.getBoundsOfCurve(x, y,controlX,controlY,controlX,controlY,tempX,tempY);x = tempX;y = tempY;break;case 'a':// TODO: optimize thisbounds = fabric.util.getBoundsOfArc(x, y,current[1],current[2],current[3],current[4],current[5],current[6] + x,current[7] + y);x += current[6];y += current[7];break;case 'A':// TODO: optimize thisbounds = fabric.util.getBoundsOfArc(x, y,current[1],current[2],current[3],current[4],current[5],current[6],current[7]);x = current[6];y = current[7];break;case 'z':case 'Z':x = subpathStartX;y = subpathStartY;break;}previous = current;bounds.forEach(function (point) {aX.push(point.x);aY.push(point.y);});aX.push(x);aY.push(y);}var minX = min(aX),minY = min(aY),maxX = max(aX),maxY = max(aY),deltaX = maxX - minX,deltaY = maxY - minY,o = {left: minX,top: minY,width: deltaX,height: deltaY};return o;}});/*** Creates an instance of fabric.Path from an object* @static* @memberOf fabric.Path* @param {Object} object* @param {Function} callback Callback to invoke when an fabric.Path instance is created*/fabric.Path.fromObject = function(object, callback) {if (typeof object.path === 'string') {fabric.loadSVGFromURL(object.path, function (elements) {var path = elements[0],pathUrl = object.path;delete object.path;fabric.util.object.extend(path, object);path.setSourcePath(pathUrl);callback(path);});}else {callback(new fabric.Path(object.path, object));}};/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)* @static* @memberOf fabric.Path* @see http://www.w3.org/TR/SVG/paths.html#PathElement*/fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']);/*** Creates an instance of fabric.Path from an SVG <path> element* @static* @memberOf fabric.Path* @param {SVGElement} element to parse* @param {Function} callback Callback to invoke when an fabric.Path instance is created* @param {Object} [options] Options object*/fabric.Path.fromElement = function(element, callback, options) {var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES);callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options)));};/* _FROM_SVG_END_ *//*** Indicates that instances of this type are async* @static* @memberOf fabric.Path* @type Boolean* @default*/fabric.Path.async = true;})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend,invoke = fabric.util.array.invoke,parentToObject = fabric.Object.prototype.toObject;if (fabric.PathGroup) {fabric.warn('fabric.PathGroup is already defined');return;}/*** Path group class* @class fabric.PathGroup* @extends fabric.Path* @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup}* @see {@link fabric.PathGroup#initialize} for constructor definition*/fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ {/*** Type of an object* @type String* @default*/type: 'path-group',/*** Fill value* @type String* @default*/fill: '',/*** Constructor* @param {Array} paths* @param {Object} [options] Options object* @return {fabric.PathGroup} thisArg*/initialize: function(paths, options) {options = options || { };this.paths = paths || [ ];for (var i = this.paths.length; i--;) {this.paths[i].group = this;}if (options.toBeParsed) {this.parseDimensionsFromPaths(options);delete options.toBeParsed;}this.setOptions(options);this.setCoords();if (options.sourcePath) {this.setSourcePath(options.sourcePath);}},/*** Calculate width and height based on paths contained*/parseDimensionsFromPaths: function(options) {var points, p, xC = [ ], yC = [ ], path, height, width,m = this.transformMatrix;for (var j = this.paths.length; j--;) {path = this.paths[j];height = path.height + path.strokeWidth;width = path.width + path.strokeWidth;points = [{ x: path.left, y: path.top },{ x: path.left + width, y: path.top },{ x: path.left, y: path.top + height },{ x: path.left + width, y: path.top + height }];for (var i = 0; i < points.length; i++) {p = points[i];if (m) {p = fabric.util.transformPoint(p, m, false);}xC.push(p.x);yC.push(p.y);}}options.width = Math.max.apply(null, xC);options.height = Math.max.apply(null, yC);},/*** Renders this group on a specified context* @param {CanvasRenderingContext2D} ctx Context to render this instance on*/render: function(ctx) {// do not render if object is not visibleif (!this.visible) {return;}ctx.save();if (this.transformMatrix) {ctx.transform.apply(ctx, this.transformMatrix);}this.transform(ctx);this._setShadow(ctx);this.clipTo && fabric.util.clipContext(this, ctx);ctx.translate(-this.width/2, -this.height/2);for (var i = 0, l = this.paths.length; i < l; ++i) {this.paths[i].render(ctx, true);}this.clipTo && ctx.restore();this._removeShadow(ctx);ctx.restore();},/*** Sets certain property to a certain value* @param {String} prop* @param {Any} value* @return {fabric.PathGroup} thisArg*/_set: function(prop, value) {if (prop === 'fill' && value && this.isSameColor()) {var i = this.paths.length;while (i--) {this.paths[i]._set(prop, value);}}return this.callSuper('_set', prop, value);},/*** Returns object representation of this path group* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {var o = extend(parentToObject.call(this, propertiesToInclude), {paths: invoke(this.getObjects(), 'toObject', propertiesToInclude)});if (this.sourcePath) {o.sourcePath = this.sourcePath;}return o;},/*** Returns dataless object representation of this path group* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} dataless object representation of an instance*/toDatalessObject: function(propertiesToInclude) {var o = this.toObject(propertiesToInclude);if (this.sourcePath) {o.paths = this.sourcePath;}return o;},/* _TO_SVG_START_ *//*** Returns svg representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var objects = this.getObjects(),p = this.getPointByOrigin('left', 'top'),translatePart = 'translate(' + p.x + ' ' + p.y + ')',markup = [//jscs:disable validateIndentation'<g ','style="', this.getSvgStyles(), '" ','transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ','>\n'//jscs:enable validateIndentation];for (var i = 0, len = objects.length; i < len; i++) {markup.push(objects[i].toSVG(reviver));}markup.push('</g>\n');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** Returns a string representation of this path group* @return {String} string representation of an object*/toString: function() {return '#<fabric.PathGroup (' + this.complexity() +'): { top: ' + this.top + ', left: ' + this.left + ' }>';},/*** Returns true if all paths in this group are of same color* @return {Boolean} true if all paths are of the same color (`fill`)*/isSameColor: function() {var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase();return this.getObjects().every(function(path) {return (path.get('fill') || '').toLowerCase() === firstPathFill;});},/*** Returns number representation of object's complexity* @return {Number} complexity*/complexity: function() {return this.paths.reduce(function(total, path) {return total + ((path && path.complexity) ? path.complexity() : 0);}, 0);},/*** Returns all paths in this path group* @return {Array} array of path objects included in this path group*/getObjects: function() {return this.paths;}});/*** Creates fabric.PathGroup instance from an object representation* @static* @memberOf fabric.PathGroup* @param {Object} object Object to create an instance from* @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created*/fabric.PathGroup.fromObject = function(object, callback) {if (typeof object.paths === 'string') {fabric.loadSVGFromURL(object.paths, function (elements) {var pathUrl = object.paths;delete object.paths;var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl);callback(pathGroup);});}else {fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) {delete object.paths;callback(new fabric.PathGroup(enlivenedObjects, object));});}};/*** Indicates that instances of this type are async* @static* @memberOf fabric.PathGroup* @type Boolean* @default*/fabric.PathGroup.async = true;})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend,min = fabric.util.array.min,max = fabric.util.array.max,invoke = fabric.util.array.invoke;if (fabric.Group) {return;}// lock-related properties, for use in fabric.Group#get// to enable locking behavior on group// when one of its objects has lock-related properties setvar _lockProperties = {lockMovementX: true,lockMovementY: true,lockRotation: true,lockScalingX: true,lockScalingY: true,lockUniScaling: true};/*** Group class* @class fabric.Group* @extends fabric.Object* @mixes fabric.Collection* @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups}* @see {@link fabric.Group#initialize} for constructor definition*/fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {/*** Type of an object* @type String* @default*/type: 'group',/*** Constructor* @param {Object} objects Group objects* @param {Object} [options] Options object* @return {Object} thisArg*/initialize: function(objects, options) {options = options || { };this._objects = objects || [];for (var i = this._objects.length; i--; ) {this._objects[i].group = this;}this.originalState = { };this.callSuper('initialize');if (options.originX) {this.originX = options.originX;}if (options.originY) {this.originY = options.originY;}this._calcBounds();this._updateObjectsCoords();this.callSuper('initialize', options);this.setCoords();this.saveCoords();},/*** @private*/_updateObjectsCoords: function() {this.forEachObject(this._updateObjectCoords, this);},/*** @private*/_updateObjectCoords: function(object) {var objectLeft = object.getLeft(),objectTop = object.getTop(),center = this.getCenterPoint();object.set({originalLeft: objectLeft,originalTop: objectTop,left: objectLeft - center.x,top: objectTop - center.y});object.setCoords();// do not display corners of objects enclosed in a groupobject.__origHasControls = object.hasControls;object.hasControls = false;},/*** Returns string represenation of a group* @return {String}*/toString: function() {return '#<fabric.Group: (' + this.complexity() + ')>';},/*** Adds an object to a group; Then recalculates group's dimension, position.* @param {Object} object* @return {fabric.Group} thisArg* @chainable*/addWithUpdate: function(object) {this._restoreObjectsState();if (object) {this._objects.push(object);object.group = this;}// since _restoreObjectsState set objects inactivethis.forEachObject(this._setObjectActive, this);this._calcBounds();this._updateObjectsCoords();return this;},/*** @private*/_setObjectActive: function(object) {object.set('active', true);object.group = this;},/*** Removes an object from a group; Then recalculates group's dimension, position.* @param {Object} object* @return {fabric.Group} thisArg* @chainable*/removeWithUpdate: function(object) {this._moveFlippedObject(object);this._restoreObjectsState();// since _restoreObjectsState set objects inactivethis.forEachObject(this._setObjectActive, this);this.remove(object);this._calcBounds();this._updateObjectsCoords();return this;},/*** @private*/_onObjectAdded: function(object) {object.group = this;},/*** @private*/_onObjectRemoved: function(object) {delete object.group;object.set('active', false);},/*** Properties that are delegated to group objects when reading/writing* @param {Object} delegatedProperties*/delegatedProperties: {fill: true,opacity: true,fontFamily: true,fontWeight: true,fontSize: true,fontStyle: true,lineHeight: true,textDecoration: true,textAlign: true,backgroundColor: true},/*** @private*/_set: function(key, value) {if (key in this.delegatedProperties) {var i = this._objects.length;while (i--) {this._objects[i].set(key, value);}}this.callSuper('_set', key, value);},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {return extend(this.callSuper('toObject', propertiesToInclude), {objects: invoke(this._objects, 'toObject', propertiesToInclude)});},/*** Renders instance on a given context* @param {CanvasRenderingContext2D} ctx context to render instance on*/render: function(ctx) {// do not render if object is not visibleif (!this.visible) {return;}ctx.save();this.clipTo && fabric.util.clipContext(this, ctx);this.transform(ctx);// the array is now sorted in order of highest first, so start from endfor (var i = 0, len = this._objects.length; i < len; i++) {this._renderObject(this._objects[i], ctx);}this.clipTo && ctx.restore();ctx.restore();},/*** Renders controls and borders for the object* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Boolean} [noTransform] When true, context is not transformed*/_renderControls: function(ctx, noTransform) {this.callSuper('_renderControls', ctx, noTransform);for (var i = 0, len = this._objects.length; i < len; i++) {this._objects[i]._renderControls(ctx);}},/*** @private*/_renderObject: function(object, ctx) {var originalHasRotatingPoint = object.hasRotatingPoint;// do not render if object is not visibleif (!object.visible) {return;}object.hasRotatingPoint = false;object.render(ctx);object.hasRotatingPoint = originalHasRotatingPoint;},/*** Retores original state of each of group objects (original state is that which was before group was created).* @private* @return {fabric.Group} thisArg* @chainable*/_restoreObjectsState: function() {this._objects.forEach(this._restoreObjectState, this);return this;},/*** Realises the transform from this group onto the supplied object* i.e. it tells you what would happen if the supplied object was in* the group, and then the group was destroyed. It mutates the supplied* object.* @param {fabric.Object} object* @return {fabric.Object} transformedObject*/realizeTransform: function(object) {this._moveFlippedObject(object);this._setObjectPosition(object);return object;},/*** Moves a flipped object to the position where it's displayed* @private* @param {fabric.Object} object* @return {fabric.Group} thisArg*/_moveFlippedObject: function(object) {var oldOriginX = object.get('originX'),oldOriginY = object.get('originY'),center = object.getCenterPoint();object.set({originX: 'center',originY: 'center',left: center.x,top: center.y});this._toggleFlipping(object);var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY);object.set({originX: oldOriginX,originY: oldOriginY,left: newOrigin.x,top: newOrigin.y});return this;},/*** @private*/_toggleFlipping: function(object) {if (this.flipX) {object.toggle('flipX');object.set('left', -object.get('left'));object.setAngle(-object.getAngle());}if (this.flipY) {object.toggle('flipY');object.set('top', -object.get('top'));object.setAngle(-object.getAngle());}},/*** Restores original state of a specified object in group* @private* @param {fabric.Object} object* @return {fabric.Group} thisArg*/_restoreObjectState: function(object) {this._setObjectPosition(object);object.setCoords();object.hasControls = object.__origHasControls;delete object.__origHasControls;object.set('active', false);object.setCoords();delete object.group;return this;},/*** @private*/_setObjectPosition: function(object) {var center = this.getCenterPoint(),rotated = this._getRotatedLeftTop(object);object.set({angle: object.getAngle() + this.getAngle(),left: center.x + rotated.left,top: center.y + rotated.top,scaleX: object.get('scaleX') * this.get('scaleX'),scaleY: object.get('scaleY') * this.get('scaleY')});},/*** @private*/_getRotatedLeftTop: function(object) {var groupAngle = this.getAngle() * (Math.PI / 180);return {left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') +Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')),top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') +Math.sin(groupAngle) * object.getLeft() * this.get('scaleX'))};},/*** Destroys a group (restoring state of its objects)* @return {fabric.Group} thisArg* @chainable*/destroy: function() {this._objects.forEach(this._moveFlippedObject, this);return this._restoreObjectsState();},/*** Saves coordinates of this instance (to be used together with `hasMoved`)* @saveCoords* @return {fabric.Group} thisArg* @chainable*/saveCoords: function() {this._originalLeft = this.get('left');this._originalTop = this.get('top');return this;},/*** Checks whether this group was moved (since `saveCoords` was called last)* @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)*/hasMoved: function() {return this._originalLeft !== this.get('left') ||this._originalTop !== this.get('top');},/*** Sets coordinates of all group objects* @return {fabric.Group} thisArg* @chainable*/setObjectsCoords: function() {this.forEachObject(function(object) {object.setCoords();});return this;},/*** @private*/_calcBounds: function(onlyWidthHeight) {var aX = [],aY = [],o, prop,props = ['tr', 'br', 'bl', 'tl'];for (var i = 0, len = this._objects.length; i < len; ++i) {o = this._objects[i];o.setCoords();for (var j = 0; j < props.length; j++) {prop = props[j];aX.push(o.oCoords[prop].x);aY.push(o.oCoords[prop].y);}}this.set(this._getBounds(aX, aY, onlyWidthHeight));},/*** @private*/_getBounds: function(aX, aY, onlyWidthHeight) {var ivt = fabric.util.invertTransform(this.getViewportTransform()),minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt),maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt),obj = {width: (maxXY.x - minXY.x) || 0,height: (maxXY.y - minXY.y) || 0};if (!onlyWidthHeight) {obj.left = minXY.x || 0;obj.top = minXY.y || 0;if (this.originX === 'center') {obj.left += obj.width / 2;}if (this.originX === 'right') {obj.left += obj.width;}if (this.originY === 'center') {obj.top += obj.height / 2;}if (this.originY === 'bottom') {obj.top += obj.height;}}return obj;},/* _TO_SVG_START_ *//*** Returns svg representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = [//jscs:disable validateIndentation'<g ','transform="', this.getSvgTransform(),'">\n'//jscs:enable validateIndentation];for (var i = 0, len = this._objects.length; i < len; i++) {markup.push(this._objects[i].toSVG(reviver));}markup.push('</g>\n');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** Returns requested property* @param {String} prop Property to get* @return {Any}*/get: function(prop) {if (prop in _lockProperties) {if (this[prop]) {return this[prop];}else {for (var i = 0, len = this._objects.length; i < len; i++) {if (this._objects[i][prop]) {return true;}}return false;}}else {if (prop in this.delegatedProperties) {return this._objects[0] && this._objects[0].get(prop);}return this[prop];}}});/*** Returns {@link fabric.Group} instance from an object representation* @static* @memberOf fabric.Group* @param {Object} object Object to create a group from* @param {Function} [callback] Callback to invoke when an group instance is created* @return {fabric.Group} An instance of fabric.Group*/fabric.Group.fromObject = function(object, callback) {fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {delete object.objects;callback && callback(new fabric.Group(enlivenedObjects, object));});};/*** Indicates that instances of this type are async* @static* @memberOf fabric.Group* @type Boolean* @default*/fabric.Group.async = true;})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var extend = fabric.util.object.extend;if (!global.fabric) {global.fabric = { };}if (global.fabric.Image) {fabric.warn('fabric.Image is already defined.');return;}/*** Image class* @class fabric.Image* @extends fabric.Object* @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images}* @see {@link fabric.Image#initialize} for constructor definition*/fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ {/*** Type of an object* @type String* @default*/type: 'image',/*** crossOrigin value (one of "", "anonymous", "allow-credentials")* @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes* @type String* @default*/crossOrigin: '',/*** AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute* This parameter defines how the picture is aligned to its viewport when image element width differs from image width.* @type String* @default*/alignX: 'none',/*** AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute* This parameter defines how the picture is aligned to its viewport when image element height differs from image height.* @type String* @default*/alignY: 'none',/*** meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice").* if meet the image is always fully visibile, if slice the viewport is always filled with image.* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute* @type String* @default*/meetOrSlice: 'meet',/*** private* contains last value of scaleX to detect* if the Image got resized after the last Render* @type Number*/_lastScaleX: 1,/*** private* contains last value of scaleY to detect* if the Image got resized after the last Render* @type Number*/_lastScaleY: 1,/*** Constructor* @param {HTMLImageElement | String} element Image element* @param {Object} [options] Options object* @return {fabric.Image} thisArg*/initialize: function(element, options) {options || (options = { });this.filters = [ ];this.resizeFilters = [ ];this.callSuper('initialize', options);this._initElement(element, options);this._initConfig(options);if (options.filters) {this.filters = options.filters;this.applyFilters();}},/*** Returns image element which this instance if based on* @return {HTMLImageElement} Image element*/getElement: function() {return this._element;},/*** Sets image element for this instance to a specified one.* If filters defined they are applied to new image.* You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area.* @param {HTMLImageElement} element* @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated* @param {Object} [options] Options object* @return {fabric.Image} thisArg* @chainable*/setElement: function(element, callback, options) {this._element = element;this._originalElement = element;this._initConfig(options);if (this.filters.length !== 0) {this.applyFilters(callback);}else if (callback) {callback();}return this;},/*** Sets crossOrigin value (on an instance and corresponding image element)* @return {fabric.Image} thisArg* @chainable*/setCrossOrigin: function(value) {this.crossOrigin = value;this._element.crossOrigin = value;return this;},/*** Returns original size of an image* @return {Object} Object with "width" and "height" properties*/getOriginalSize: function() {var element = this.getElement();return {width: element.width,height: element.height};},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_stroke: function(ctx) {ctx.save();this._setStrokeStyles(ctx);ctx.beginPath();ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height);ctx.closePath();ctx.restore();},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderDashedStroke: function(ctx) {var x = -this.width / 2,y = -this.height / 2,w = this.width,h = this.height;ctx.save();this._setStrokeStyles(ctx);ctx.beginPath();fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);ctx.closePath();ctx.restore();},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} Object representation of an instance*/toObject: function(propertiesToInclude) {return extend(this.callSuper('toObject', propertiesToInclude), {src: this._originalElement.src || this._originalElement._src,filters: this.filters.map(function(filterObj) {return filterObj && filterObj.toObject();}),crossOrigin: this.crossOrigin,alignX: this.alignX,alignY: this.alignY,meetOrSlice: this.meetOrSlice});},/* _TO_SVG_START_ *//*** Returns SVG representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = [], x = -this.width / 2, y = -this.height / 2,preserveAspectRatio = 'none';if (this.group && this.group.type === 'path-group') {x = this.left;y = this.top;}if (this.alignX !== 'none' && this.alignY !== 'none') {preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;}markup.push('<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n','<image xlink:href="', this.getSvgSrc(),'" x="', x, '" y="', y,'" style="', this.getSvgStyles(),// we're essentially moving origin of transformation from top/left corner to the center of the shape// by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left// so that object's center aligns with container's left/top'" width="', this.width,'" height="', this.height,'" preserveAspectRatio="', preserveAspectRatio, '"','></image>\n');if (this.stroke || this.strokeDashArray) {var origFill = this.fill;this.fill = null;markup.push('<rect ','x="', x, '" y="', y,'" width="', this.width, '" height="', this.height,'" style="', this.getSvgStyles(),'"/>\n');this.fill = origFill;}markup.push('</g>\n');return reviver ? reviver(markup.join('')) : markup.join('');},/* _TO_SVG_END_ *//*** Returns source of an image* @return {String} Source of an image*/getSrc: function() {if (this.getElement()) {return this.getElement().src || this.getElement()._src;}},/*** Sets source of an image* @param {String} src Source string (URL)* @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)* @param {Object} [options] Options object* @return {fabric.Image} thisArg* @chainable*/setSrc: function(src, callback, options) {fabric.util.loadImage(src, function(img) {return this.setElement(img, callback, options);}, this, options && options.crossOrigin);},/*** Returns string representation of an instance* @return {String} String representation of an instance*/toString: function() {return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';},/*** Returns a clone of an instance* @param {Function} callback Callback is invoked with a clone as a first argument* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output*/clone: function(callback, propertiesToInclude) {this.constructor.fromObject(this.toObject(propertiesToInclude), callback);},/*** Applies filters assigned to this image (from "filters" array)* @method applyFilters* @param {Function} callback Callback is invoked when all filters have been applied and new image is generated* @return {fabric.Image} thisArg* @chainable*/applyFilters: function(callback, filters, imgElement, forResizing) {filters = filters || this.filters;imgElement = imgElement || this._originalElement;if (!imgElement) {return;}var imgEl = imgElement,canvasEl = fabric.util.createCanvasElement(),replacement = fabric.util.createImage(),_this = this;canvasEl.width = imgEl.width;canvasEl.height = imgEl.height;canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height);if (filters.length === 0) {this._element = imgElement;callback && callback();return canvasEl;}filters.forEach(function(filter) {filter && filter.applyTo(canvasEl, filter.scaleX || _this.scaleX, filter.scaleY || _this.scaleY);if (!forResizing && filter && filter.type === 'Resize') {_this.width *= filter.scaleX;_this.height *= filter.scaleY;}});/** @ignore */replacement.width = canvasEl.width;replacement.height = canvasEl.height;if (fabric.isLikelyNode) {replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression);// onload doesn't fire in some node versions, so we invoke callback manually_this._element = replacement;!forResizing && (_this._filteredEl = replacement);callback && callback();}else {replacement.onload = function() {_this._element = replacement;!forResizing && (_this._filteredEl = replacement);callback && callback();replacement.onload = canvasEl = imgEl = null;};replacement.src = canvasEl.toDataURL('image/png');}return canvasEl;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx, noTransform) {var x, y, imageMargins = this._findMargins(), elementToDraw;x = (noTransform ? this.left : -this.width / 2);y = (noTransform ? this.top : -this.height / 2);if (this.meetOrSlice === 'slice') {ctx.beginPath();ctx.rect(x, y, this.width, this.height);ctx.clip();}if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) {this._lastScaleX = this.scaleX;this._lastScaleY = this.scaleY;elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true);}else {elementToDraw = this._element;}elementToDraw && ctx.drawImage(elementToDraw,x + imageMargins.marginX,y + imageMargins.marginY,imageMargins.width,imageMargins.height);this._renderStroke(ctx);},/*** @private, needed to check if image needs resize*/_needsResize: function() {return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);},/*** @private*/_findMargins: function() {var width = this.width, height = this.height, scales,scale, marginX = 0, marginY = 0;if (this.alignX !== 'none' || this.alignY !== 'none') {scales = [this.width / this._element.width, this.height / this._element.height];scale = this.meetOrSlice === 'meet'? Math.min.apply(null, scales) : Math.max.apply(null, scales);width = this._element.width * scale;height = this._element.height * scale;if (this.alignX === 'Mid') {marginX = (this.width - width) / 2;}if (this.alignX === 'Max') {marginX = this.width - width;}if (this.alignY === 'Mid') {marginY = (this.height - height) / 2;}if (this.alignY === 'Max') {marginY = this.height - height;}}return {width: width,height: height,marginX: marginX,marginY: marginY};},/*** @private*/_resetWidthHeight: function() {var element = this.getElement();this.set('width', element.width);this.set('height', element.height);},/*** The Image class's initialization method. This method is automatically* called by the constructor.* @private* @param {HTMLImageElement|String} element The element representing the image*/_initElement: function(element) {this.setElement(fabric.util.getById(element));fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);},/*** @private* @param {Object} [options] Options object*/_initConfig: function(options) {options || (options = { });this.setOptions(options);this._setWidthHeight(options);if (this._element && this.crossOrigin) {this._element.crossOrigin = this.crossOrigin;}},/*** @private* @param {Object} object Object with filters property* @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created*/_initFilters: function(object, callback) {if (object.filters && object.filters.length) {fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) {callback && callback(enlivenedObjects);}, 'fabric.Image.filters');}else {callback && callback();}},/*** @private* @param {Object} [options] Object with width/height properties*/_setWidthHeight: function(options) {this.width = 'width' in options? options.width: (this.getElement()? this.getElement().width || 0: 0);this.height = 'height' in options? options.height: (this.getElement()? this.getElement().height || 0: 0);},/*** Returns complexity of an instance* @return {Number} complexity of this instance*/complexity: function() {return 1;}});/*** Default CSS class name for canvas* @static* @type String* @default*/fabric.Image.CSS_CANVAS = 'canvas-img';/*** Alias for getSrc* @static*/fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;/*** Creates an instance of fabric.Image from its object representation* @static* @param {Object} object Object to create an instance from* @param {Function} [callback] Callback to invoke when an image instance is created*/fabric.Image.fromObject = function(object, callback) {fabric.util.loadImage(object.src, function(img) {fabric.Image.prototype._initFilters.call(object, object, function(filters) {object.filters = filters || [ ];var instance = new fabric.Image(img, object);callback && callback(instance);});}, null, object.crossOrigin);};/*** Creates an instance of fabric.Image from an URL string* @static* @param {String} url URL to create an image from* @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument)* @param {Object} [imgOptions] Options object*/fabric.Image.fromURL = function(url, callback, imgOptions) {fabric.util.loadImage(url, function(img) {callback && callback(new fabric.Image(img, imgOptions));}, null, imgOptions && imgOptions.crossOrigin);};/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement})* @static* @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement}*/fabric.Image.ATTRIBUTE_NAMES =fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' '));/*** Returns {@link fabric.Image} instance from an SVG element* @static* @param {SVGElement} element Element to parse* @param {Function} callback Callback to execute when fabric.Image object is created* @param {Object} [options] Options object* @return {fabric.Image} Instance of fabric.Image*/fabric.Image.fromElement = function(element, callback, options) {var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES),align = 'xMidYMid', meetOrSlice = 'meet', alignX, alignY, aspectRatioAttrs;if (parsedAttributes.preserveAspectRatio) {aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(' ');}if (aspectRatioAttrs && aspectRatioAttrs.length) {meetOrSlice = aspectRatioAttrs.pop();if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {align = meetOrSlice;meetOrSlice = 'meet';}else if (aspectRatioAttrs.length) {align = aspectRatioAttrs.pop();}}//divide align in alignX and alignYalignX = align !== 'none' ? align.slice(1, 4) : 'none';alignY = align !== 'none' ? align.slice(5, 8) : 'none';parsedAttributes.alignX = alignX;parsedAttributes.alignY = alignY;parsedAttributes.meetOrSlice = meetOrSlice;fabric.Image.fromURL(parsedAttributes['xlink:href'], callback,extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));};/* _FROM_SVG_END_ *//*** Indicates that instances of this type are async* @static* @type Boolean* @default*/fabric.Image.async = true;/*** Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9* @static* @type Number* @default*/fabric.Image.pngCompression = 1;})(typeof exports !== 'undefined' ? exports : this);fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {/*** @private* @return {Number} angle value*/_getAngleValueForStraighten: function() {var angle = this.getAngle() % 360;if (angle > 0) {return Math.round((angle - 1) / 90) * 90;}return Math.round(angle / 90) * 90;},/*** Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)* @return {fabric.Object} thisArg* @chainable*/straighten: function() {this.setAngle(this._getAngleValueForStraighten());return this;},/*** Same as {@link fabric.Object.prototype.straighten} but with animation* @param {Object} callbacks Object with callback functions* @param {Function} [callbacks.onComplete] Invoked on completion* @param {Function} [callbacks.onChange] Invoked on every step of animation* @return {fabric.Object} thisArg* @chainable*/fxStraighten: function(callbacks) {callbacks = callbacks || { };var empty = function() { },onComplete = callbacks.onComplete || empty,onChange = callbacks.onChange || empty,_this = this;fabric.util.animate({startValue: this.get('angle'),endValue: this._getAngleValueForStraighten(),duration: this.FX_DURATION,onChange: function(value) {_this.setAngle(value);onChange();},onComplete: function() {_this.setCoords();onComplete();},onStart: function() {_this.set('active', false);}});return this;}});fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {/*** Straightens object, then rerenders canvas* @param {fabric.Object} object Object to straighten* @return {fabric.Canvas} thisArg* @chainable*/straightenObject: function (object) {object.straighten();this.renderAll();return this;},/*** Same as {@link fabric.Canvas.prototype.straightenObject}, but animated* @param {fabric.Object} object Object to straighten* @return {fabric.Canvas} thisArg* @chainable*/fxStraightenObject: function (object) {object.fxStraighten({onChange: this.renderAll.bind(this)});return this;}});/*** @namespace fabric.Image.filters* @memberOf fabric.Image* @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters}* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}*/fabric.Image.filters = fabric.Image.filters || { };/*** Root filter class from which all filter classes inherit from* @class fabric.Image.filters.BaseFilter* @memberOf fabric.Image.filters*/fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'BaseFilter',/*** Constructor* @param {Object} [options] Options object*/initialize: function(options) {if (options) {this.setOptions(options);}},/*** Sets filter's properties from options* @param {Object} [options] Options object*/setOptions: function(options) {for (var prop in options) {this[prop] = options[prop];}},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return { type: this.type };},/*** Returns a JSON representation of an instance* @return {Object} JSON*/toJSON: function() {// delegate, not aliasreturn this.toObject();}});(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Brightness filter class* @class fabric.Image.filters.Brightness* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Brightness({* brightness: 200* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Brightness',/*** Constructor* @memberOf fabric.Image.filters.Brightness.prototype* @param {Object} [options] Options object* @param {Number} [options.brightness=0] Value to brighten the image up (0..255)*/initialize: function(options) {options = options || { };this.brightness = options.brightness || 0;},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,brightness = this.brightness;for (var i = 0, len = data.length; i < len; i += 4) {data[i] += brightness;data[i + 1] += brightness;data[i + 2] += brightness;}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {brightness: this.brightness});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness*/fabric.Image.filters.Brightness.fromObject = function(object) {return new fabric.Image.filters.Brightness(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a>* @class fabric.Image.filters.Convolute* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example <caption>Sharpen filter</caption>* var filter = new fabric.Image.filters.Convolute({* matrix: [ 0, -1, 0,* -1, 5, -1,* 0, -1, 0 ]* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));* @example <caption>Blur filter</caption>* var filter = new fabric.Image.filters.Convolute({* matrix: [ 1/9, 1/9, 1/9,* 1/9, 1/9, 1/9,* 1/9, 1/9, 1/9 ]* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));* @example <caption>Emboss filter</caption>* var filter = new fabric.Image.filters.Convolute({* matrix: [ 1, 1, 1,* 1, 0.7, -1,* -1, -1, -1 ]* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));* @example <caption>Emboss filter with opaqueness</caption>* var filter = new fabric.Image.filters.Convolute({* opaque: true,* matrix: [ 1, 1, 1,* 1, 0.7, -1,* -1, -1, -1 ]* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Convolute',/*** Constructor* @memberOf fabric.Image.filters.Convolute.prototype* @param {Object} [options] Options object* @param {Boolean} [options.opaque=false] Opaque value (true/false)* @param {Array} [options.matrix] Filter matrix*/initialize: function(options) {options = options || { };this.opaque = options.opaque;this.matrix = options.matrix || [0, 0, 0,0, 1, 0,0, 0, 0];var canvasEl = fabric.util.createCanvasElement();this.tmpCtx = canvasEl.getContext('2d');},/*** @private*/_createImageData: function(w, h) {return this.tmpCtx.createImageData(w, h);},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var weights = this.matrix,context = canvasEl.getContext('2d'),pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height),side = Math.round(Math.sqrt(weights.length)),halfSide = Math.floor(side/2),src = pixels.data,sw = pixels.width,sh = pixels.height,// pad output by the convolution matrixw = sw,h = sh,output = this._createImageData(w, h),dst = output.data,// go through the destination image pixelsalphaFac = this.opaque ? 1 : 0;for (var y = 0; y < h; y++) {for (var x = 0; x < w; x++) {var sy = y,sx = x,dstOff = (y * w + x) * 4,// calculate the weighed sum of the source image pixels that// fall under the convolution matrixr = 0, g = 0, b = 0, a = 0;for (var cy = 0; cy < side; cy++) {for (var cx = 0; cx < side; cx++) {var scy = sy + cy - halfSide,scx = sx + cx - halfSide;/* jshint maxdepth:5 */if (scy < 0 || scy > sh || scx < 0 || scx > sw) {continue;}var srcOff = (scy * sw + scx) * 4,wt = weights[cy * side + cx];r += src[srcOff] * wt;g += src[srcOff + 1] * wt;b += src[srcOff + 2] * wt;a += src[srcOff + 3] * wt;}}dst[dstOff] = r;dst[dstOff + 1] = g;dst[dstOff + 2] = b;dst[dstOff + 3] = a + alphaFac * (255 - a);}}context.putImageData(output, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {opaque: this.opaque,matrix: this.matrix});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute*/fabric.Image.filters.Convolute.fromObject = function(object) {return new fabric.Image.filters.Convolute(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** GradientTransparency filter class* @class fabric.Image.filters.GradientTransparency* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.GradientTransparency({* threshold: 200* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'GradientTransparency',/*** Constructor* @memberOf fabric.Image.filters.GradientTransparency.prototype* @param {Object} [options] Options object* @param {Number} [options.threshold=100] Threshold value*/initialize: function(options) {options = options || { };this.threshold = options.threshold || 100;},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,threshold = this.threshold,total = data.length;for (var i = 0, len = data.length; i < len; i += 4) {data[i + 3] = threshold + 255 * (total - i) / total;}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {threshold: this.threshold});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency*/fabric.Image.filters.GradientTransparency.fromObject = function(object) {return new fabric.Image.filters.GradientTransparency(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { });/*** Grayscale image filter class* @class fabric.Image.filters.Grayscale* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Grayscale();* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Grayscale',/*** Applies filter to canvas element* @memberOf fabric.Image.filters.Grayscale.prototype* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,len = imageData.width * imageData.height * 4,index = 0,average;while (index < len) {average = (data[index] + data[index + 1] + data[index + 2]) / 3;data[index] = average;data[index + 1] = average;data[index + 2] = average;index += 4;}context.putImageData(imageData, 0, 0);}});/*** Returns filter instance from an object representation* @static* @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale*/fabric.Image.filters.Grayscale.fromObject = function() {return new fabric.Image.filters.Grayscale();};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { });/*** Invert filter class* @class fabric.Image.filters.Invert* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Invert();* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Invert',/*** Applies filter to canvas element* @memberOf fabric.Image.filters.Invert.prototype* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,iLen = data.length, i;for (i = 0; i < iLen; i+=4) {data[i] = 255 - data[i];data[i + 1] = 255 - data[i + 1];data[i + 2] = 255 - data[i + 2];}context.putImageData(imageData, 0, 0);}});/*** Returns filter instance from an object representation* @static* @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert*/fabric.Image.filters.Invert.fromObject = function() {return new fabric.Image.filters.Invert();};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Mask filter class* See http://resources.aleph-1.com/mask/* @class fabric.Image.filters.Mask* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.Mask#initialize} for constructor definition*/fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Mask',/*** Constructor* @memberOf fabric.Image.filters.Mask.prototype* @param {Object} [options] Options object* @param {fabric.Image} [options.mask] Mask image object* @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3)*/initialize: function(options) {options = options || { };this.mask = options.mask;this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0;},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {if (!this.mask) {return;}var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,maskEl = this.mask.getElement(),maskCanvasEl = fabric.util.createCanvasElement(),channel = this.channel,i,iLen = imageData.width * imageData.height * 4;maskCanvasEl.width = maskEl.width;maskCanvasEl.height = maskEl.height;maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height);var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height),maskData = maskImageData.data;for (i = 0; i < iLen; i += 4) {data[i + 3] = maskData[i + channel];}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {mask: this.mask.toObject(),channel: this.channel});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @param {Function} [callback] Callback to invoke when a mask filter instance is created*/fabric.Image.filters.Mask.fromObject = function(object, callback) {fabric.util.loadImage(object.mask.src, function(img) {object.mask = new fabric.Image(img, object.mask);callback && callback(new fabric.Image.filters.Mask(object));});};/*** Indicates that instances of this type are async* @static* @type Boolean* @default*/fabric.Image.filters.Mask.async = true;})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Noise filter class* @class fabric.Image.filters.Noise* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.Noise#initialize} for constructor definition* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Noise({* noise: 700* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Noise',/*** Constructor* @memberOf fabric.Image.filters.Noise.prototype* @param {Object} [options] Options object* @param {Number} [options.noise=0] Noise value*/initialize: function(options) {options = options || { };this.noise = options.noise || 0;},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,noise = this.noise, rand;for (var i = 0, len = data.length; i < len; i += 4) {rand = (0.5 - Math.random()) * noise;data[i] += rand;data[i + 1] += rand;data[i + 2] += rand;}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {noise: this.noise});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise*/fabric.Image.filters.Noise.fromObject = function(object) {return new fabric.Image.filters.Noise(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Pixelate filter class* @class fabric.Image.filters.Pixelate* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Pixelate({* blocksize: 8* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Pixelate',/*** Constructor* @memberOf fabric.Image.filters.Pixelate.prototype* @param {Object} [options] Options object* @param {Number} [options.blocksize=4] Blocksize for pixelate*/initialize: function(options) {options = options || { };this.blocksize = options.blocksize || 4;},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,iLen = imageData.height,jLen = imageData.width,index, i, j, r, g, b, a;for (i = 0; i < iLen; i += this.blocksize) {for (j = 0; j < jLen; j += this.blocksize) {index = (i * 4) * jLen + (j * 4);r = data[index];g = data[index + 1];b = data[index + 2];a = data[index + 3];/*blocksize: 4[1,x,x,x,1][x,x,x,x,1][x,x,x,x,1][x,x,x,x,1][1,1,1,1,1]*/for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) {for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) {index = (_i * 4) * jLen + (_j * 4);data[index] = r;data[index + 1] = g;data[index + 2] = b;data[index + 3] = a;}}}}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {blocksize: this.blocksize});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate*/fabric.Image.filters.Pixelate.fromObject = function(object) {return new fabric.Image.filters.Pixelate(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Remove white filter class* @class fabric.Image.filters.RemoveWhite* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.RemoveWhite({* threshold: 40,* distance: 140* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'RemoveWhite',/*** Constructor* @memberOf fabric.Image.filters.RemoveWhite.prototype* @param {Object} [options] Options object* @param {Number} [options.threshold=30] Threshold value* @param {Number} [options.distance=20] Distance value*/initialize: function(options) {options = options || { };this.threshold = options.threshold || 30;this.distance = options.distance || 20;},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,threshold = this.threshold,distance = this.distance,limit = 255 - threshold,abs = Math.abs,r, g, b;for (var i = 0, len = data.length; i < len; i += 4) {r = data[i];g = data[i + 1];b = data[i + 2];if (r > limit &&g > limit &&b > limit &&abs(r - g) < distance &&abs(r - b) < distance &&abs(g - b) < distance) {data[i + 3] = 1;}}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {threshold: this.threshold,distance: this.distance});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite*/fabric.Image.filters.RemoveWhite.fromObject = function(object) {return new fabric.Image.filters.RemoveWhite(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { });/*** Sepia filter class* @class fabric.Image.filters.Sepia* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Sepia();* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Sepia',/*** Applies filter to canvas element* @memberOf fabric.Image.filters.Sepia.prototype* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,iLen = data.length, i, avg;for (i = 0; i < iLen; i+=4) {avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];data[i] = avg + 100;data[i + 1] = avg + 50;data[i + 2] = avg + 255;}context.putImageData(imageData, 0, 0);}});/*** Returns filter instance from an object representation* @static* @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia*/fabric.Image.filters.Sepia.fromObject = function() {return new fabric.Image.filters.Sepia();};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { });/*** Sepia2 filter class* @class fabric.Image.filters.Sepia2* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Sepia2();* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Sepia2',/*** Applies filter to canvas element* @memberOf fabric.Image.filters.Sepia.prototype* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,iLen = data.length, i, r, g, b;for (i = 0; i < iLen; i+=4) {r = data[i];g = data[i + 1];b = data[i + 2];data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351;data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203;data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140;}context.putImageData(imageData, 0, 0);}});/*** Returns filter instance from an object representation* @static* @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2*/fabric.Image.filters.Sepia2.fromObject = function() {return new fabric.Image.filters.Sepia2();};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Tint filter class* Adapted from <a href="https://github.com/mezzoblue/PaintbrushJS">https://github.com/mezzoblue/PaintbrushJS</a>* @class fabric.Image.filters.Tint* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link fabric.Image.filters.Tint#initialize} for constructor definition* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example <caption>Tint filter with hex color and opacity</caption>* var filter = new fabric.Image.filters.Tint({* color: '#3513B0',* opacity: 0.5* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));* @example <caption>Tint filter with rgba color</caption>* var filter = new fabric.Image.filters.Tint({* color: 'rgba(53, 21, 176, 0.5)'* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Tint',/*** Constructor* @memberOf fabric.Image.filters.Tint.prototype* @param {Object} [options] Options object* @param {String} [options.color=#000000] Color to tint the image with* @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1)*/initialize: function(options) {options = options || { };this.color = options.color || '#000000';this.opacity = typeof options.opacity !== 'undefined'? options.opacity: new fabric.Color(this.color).getAlpha();},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,iLen = data.length, i,tintR, tintG, tintB,r, g, b, alpha1,source;source = new fabric.Color(this.color).getSource();tintR = source[0] * this.opacity;tintG = source[1] * this.opacity;tintB = source[2] * this.opacity;alpha1 = 1 - this.opacity;for (i = 0; i < iLen; i+=4) {r = data[i];g = data[i + 1];b = data[i + 2];// alpha compositingdata[i] = tintR + r * alpha1;data[i + 1] = tintG + g * alpha1;data[i + 2] = tintB + b * alpha1;}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {color: this.color,opacity: this.opacity});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint*/fabric.Image.filters.Tint.fromObject = function(object) {return new fabric.Image.filters.Tint(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend;/*** Multiply filter class* Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a>* @class fabric.Image.filters.Multiply* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @example <caption>Multiply filter with hex color</caption>* var filter = new fabric.Image.filters.Multiply({* color: '#F0F'* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));* @example <caption>Multiply filter with rgb color</caption>* var filter = new fabric.Image.filters.Multiply({* color: 'rgb(53, 21, 176)'* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Multiply',/*** Constructor* @memberOf fabric.Image.filters.Multiply.prototype* @param {Object} [options] Options object* @param {String} [options.color=#000000] Color to multiply the image pixels with*/initialize: function(options) {options = options || { };this.color = options.color || '#000000';},/*** Applies filter to canvas element* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,iLen = data.length, i,source;source = new fabric.Color(this.color).getSource();for (i = 0; i < iLen; i+=4) {data[i] *= source[0] / 255;data[i + 1] *= source[1] / 255;data[i + 2] *= source[2] / 255;}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return extend(this.callSuper('toObject'), {color: this.color});}});/*** Returns filter instance from an object representation* @static* @param {Object} object Object to create an instance from* @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply*/fabric.Image.filters.Multiply.fromObject = function(object) {return new fabric.Image.filters.Multiply(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric;/*** Color Blend filter class* @class fabric.Image.filter.Blend* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @example* var filter = new fabric.Image.filters.Blend({* color: '#000',* mode: 'multiply'* });** var filter = new fabric.Image.filters.Blend({* image: fabricImageObject,* mode: 'multiply',* alpha: 0.5* });* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Blend = fabric.util.createClass({type: 'Blend',initialize: function(options) {options = options || {};this.color = options.color || '#000';this.image = options.image || false;this.mode = options.mode || 'multiply';this.alpha = options.alpha || 1;},applyTo: function(canvasEl) {var context = canvasEl.getContext('2d'),imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),data = imageData.data,tr, tg, tb,r, g, b,_r, _g, _b,source,isImage = false;if (this.image) {// Blend imagesisImage = true;var _el = fabric.util.createCanvasElement();_el.width = this.image.width;_el.height = this.image.height;var tmpCanvas = new fabric.StaticCanvas(_el);tmpCanvas.add(this.image);var context2 = tmpCanvas.getContext('2d');source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data;}else {// Blend colorsource = new fabric.Color(this.color).getSource();tr = source[0] * this.alpha;tg = source[1] * this.alpha;tb = source[2] * this.alpha;}for (var i = 0, len = data.length; i < len; i += 4) {r = data[i];g = data[i + 1];b = data[i + 2];if (isImage) {tr = source[i] * this.alpha;tg = source[i + 1] * this.alpha;tb = source[i + 2] * this.alpha;}switch (this.mode) {case 'multiply':data[i] = r * tr / 255;data[i + 1] = g * tg / 255;data[i + 2] = b * tb / 255;break;case 'screen':data[i] = 1 - (1 - r) * (1 - tr);data[i + 1] = 1 - (1 - g) * (1 - tg);data[i + 2] = 1 - (1 - b) * (1 - tb);break;case 'add':data[i] = Math.min(255, r + tr);data[i + 1] = Math.min(255, g + tg);data[i + 2] = Math.min(255, b + tb);break;case 'diff':case 'difference':data[i] = Math.abs(r - tr);data[i + 1] = Math.abs(g - tg);data[i + 2] = Math.abs(b - tb);break;case 'subtract':_r = r - tr;_g = g - tg;_b = b - tb;data[i] = (_r < 0) ? 0 : _r;data[i + 1] = (_g < 0) ? 0 : _g;data[i + 2] = (_b < 0) ? 0 : _b;break;case 'darken':data[i] = Math.min(r, tr);data[i + 1] = Math.min(g, tg);data[i + 2] = Math.min(b, tb);break;case 'lighten':data[i] = Math.max(r, tr);data[i + 1] = Math.max(g, tg);data[i + 2] = Math.max(b, tb);break;}}context.putImageData(imageData, 0, 0);},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return {color: this.color,image: this.image,mode: this.mode,alpha: this.alpha};}});fabric.Image.filters.Blend.fromObject = function(object) {return new fabric.Image.filters.Blend(object);};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor,sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin,ceil = Math.ceil;/*** Resize image filter class* @class fabric.Image.filters.Resize* @memberOf fabric.Image.filters* @extends fabric.Image.filters.BaseFilter* @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}* @example* var filter = new fabric.Image.filters.Resize();* object.filters.push(filter);* object.applyFilters(canvas.renderAll.bind(canvas));*/fabric.Image.filters.Resize = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ {/*** Filter type* @param {String} type* @default*/type: 'Resize',/*** Resize type* @param {String} resizeType* @default*/resizeType: 'hermite',/*** Scale factor for resizing, x axis* @param {Number} scaleX* @default*/scaleX: 0,/*** Scale factor for resizing, y axis* @param {Number} scaleY* @default*/scaleY: 0,/*** LanczosLobes parameter for lanczos filter* @param {Number} lanczosLobes* @default*/lanczosLobes: 3,/*** Applies filter to canvas element* @memberOf fabric.Image.filters.Resize.prototype* @param {Object} canvasEl Canvas element to apply filter to*/applyTo: function(canvasEl, scaleX, scaleY) {this.rcpScaleX = 1 / scaleX;this.rcpScaleY = 1 / scaleY;var oW = canvasEl.width, oH = canvasEl.height,dW = round(oW * scaleX), dH = round(oH * scaleY),imageData;if (this.resizeType === 'sliceHack') {imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH);}if (this.resizeType === 'hermite') {imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH);}if (this.resizeType === 'bilinear') {imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH);}if (this.resizeType === 'lanczos') {imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH);}canvasEl.width = dW;canvasEl.height = dH;canvasEl.getContext('2d').putImageData(imageData, 0, 0);},sliceByTwo: function(canvasEl, width, height, newWidth, newHeight) {var context = canvasEl.getContext('2d'), imageData,multW = 0.5, multH = 0.5, signW = 1, signH = 1,doneW = false, doneH = false, stepW = width, stepH = height,tmpCanvas = fabric.util.createCanvasElement(),tmpCtx = tmpCanvas.getContext('2d');newWidth = floor(newWidth);newHeight = floor(newHeight);tmpCanvas.width = max(newWidth, width);tmpCanvas.height = max(newHeight, height);if (newWidth > width) {multW = 2;signW = -1;}if (newHeight > height) {multH = 2;signH = -1;}imageData = context.getImageData(0, 0, width, height);canvasEl.width = max(newWidth, width);canvasEl.height = max(newHeight, height);context.putImageData(imageData, 0, 0);while (!doneW || !doneH) {width = stepW;height = stepH;if (newWidth * signW < floor(stepW * multW * signW)) {stepW = floor(stepW * multW);}else {stepW = newWidth;doneW = true;}if (newHeight * signH < floor(stepH * multH * signH)) {stepH = floor(stepH * multH);}else {stepH = newHeight;doneH = true;}imageData = context.getImageData(0, 0, width, height);tmpCtx.putImageData(imageData, 0, 0);context.clearRect(0, 0, stepW, stepH);context.drawImage(tmpCanvas, 0, 0, width, height, 0, 0, stepW, stepH);}return context.getImageData(0, 0, newWidth, newHeight);},lanczosResize: function(canvasEl, oW, oH, dW, dH) {function lanczosCreate(lobes) {return function(x) {if (x > lobes) {return 0;}x *= Math.PI;if (abs(x) < 1e-16) {return 1;}var xx = x / lobes;return sin(x) * sin(xx) / x / xx;};}function process(u) {var v, i, weight, idx, a, red, green,blue, alpha, fX, fY;center.x = (u + 0.5) * ratioX;icenter.x = floor(center.x);for (v = 0; v < dH; v++) {center.y = (v + 0.5) * ratioY;icenter.y = floor(center.y);a = 0, red = 0, green = 0, blue = 0, alpha = 0;for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) {if (i < 0 || i >= oW) {continue;}fX = floor(1000 * abs(i - center.x));if (!cacheLanc[fX]) {cacheLanc[fX] = { };}for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) {if (j < 0 || j >= oH) {continue;}fY = floor(1000 * abs(j - center.y));if (!cacheLanc[fX][fY]) {cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000);}weight = cacheLanc[fX][fY];if (weight > 0) {idx = (j * oW + i) * 4;a += weight;red += weight * srcData[idx];green += weight * srcData[idx + 1];blue += weight * srcData[idx + 2];alpha += weight * srcData[idx + 3];}}}idx = (v * dW + u) * 4;destData[idx] = red / a;destData[idx + 1] = green / a;destData[idx + 2] = blue / a;destData[idx + 3] = alpha / a;}if (++u < dW) {return process(u);}else {return destImg;}}var context = canvasEl.getContext('2d'),srcImg = context.getImageData(0, 0, oW, oH),destImg = context.getImageData(0, 0, dW, dH),srcData = srcImg.data, destData = destImg.data,lanczos = lanczosCreate(this.lanczosLobes),ratioX = this.rcpScaleX, ratioY = this.rcpScaleY,rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY,range2X = ceil(ratioX * this.lanczosLobes / 2),range2Y = ceil(ratioY * this.lanczosLobes / 2),cacheLanc = { }, center = { }, icenter = { };return process(0);},bilinearFiltering: function(canvasEl, w, h, w2, h2) {var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl,color, offset = 0, origPix, ratioX = this.rcpScaleX,ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'),w4 = 4 * (w - 1), img = context.getImageData(0, 0, w, h),pixels = img.data, destImage = context.getImageData(0, 0, w2, h2),destPixels = destImage.data;for (i = 0; i < h2; i++) {for (j = 0; j < w2; j++) {x = floor(ratioX * j);y = floor(ratioY * i);xDiff = ratioX * j - x;yDiff = ratioY * i - y;origPix = 4 * (y * w + x);for (chnl = 0; chnl < 4; chnl++) {a = pixels[origPix + chnl];b = pixels[origPix + 4 + chnl];c = pixels[origPix + w4 + chnl];d = pixels[origPix + w4 + 4 + chnl];color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) +c * yDiff * (1 - xDiff) + d * xDiff * yDiff;destPixels[offset++] = color;}}}return destImage;},hermiteFastResize: function(canvasEl, oW, oH, dW, dH) {var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY,ratioWHalf = ceil(ratioW / 2),ratioHHalf = ceil(ratioH / 2),context = canvasEl.getContext('2d'),img = context.getImageData(0, 0, oW, oH), data = img.data,img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data;for (var j = 0; j < dH; j++) {for (var i = 0; i < dW; i++) {var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0,gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH;for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) {var dy = abs(centerY - (yy + 0.5)) / ratioHHalf,centerX = (i + 0.5) * ratioW, w0 = dy * dy;for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) {var dx = abs(centerX - (xx + 0.5)) / ratioWHalf,w = sqrt(w0 + dx * dx);/*jshint maxdepth:5 */if (w > 1 && w < -1) {continue;}//hermite filterweight = 2 * w * w * w - 3 * w * w + 1;if (weight > 0) {dx = 4 * (xx + yy * oW);//alphagxA += weight * data[dx + 3];weightsAlpha += weight;//colors/*jshint maxdepth:6 */if (data[dx + 3] < 255) {weight = weight * data[dx + 3] / 250;}/*jshint maxdepth:5 */gxR += weight * data[dx];gxG += weight * data[dx + 1];gxB += weight * data[dx + 2];weights += weight;}/*jshint maxdepth:4 */}}data2[x2] = gxR / weights;data2[x2 + 1] = gxG / weights;data2[x2 + 2] = gxB / weights;data2[x2 + 3] = gxA / weightsAlpha;}}return img2;},/*** Returns object representation of an instance* @return {Object} Object representation of an instance*/toObject: function() {return {type: this.type,scaleX: this.scaleX,scaley: this.scaleY,resizeType: this.resizeType,lanczosLobes: this.lanczosLobes};}});/*** Returns filter instance from an object representation* @static* @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize*/fabric.Image.filters.Resize.fromObject = function() {return new fabric.Image.filters.Resize();};})(typeof exports !== 'undefined' ? exports : this);(function(global) {'use strict';var fabric = global.fabric || (global.fabric = { }),extend = fabric.util.object.extend,clone = fabric.util.object.clone,toFixed = fabric.util.toFixed,supportsLineDash = fabric.StaticCanvas.supports('setLineDash');if (fabric.Text) {fabric.warn('fabric.Text is already defined');return;}var stateProperties = fabric.Object.prototype.stateProperties.concat();stateProperties.push('fontFamily','fontWeight','fontSize','text','textDecoration','textAlign','fontStyle','lineHeight','textBackgroundColor');/*** Text class* @class fabric.Text* @extends fabric.Object* @return {fabric.Text} thisArg* @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text}* @see {@link fabric.Text#initialize} for constructor definition*/fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ {/*** Properties which when set cause object to change dimensions* @type Object* @private*/_dimensionAffectingProps: {fontSize: true,fontWeight: true,fontFamily: true,fontStyle: true,lineHeight: true,stroke: true,strokeWidth: true,text: true,textAlign: true},/*** @private*/_reNewline: /\r?\n/,/*** Retrieves object's fontSize* @method getFontSize* @memberOf fabric.Text.prototype* @return {String} Font size (in pixels)*//*** Sets object's fontSize* @method setFontSize* @memberOf fabric.Text.prototype* @param {Number} fontSize Font size (in pixels)* @return {fabric.Text}* @chainable*//*** Retrieves object's fontWeight* @method getFontWeight* @memberOf fabric.Text.prototype* @return {(String|Number)} Font weight*//*** Sets object's fontWeight* @method setFontWeight* @memberOf fabric.Text.prototype* @param {(Number|String)} fontWeight Font weight* @return {fabric.Text}* @chainable*//*** Retrieves object's fontFamily* @method getFontFamily* @memberOf fabric.Text.prototype* @return {String} Font family*//*** Sets object's fontFamily* @method setFontFamily* @memberOf fabric.Text.prototype* @param {String} fontFamily Font family* @return {fabric.Text}* @chainable*//*** Retrieves object's text* @method getText* @memberOf fabric.Text.prototype* @return {String} text*//*** Sets object's text* @method setText* @memberOf fabric.Text.prototype* @param {String} text Text* @return {fabric.Text}* @chainable*//*** Retrieves object's textDecoration* @method getTextDecoration* @memberOf fabric.Text.prototype* @return {String} Text decoration*//*** Sets object's textDecoration* @method setTextDecoration* @memberOf fabric.Text.prototype* @param {String} textDecoration Text decoration* @return {fabric.Text}* @chainable*//*** Retrieves object's fontStyle* @method getFontStyle* @memberOf fabric.Text.prototype* @return {String} Font style*//*** Sets object's fontStyle* @method setFontStyle* @memberOf fabric.Text.prototype* @param {String} fontStyle Font style* @return {fabric.Text}* @chainable*//*** Retrieves object's lineHeight* @method getLineHeight* @memberOf fabric.Text.prototype* @return {Number} Line height*//*** Sets object's lineHeight* @method setLineHeight* @memberOf fabric.Text.prototype* @param {Number} lineHeight Line height* @return {fabric.Text}* @chainable*//*** Retrieves object's textAlign* @method getTextAlign* @memberOf fabric.Text.prototype* @return {String} Text alignment*//*** Sets object's textAlign* @method setTextAlign* @memberOf fabric.Text.prototype* @param {String} textAlign Text alignment* @return {fabric.Text}* @chainable*//*** Retrieves object's textBackgroundColor* @method getTextBackgroundColor* @memberOf fabric.Text.prototype* @return {String} Text background color*//*** Sets object's textBackgroundColor* @method setTextBackgroundColor* @memberOf fabric.Text.prototype* @param {String} textBackgroundColor Text background color* @return {fabric.Text}* @chainable*//*** Type of an object* @type String* @default*/type: 'text',/*** Font size (in pixels)* @type Number* @default*/fontSize: 40,/*** Font weight (e.g. bold, normal, 400, 600, 800)* @type {(Number|String)}* @default*/fontWeight: 'normal',/*** Font family* @type String* @default*/fontFamily: 'Times New Roman',/*** Text decoration Possible values: "", "underline", "overline" or "line-through".* @type String* @default*/textDecoration: '',/*** Text alignment. Possible values: "left", "center", or "right".* @type String* @default*/textAlign: 'left',/*** Font style . Possible values: "", "normal", "italic" or "oblique".* @type String* @default*/fontStyle: '',/*** Line height* @type Number* @default*/lineHeight: 1.16,/*** Background color of text lines* @type String* @default*/textBackgroundColor: '',/*** List of properties to consider when checking if* state of an object is changed ({@link fabric.Object#hasStateChanged})* as well as for history (undo/redo) purposes* @type Array*/stateProperties: stateProperties,/*** When defined, an object is rendered via stroke and this property specifies its color.* <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6* @type String* @default*/stroke: null,/*** Shadow object representing shadow of this shape.* <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11* @type fabric.Shadow* @default*/shadow: null,/*** @private*/_fontSizeFraction: 0.25,/*** Text Line proportion to font Size (in pixels)* @type Number* @default*/_fontSizeMult: 1.13,/*** Constructor* @param {String} text Text string* @param {Object} [options] Options object* @return {fabric.Text} thisArg*/initialize: function(text, options) {options = options || { };this.text = text;this.__skipDimension = true;this.setOptions(options);this.__skipDimension = false;this._initDimensions();},/*** Renders text object on offscreen canvas, so that it would get dimensions* @private*/_initDimensions: function(ctx) {if (this.__skipDimension) {return;}if (!ctx) {ctx = fabric.util.createCanvasElement().getContext('2d');this._setTextStyles(ctx);}this._textLines = this.text.split(this._reNewline);this._clearCache();var currentTextAlign = this.textAlign;this.textAlign = 'left';this.width = this._getTextWidth(ctx);this.textAlign = currentTextAlign;this.height = this._getTextHeight(ctx);},/*** Returns string representation of an instance* @return {String} String representation of text object*/toString: function() {return '#<fabric.Text (' + this.complexity() +'): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>';},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx) {this.clipTo && fabric.util.clipContext(this, ctx);this._renderTextBackground(ctx);this._renderText(ctx);this._renderTextDecoration(ctx);this.clipTo && ctx.restore();},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderText: function(ctx) {ctx.save();this._translateForTextAlign(ctx);this._setOpacity(ctx);this._setShadow(ctx);this._setupCompositeOperation(ctx);this._renderTextFill(ctx);this._renderTextStroke(ctx);this._restoreCompositeOperation(ctx);this._removeShadow(ctx);ctx.restore();},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_translateForTextAlign: function(ctx) {if (this.textAlign !== 'left' && this.textAlign !== 'justify') {ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0);}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_setTextStyles: function(ctx) {ctx.textBaseline = 'alphabetic';if (!this.skipTextAlign) {ctx.textAlign = this.textAlign;}ctx.font = this._getFontDeclaration();},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @return {Number} Height of fabric.Text object*/_getTextHeight: function() {return this._textLines.length * this._getHeightOfLine();},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @return {Number} Maximum width of fabric.Text object*/_getTextWidth: function(ctx) {var maxWidth = this._getLineWidth(ctx, 0);for (var i = 1, len = this._textLines.length; i < len; i++) {var currentLineWidth = this._getLineWidth(ctx, i);if (currentLineWidth > maxWidth) {maxWidth = currentLineWidth;}}return maxWidth;},/*** @private* @param {String} method Method name ("fillText" or "strokeText")* @param {CanvasRenderingContext2D} ctx Context to render on* @param {String} chars Chars to render* @param {Number} left Left position of text* @param {Number} top Top position of text*/_renderChars: function(method, ctx, chars, left, top) {ctx[method](chars, left, top);},/*** @private* @param {String} method Method name ("fillText" or "strokeText")* @param {CanvasRenderingContext2D} ctx Context to render on* @param {String} line Text to render* @param {Number} left Left position of text* @param {Number} top Top position of text* @param {Number} lineIndex Index of a line in a text*/_renderTextLine: function(method, ctx, line, left, top, lineIndex) {// lift the line by quarter of fontSizetop -= this.fontSize * this._fontSizeFraction;// short-circuitif (this.textAlign !== 'justify') {this._renderChars(method, ctx, line, left, top, lineIndex);return;}var lineWidth = this._getLineWidth(ctx, lineIndex),totalWidth = this.width;if (totalWidth >= lineWidth) {// stretch the linevar words = line.split(/\s+/),wordsWidth = this._getWidthOfWords(ctx, line, lineIndex),widthDiff = totalWidth - wordsWidth,numSpaces = words.length - 1,spaceWidth = widthDiff / numSpaces,leftOffset = 0;for (var i = 0, len = words.length; i < len; i++) {this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex);leftOffset += ctx.measureText(words[i]).width + spaceWidth;}}else {this._renderChars(method, ctx, line, left, top, lineIndex);}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Number} line*/_getWidthOfWords: function (ctx, line) {return ctx.measureText(line.replace(/\s+/g, '')).width;},/*** @private* @return {Number} Left offset*/_getLeftOffset: function() {return -this.width / 2;},/*** @private* @return {Number} Top offset*/_getTopOffset: function() {return -this.height / 2;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderTextFill: function(ctx) {if (!this.fill && !this._skipFillStrokeCheck) {return;}var lineHeights = 0;for (var i = 0, len = this._textLines.length; i < len; i++) {var heightOfLine = this._getHeightOfLine(ctx, i),maxHeight = heightOfLine / this.lineHeight;this._renderTextLine('fillText',ctx,this._textLines[i],this._getLeftOffset(),this._getTopOffset() + lineHeights + maxHeight,i);lineHeights += heightOfLine;}if (this.shadow && !this.shadow.affectStroke) {this._removeShadow(ctx);}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderTextStroke: function(ctx) {if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) {return;}var lineHeights = 0;ctx.save();if (this.strokeDashArray) {// Spec requires the concatenation of two copies the dash list when the number of elements is oddif (1 & this.strokeDashArray.length) {this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);}supportsLineDash && ctx.setLineDash(this.strokeDashArray);}ctx.beginPath();for (var i = 0, len = this._textLines.length; i < len; i++) {var heightOfLine = this._getHeightOfLine(ctx, i),maxHeight = heightOfLine / this.lineHeight;this._renderTextLine('strokeText',ctx,this._textLines[i],this._getLeftOffset(),this._getTopOffset() + lineHeights + maxHeight,i);lineHeights += heightOfLine;}ctx.closePath();ctx.restore();},_getHeightOfLine: function() {return this.fontSize * this._fontSizeMult * this.lineHeight;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Array} textLines Array of all text lines*/_renderTextBackground: function(ctx) {this._renderTextBoxBackground(ctx);this._renderTextLinesBackground(ctx);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderTextBoxBackground: function(ctx) {if (!this.backgroundColor) {return;}ctx.save();ctx.fillStyle = this.backgroundColor;ctx.fillRect(this._getLeftOffset(),this._getTopOffset(),this.width,this.height);ctx.restore();},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderTextLinesBackground: function(ctx) {var lineTopOffset = 0, heightOfLine = this._getHeightOfLine();if (!this.textBackgroundColor) {return;}ctx.save();ctx.fillStyle = this.textBackgroundColor;for (var i = 0, len = this._textLines.length; i < len; i++) {if (this._textLines[i] !== '') {var lineWidth = this._getLineWidth(ctx, i),lineLeftOffset = this._getLineLeftOffset(lineWidth);ctx.fillRect(this._getLeftOffset() + lineLeftOffset,this._getTopOffset() + lineTopOffset,lineWidth,this.fontSize * this._fontSizeMult);}lineTopOffset += heightOfLine;}ctx.restore();},/*** @private* @param {Number} lineWidth Width of text line* @return {Number} Line left offset*/_getLineLeftOffset: function(lineWidth) {if (this.textAlign === 'center') {return (this.width - lineWidth) / 2;}if (this.textAlign === 'right') {return this.width - lineWidth;}return 0;},/*** @private*/_clearCache: function() {this.__lineWidths = [ ];this.__lineHeights = [ ];this.__lineOffsets = [ ];},/*** @private*/_shouldClearCache: function() {var shouldClear = false;for (var prop in this._dimensionAffectingProps) {if (this['__' + prop] !== this[prop]) {this['__' + prop] = this[prop];shouldClear = true;}}return shouldClear;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @return {Number} Line width*/_getLineWidth: function(ctx, lineIndex) {if (this.__lineWidths[lineIndex]) {return this.__lineWidths[lineIndex];}this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width;return this.__lineWidths[lineIndex];},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderTextDecoration: function(ctx) {if (!this.textDecoration) {return;}var halfOfVerticalBox = this.height / 2,_this = this, offsets = [];/** @ignore */function renderLinesAtOffset(offsets) {var i, lineHeight = 0, len, j, oLen;for (i = 0, len = _this._textLines.length; i < len; i++) {var lineWidth = _this._getLineWidth(ctx, i),lineLeftOffset = _this._getLineLeftOffset(lineWidth),heightOfLine = _this._getHeightOfLine(ctx, i);for (j = 0, oLen = offsets.length; j < oLen; j++) {ctx.fillRect(_this._getLeftOffset() + lineLeftOffset,lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox,lineWidth,_this.fontSize / 15);}lineHeight += heightOfLine;}}if (this.textDecoration.indexOf('underline') > -1) {offsets.push(0.85); // 1 - 3/16}if (this.textDecoration.indexOf('line-through') > -1) {offsets.push(0.43);}if (this.textDecoration.indexOf('overline') > -1) {offsets.push(-0.12);}if (offsets.length > 0) {renderLinesAtOffset(offsets);}},/*** @private*/_getFontDeclaration: function() {return [// node-canvas needs "weight style", while browsers need "style weight"(fabric.isLikelyNode ? this.fontWeight : this.fontStyle),(fabric.isLikelyNode ? this.fontStyle : this.fontWeight),this.fontSize + 'px',(fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily)].join(' ');},/*** Renders text instance on a specified context* @param {CanvasRenderingContext2D} ctx Context to render on*/render: function(ctx, noTransform) {// do not render if object is not visibleif (!this.visible) {return;}ctx.save();this._setTextStyles(ctx);if (this._shouldClearCache()) {this._initDimensions(ctx);}if (!noTransform) {this.transform(ctx);}this._setStrokeStyles(ctx);this._setFillStyles(ctx);if (this.transformMatrix) {ctx.transform.apply(ctx, this.transformMatrix);}if (this.group && this.group.type === 'path-group') {ctx.translate(this.left, this.top);}this._render(ctx);ctx.restore();},/*** Returns object representation of an instance* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} Object representation of an instance*/toObject: function(propertiesToInclude) {var object = extend(this.callSuper('toObject', propertiesToInclude), {text: this.text,fontSize: this.fontSize,fontWeight: this.fontWeight,fontFamily: this.fontFamily,fontStyle: this.fontStyle,lineHeight: this.lineHeight,textDecoration: this.textDecoration,textAlign: this.textAlign,textBackgroundColor: this.textBackgroundColor});if (!this.includeDefaultValues) {this._removeDefaultValues(object);}return object;},/* _TO_SVG_START_ *//*** Returns SVG representation of an instance* @param {Function} [reviver] Method for further parsing of svg representation.* @return {String} svg representation of an instance*/toSVG: function(reviver) {var markup = this._createBaseSVGMarkup(),offsets = this._getSVGLeftTopOffsets(this.ctx),textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);this._wrapSVGTextAndBg(markup, textAndBg);return reviver ? reviver(markup.join('')) : markup.join('');},/*** @private*/_getSVGLeftTopOffsets: function(ctx) {var lineTop = this._getHeightOfLine(ctx, 0),textLeft = -this.width / 2,textTop = 0;return {textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0),textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0),lineTop: lineTop};},/*** @private*/_wrapSVGTextAndBg: function(markup, textAndBg) {markup.push('\t<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',textAndBg.textBgRects.join(''),'\t\t<text ',(this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ': ''),(this.fontSize ? 'font-size="' + this.fontSize + '" ': ''),(this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''),(this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''),(this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''),'style="', this.getSvgStyles(), '" >',textAndBg.textSpans.join(''),'</text>\n','\t</g>\n');},/*** @private* @param {Number} textTopOffset Text top offset* @param {Number} textLeftOffset Text left offset* @return {Object}*/_getSVGTextAndBg: function(textTopOffset, textLeftOffset) {var textSpans = [ ],textBgRects = [ ],height = 0;// bounding-box backgroundthis._setSVGBg(textBgRects);// text and text-backgroundfor (var i = 0, len = this._textLines.length; i < len; i++) {if (this.textBackgroundColor) {this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height);}this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects);height += this._getHeightOfLine(this.ctx, i);}return {textSpans: textSpans,textBgRects: textBgRects};},_setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) {var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction)- textTopOffset + height - this.height / 2;textSpans.push('<tspan x="',toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4), '" ','y="',toFixed(yPos, 4),'" ',// doing this on <tspan> elements since setting opacity// on containing <text> one doesn't work in Illustratorthis._getFillAttributes(this.fill), '>',fabric.util.string.escapeXml(this._textLines[i]),'</tspan>');},_setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) {textBgRects.push('\t\t<rect ',this._getFillAttributes(this.textBackgroundColor),' x="',toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4),'" y="',toFixed(height - this.height / 2, 4),'" width="',toFixed(this.__lineWidths[i], 4),'" height="',toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, 4),'"></rect>\n');},_setSVGBg: function(textBgRects) {if (this.backgroundColor) {textBgRects.push('\t\t<rect ',this._getFillAttributes(this.backgroundColor),' x="',toFixed(-this.width / 2, 4),'" y="',toFixed(-this.height / 2, 4),'" width="',toFixed(this.width, 4),'" height="',toFixed(this.height, 4),'"></rect>\n');}},/*** Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values* we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1** @private* @param {Any} value* @return {String}*/_getFillAttributes: function(value) {var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : '';if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) {return 'fill="' + value + '"';}return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"';},/* _TO_SVG_END_ *//*** Sets specified property to a specified value* @param {String} key* @param {Any} value* @return {fabric.Text} thisArg* @chainable*/_set: function(key, value) {this.callSuper('_set', key, value);if (key in this._dimensionAffectingProps) {this._initDimensions();this.setCoords();}},/*** Returns complexity of an instance* @return {Number} complexity*/complexity: function() {return 1;}});/* _FROM_SVG_START_ *//*** List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement})* @static* @memberOf fabric.Text* @see: http://www.w3.org/TR/SVG/text.html#TextElement*/fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' '));/*** Default SVG font size* @static* @memberOf fabric.Text*/fabric.Text.DEFAULT_SVG_FONT_SIZE = 16;/*** Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)* @static* @memberOf fabric.Text* @param {SVGElement} element Element to parse* @param {Object} [options] Options object* @return {fabric.Text} Instance of fabric.Text*/fabric.Text.fromElement = function(element, options) {if (!element) {return null;}var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);options.top = options.top || 0;options.left = options.left || 0;if ('dx' in parsedAttributes) {options.left += parsedAttributes.dx;}if ('dy' in parsedAttributes) {options.top += parsedAttributes.dy;}if (!('fontSize' in options)) {options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;}if (!options.originX) {options.originX = 'left';}var textContent = element.textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '),text = new fabric.Text(textContent, options),/*Adjust positioning:x/y attributes in SVG correspond to the bottom-left corner of text bounding boxtop/left properties in Fabric correspond to center point of text bounding box*/offX = 0;if (text.originX === 'left') {offX = text.getWidth() / 2;}if (text.originX === 'right') {offX = -text.getWidth() / 2;}text.set({left: text.getLeft() + offX,top: text.getTop() - text.getHeight() / 2 + text.fontSize * (0.18 + text._fontSizeFraction) /* 0.3 is the old lineHeight */});return text;};/* _FROM_SVG_END_ *//*** Returns fabric.Text instance from an object representation* @static* @memberOf fabric.Text* @param {Object} object Object to create an instance from* @return {fabric.Text} Instance of fabric.Text*/fabric.Text.fromObject = function(object) {return new fabric.Text(object.text, clone(object));};fabric.util.createAccessors(fabric.Text);})(typeof exports !== 'undefined' ? exports : this);(function() {var clone = fabric.util.object.clone;/*** IText class (introduced in <b>v1.4</b>) Events are also fired with "text:"* prefix when observing canvas.* @class fabric.IText* @extends fabric.Text* @mixes fabric.Observable** @fires changed* @fires selection:changed* @fires editing:entered* @fires editing:exited** @return {fabric.IText} thisArg* @see {@link fabric.IText#initialize} for constructor definition** <p>Supported key combinations:</p>* <pre>* Move cursor: left, right, up, down* Select character: shift + left, shift + right* Select text vertically: shift + up, shift + down* Move cursor by word: alt + left, alt + right* Select words: shift + alt + left, shift + alt + right* Move cursor to line start/end: cmd + left, cmd + right or home, end* Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end* Jump to start/end of text: cmd + up, cmd + down* Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown* Delete character: backspace* Delete word: alt + backspace* Delete line: cmd + backspace* Forward delete: delete* Copy text: ctrl/cmd + c* Paste text: ctrl/cmd + v* Cut text: ctrl/cmd + x* Select entire text: ctrl/cmd + a* Quit editing tab or esc* </pre>** <p>Supported mouse/touch combination</p>* <pre>* Position cursor: click/touch* Create selection: click/touch & drag* Create selection: click & shift + click* Select word: double click* Select line: triple click* </pre>*/fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ {/*** Type of an object* @type String* @default*/type: 'i-text',/*** Index where text selection starts (or where cursor is when there is no selection)* @type Nubmer* @default*/selectionStart: 0,/*** Index where text selection ends* @type Nubmer* @default*/selectionEnd: 0,/*** Color of text selection* @type String* @default*/selectionColor: 'rgba(17,119,255,0.3)',/*** Indicates whether text is in editing mode* @type Boolean* @default*/isEditing: false,/*** Indicates whether a text can be edited* @type Boolean* @default*/editable: true,/*** Border color of text object while it's in editing mode* @type String* @default*/editingBorderColor: 'rgba(102,153,255,0.25)',/*** Width of cursor (in px)* @type Number* @default*/cursorWidth: 2,/*** Color of default cursor (when not overwritten by character style)* @type String* @default*/cursorColor: '#333',/*** Delay between cursor blink (in ms)* @type Number* @default*/cursorDelay: 1000,/*** Duration of cursor fadein (in ms)* @type Number* @default*/cursorDuration: 600,/*** Object containing character styles* (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line)* @type Object* @default*/styles: null,/*** Indicates whether internal text char widths can be cached* @type Boolean* @default*/caching: true,/*** @private* @type Boolean* @default*/_skipFillStrokeCheck: false,/*** @private*/_reSpace: /\s|\n/,/*** @private*/_currentCursorOpacity: 0,/*** @private*/_selectionDirection: null,/*** @private*/_abortCursorAnimation: false,/*** @private*/_charWidthsCache: { },/*** Constructor* @param {String} text Text string* @param {Object} [options] Options object* @return {fabric.IText} thisArg*/initialize: function(text, options) {this.styles = options ? (options.styles || { }) : { };this.callSuper('initialize', text, options);this.initBehavior();},/*** @private*/_clearCache: function() {this.callSuper('_clearCache');this.__maxFontHeights = [ ];this.__widthOfSpace = [ ];},/*** Returns true if object has no styling*/isEmptyStyles: function() {if (!this.styles) {return true;}var obj = this.styles;for (var p1 in obj) {for (var p2 in obj[p1]) {/*jshint unused:false */for (var p3 in obj[p1][p2]) {return false;}}}return true;},/*** Sets selection start (left boundary of a selection)* @param {Number} index Index to set selection start to*/setSelectionStart: function(index) {index = Math.max(index, 0);if (this.selectionStart !== index) {this.fire('selection:changed');this.canvas && this.canvas.fire('text:selection:changed', { target: this });this.selectionStart = index;}this._updateTextarea();},/*** Sets selection end (right boundary of a selection)* @param {Number} index Index to set selection end to*/setSelectionEnd: function(index) {index = Math.min(index, this.text.length);if (this.selectionEnd !== index) {this.fire('selection:changed');this.canvas && this.canvas.fire('text:selection:changed', { target: this });this.selectionEnd = index;}this._updateTextarea();},/*** Gets style of a current selection/cursor (at the start position)* @param {Number} [startIndex] Start index to get styles at* @param {Number} [endIndex] End index to get styles at* @return {Object} styles Style object at a specified (or current) index*/getSelectionStyles: function(startIndex, endIndex) {if (arguments.length === 2) {var styles = [ ];for (var i = startIndex; i < endIndex; i++) {styles.push(this.getSelectionStyles(i));}return styles;}var loc = this.get2DCursorLocation(startIndex);if (this.styles[loc.lineIndex]) {return this.styles[loc.lineIndex][loc.charIndex] || { };}return { };},/*** Sets style of a current selection* @param {Object} [styles] Styles object* @return {fabric.IText} thisArg* @chainable*/setSelectionStyles: function(styles) {if (this.selectionStart === this.selectionEnd) {this._extendStyles(this.selectionStart, styles);}else {for (var i = this.selectionStart; i < this.selectionEnd; i++) {this._extendStyles(i, styles);}}/* not included in _extendStyles to avoid clearing cache more than once */this._clearCache();return this;},/*** @private*/_extendStyles: function(index, styles) {var loc = this.get2DCursorLocation(index);if (!this.styles[loc.lineIndex]) {this.styles[loc.lineIndex] = { };}if (!this.styles[loc.lineIndex][loc.charIndex]) {this.styles[loc.lineIndex][loc.charIndex] = { };}fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_render: function(ctx) {this.callSuper('_render', ctx);this.ctx = ctx;this.isEditing && this.renderCursorOrSelection();},/*** Renders cursor or selection (depending on what exists)*/renderCursorOrSelection: function() {if (!this.active) {return;}var chars = this.text.split(''),boundaries, ctx;if (this.canvas.contextTop) {ctx = this.canvas.contextTop;ctx.save();ctx.transform.apply(ctx, this.canvas.viewportTransform);this.transform(ctx);}else {ctx = this.ctx;ctx.save();}if (this.selectionStart === this.selectionEnd) {boundaries = this._getCursorBoundaries(chars, 'cursor');this.renderCursor(boundaries, ctx);}else {boundaries = this._getCursorBoundaries(chars, 'selection');this.renderSelection(chars, boundaries, ctx);}ctx.restore();},/*** Returns 2d representation (lineIndex and charIndex) of cursor (or selection start)* @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.*/get2DCursorLocation: function(selectionStart) {if (typeof selectionStart === 'undefined') {selectionStart = this.selectionStart;}var textBeforeCursor = this.text.slice(0, selectionStart),linesBeforeCursor = textBeforeCursor.split(this._reNewline);return {lineIndex: linesBeforeCursor.length - 1,charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length};},/*** Returns complete style of char at the current cursor* @param {Number} lineIndex Line index* @param {Number} charIndex Char index* @return {Object} Character style*/getCurrentCharStyle: function(lineIndex, charIndex) {var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)];return {fontSize: style && style.fontSize || this.fontSize,fill: style && style.fill || this.fill,textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor,textDecoration: style && style.textDecoration || this.textDecoration,fontFamily: style && style.fontFamily || this.fontFamily,fontWeight: style && style.fontWeight || this.fontWeight,fontStyle: style && style.fontStyle || this.fontStyle,stroke: style && style.stroke || this.stroke,strokeWidth: style && style.strokeWidth || this.strokeWidth};},/*** Returns fontSize of char at the current cursor* @param {Number} lineIndex Line index* @param {Number} charIndex Char index* @return {Number} Character font size*/getCurrentCharFontSize: function(lineIndex, charIndex) {return (this.styles[lineIndex] &&this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize;},/*** Returns color (fill) of char at the current cursor* @param {Number} lineIndex Line index* @param {Number} charIndex Char index* @return {String} Character color (fill)*/getCurrentCharColor: function(lineIndex, charIndex) {return (this.styles[lineIndex] &&this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor;},/*** Returns cursor boundaries (left, top, leftOffset, topOffset)* @private* @param {Array} chars Array of characters* @param {String} typeOfBoundaries*/_getCursorBoundaries: function(chars, typeOfBoundaries) {// left/top are left/top of entire text box// leftOffset/topOffset are offset from that left/top point of a text boxvar left = Math.round(this._getLeftOffset()),top = this._getTopOffset(),offsets = this._getCursorBoundariesOffsets(chars, typeOfBoundaries);return {left: left,top: top,leftOffset: offsets.left + offsets.lineLeft,topOffset: offsets.top};},/*** @private*/_getCursorBoundariesOffsets: function(chars, typeOfBoundaries) {var lineLeftOffset = 0,lineIndex = 0,charIndex = 0,topOffset = 0,leftOffset = 0;for (var i = 0; i < this.selectionStart; i++) {if (chars[i] === '\n') {leftOffset = 0;topOffset += this._getHeightOfLine(this.ctx, lineIndex);lineIndex++;charIndex = 0;}else {leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex);charIndex++;}lineLeftOffset = this._getCachedLineOffset(lineIndex);}if (typeOfBoundaries === 'cursor') {topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight- this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction);}return {top: topOffset,left: leftOffset,lineLeft: lineLeftOffset};},/*** @private*/_getCachedLineOffset: function(lineIndex) {var widthOfLine = this._getLineWidth(this.ctx, lineIndex);return this.__lineOffsets[lineIndex] ||(this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine));},/*** Renders cursor* @param {Object} boundaries* @param {CanvasRenderingContext2D} ctx transformed context to draw on*/renderCursor: function(boundaries, ctx) {var cursorLocation = this.get2DCursorLocation(),lineIndex = cursorLocation.lineIndex,charIndex = cursorLocation.charIndex,charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),leftOffset = (lineIndex === 0 && charIndex === 0)? this._getCachedLineOffset(lineIndex): boundaries.leftOffset;ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity;ctx.fillRect(boundaries.left + leftOffset,boundaries.top + boundaries.topOffset,this.cursorWidth / this.scaleX,charHeight);},/*** Renders text selection* @param {Array} chars Array of characters* @param {Object} boundaries Object with left/top/leftOffset/topOffset* @param {CanvasRenderingContext2D} ctx transformed context to draw on*/renderSelection: function(chars, boundaries, ctx) {ctx.fillStyle = this.selectionColor;var start = this.get2DCursorLocation(this.selectionStart),end = this.get2DCursorLocation(this.selectionEnd),startLine = start.lineIndex,endLine = end.lineIndex;for (var i = startLine; i <= endLine; i++) {var lineOffset = this._getCachedLineOffset(i) || 0,lineHeight = this._getHeightOfLine(this.ctx, i),boxWidth = 0, line = this._textLines[i];if (i === startLine) {for (var j = 0, len = line.length; j < len; j++) {if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) {boxWidth += this._getWidthOfChar(ctx, line[j], i, j);}if (j < start.charIndex) {lineOffset += this._getWidthOfChar(ctx, line[j], i, j);}}}else if (i > startLine && i < endLine) {boxWidth += this._getLineWidth(ctx, i) || 5;}else if (i === endLine) {for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2);}}ctx.fillRect(boundaries.left + lineOffset,boundaries.top + boundaries.topOffset,boxWidth,lineHeight);boundaries.topOffset += lineHeight;}},/*** @private* @param {String} method* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderChars: function(method, ctx, line, left, top, lineIndex) {if (this.isEmptyStyles()) {return this._renderCharsFast(method, ctx, line, left, top);}this.skipTextAlign = true;// set proper box offsetleft -= this.textAlign === 'center'? (this.width / 2): (this.textAlign === 'right')? this.width: 0;// set proper line offsetvar lineHeight = this._getHeightOfLine(ctx, lineIndex),lineLeftOffset = this._getCachedLineOffset(lineIndex),chars = line.split(''),prevStyle,charsToRender = '';left += lineLeftOffset || 0;ctx.save();top -= lineHeight / this.lineHeight * this._fontSizeFraction;for (var i = 0, len = chars.length; i <= len; i++) {prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1);if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) {this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight);charsToRender = '';prevStyle = thisStyle;}charsToRender += chars[i];}ctx.restore();},/*** @private* @param {String} method* @param {CanvasRenderingContext2D} ctx Context to render on* @param {String} line Content of the line* @param {Number} left Left coordinate* @param {Number} top Top coordinate*/_renderCharsFast: function(method, ctx, line, left, top) {this.skipTextAlign = false;if (method === 'fillText' && this.fill) {this.callSuper('_renderChars', method, ctx, line, left, top);}if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) {this.callSuper('_renderChars', method, ctx, line, left, top);}},/*** @private* @param {String} method* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Number} lineIndex* @param {Number} i* @param {String} _char* @param {Number} left Left coordinate* @param {Number} top Top coordinate* @param {Number} lineHeight Height of the line*/_renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {var decl, charWidth, charHeight,offset = this._fontSizeFraction * lineHeight / this.lineHeight;if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {var shouldStroke = decl.stroke || this.stroke,shouldFill = decl.fill || this.fill;ctx.save();charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl);charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);if (shouldFill) {ctx.fillText(_char, left, top);}if (shouldStroke) {ctx.strokeText(_char, left, top);}this._renderCharDecoration(ctx, decl, left, top, offset, charWidth, charHeight);ctx.restore();ctx.translate(charWidth, 0);}else {if (method === 'strokeText' && this.stroke) {ctx[method](_char, left, top);}if (method === 'fillText' && this.fill) {ctx[method](_char, left, top);}charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i);this._renderCharDecoration(ctx, null, left, top, offset, charWidth, this.fontSize);ctx.translate(ctx.measureText(_char).width, 0);}},/*** @private* @param {Object} prevStyle* @param {Object} thisStyle*/_hasStyleChanged: function(prevStyle, thisStyle) {return (prevStyle.fill !== thisStyle.fill ||prevStyle.fontSize !== thisStyle.fontSize ||prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor ||prevStyle.textDecoration !== thisStyle.textDecoration ||prevStyle.fontFamily !== thisStyle.fontFamily ||prevStyle.fontWeight !== thisStyle.fontWeight ||prevStyle.fontStyle !== thisStyle.fontStyle ||prevStyle.stroke !== thisStyle.stroke ||prevStyle.strokeWidth !== thisStyle.strokeWidth);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderCharDecoration: function(ctx, styleDeclaration, left, top, offset, charWidth, charHeight) {var textDecoration = styleDeclaration? (styleDeclaration.textDecoration || this.textDecoration): this.textDecoration;if (!textDecoration) {return;}if (textDecoration.indexOf('underline') > -1) {ctx.fillRect(left,top + charHeight / 10,charWidth ,charHeight / 15);}if (textDecoration.indexOf('line-through') > -1) {ctx.fillRect(left,top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + charHeight / 15,charWidth,charHeight / 15);}if (textDecoration.indexOf('overline') > -1) {ctx.fillRect(left,top - (this._fontSizeMult - this._fontSizeFraction) * charHeight,charWidth,charHeight / 15);}},/*** @private* @param {String} method* @param {CanvasRenderingContext2D} ctx Context to render on* @param {String} line*/_renderTextLine: function(method, ctx, line, left, top, lineIndex) {// to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine// the adding 0.03 is just to align text with itext by overlap testif (!this.isEmptyStyles()) {top += this.fontSize * (this._fontSizeFraction + 0.03);}this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderTextDecoration: function(ctx) {if (this.isEmptyStyles()) {return this.callSuper('_renderTextDecoration', ctx);}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_renderTextLinesBackground: function(ctx) {if (!this.textBackgroundColor && !this.styles) {return;}ctx.save();if (this.textBackgroundColor) {ctx.fillStyle = this.textBackgroundColor;}var lineHeights = 0;for (var i = 0, len = this._textLines.length; i < len; i++) {var heightOfLine = this._getHeightOfLine(ctx, i);if (this._textLines[i] === '') {lineHeights += heightOfLine;continue;}var lineWidth = this._getLineWidth(ctx, i),lineLeftOffset = this._getCachedLineOffset(i);if (this.textBackgroundColor) {ctx.fillStyle = this.textBackgroundColor;ctx.fillRect(this._getLeftOffset() + lineLeftOffset,this._getTopOffset() + lineHeights,lineWidth,heightOfLine / this.lineHeight);}if (this.styles[i]) {for (var j = 0, jlen = this._textLines[i].length; j < jlen; j++) {if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) {var _char = this._textLines[i][j];ctx.fillStyle = this.styles[i][j].textBackgroundColor;ctx.fillRect(this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j),this._getTopOffset() + lineHeights,this._getWidthOfChar(ctx, _char, i, j) + 1,heightOfLine / this.lineHeight);}}}lineHeights += heightOfLine;}ctx.restore();},/*** @private*/_getCacheProp: function(_char, styleDeclaration) {return _char +styleDeclaration.fontFamily +styleDeclaration.fontSize +styleDeclaration.fontWeight +styleDeclaration.fontStyle +styleDeclaration.shadow;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @param {String} _char* @param {Number} lineIndex* @param {Number} charIndex* @param {Object} [decl]*/_applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) {var styleDeclaration = decl ||(this.styles[lineIndex] &&this.styles[lineIndex][charIndex]);if (styleDeclaration) {// cloning so that original style object is not polluted with following font declarationsstyleDeclaration = clone(styleDeclaration);}else {styleDeclaration = { };}this._applyFontStyles(styleDeclaration);var cacheProp = this._getCacheProp(_char, styleDeclaration);// short-circuit if no stylesif (this.isEmptyStyles() && this._charWidthsCache[cacheProp] && this.caching) {return this._charWidthsCache[cacheProp];}if (typeof styleDeclaration.shadow === 'string') {styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow);}var fill = styleDeclaration.fill || this.fill;ctx.fillStyle = fill.toLive? fill.toLive(ctx, this): fill;if (styleDeclaration.stroke) {ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive)? styleDeclaration.stroke.toLive(ctx, this): styleDeclaration.stroke;}ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;ctx.font = this._getFontDeclaration.call(styleDeclaration);this._setShadow.call(styleDeclaration, ctx);if (!this.caching) {return ctx.measureText(_char).width;}if (!this._charWidthsCache[cacheProp]) {this._charWidthsCache[cacheProp] = ctx.measureText(_char).width;}return this._charWidthsCache[cacheProp];},/*** @private* @param {Object} styleDeclaration*/_applyFontStyles: function(styleDeclaration) {if (!styleDeclaration.fontFamily) {styleDeclaration.fontFamily = this.fontFamily;}if (!styleDeclaration.fontSize) {styleDeclaration.fontSize = this.fontSize;}if (!styleDeclaration.fontWeight) {styleDeclaration.fontWeight = this.fontWeight;}if (!styleDeclaration.fontStyle) {styleDeclaration.fontStyle = this.fontStyle;}},/*** @private* @param {Number} lineIndex* @param {Number} charIndex*/_getStyleDeclaration: function(lineIndex, charIndex) {return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])? clone(this.styles[lineIndex][charIndex]): { };},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_getWidthOfChar: function(ctx, _char, lineIndex, charIndex) {if (this.textAlign === 'justify' && /\s/.test(_char)) {return this._getWidthOfSpace(ctx, lineIndex);}var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex);this._applyFontStyles(styleDeclaration);var cacheProp = this._getCacheProp(_char, styleDeclaration);if (this._charWidthsCache[cacheProp] && this.caching) {return this._charWidthsCache[cacheProp];}else if (ctx) {ctx.save();var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);ctx.restore();return width;}},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_getHeightOfChar: function(ctx, _char, lineIndex, charIndex) {if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) {return this.styles[lineIndex][charIndex].fontSize || this.fontSize;}return this.fontSize;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_getHeightOfCharAt: function(ctx, lineIndex, charIndex) {var _char = this._textLines[lineIndex][charIndex];return this._getHeightOfChar(ctx, _char, lineIndex, charIndex);},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_getWidthOfCharsAt: function(ctx, lineIndex, charIndex) {var width = 0, i, _char;for (i = 0; i < charIndex; i++) {_char = this._textLines[lineIndex][i];width += this._getWidthOfChar(ctx, _char, lineIndex, i);}return width;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_getLineWidth: function(ctx, lineIndex) {if (this.__lineWidths[lineIndex]) {return this.__lineWidths[lineIndex];}this.__lineWidths[lineIndex] = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length);return this.__lineWidths[lineIndex];},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Number} lineIndex*/_getWidthOfSpace: function (ctx, lineIndex) {if (this.__widthOfSpace[lineIndex]) {return this.__widthOfSpace[lineIndex];}var line = this._textLines[lineIndex],wordsWidth = this._getWidthOfWords(ctx, line, lineIndex),widthDiff = this.width - wordsWidth,numSpaces = line.length - line.replace(/\s+/g, '').length,width = widthDiff / numSpaces;this.__widthOfSpace[lineIndex] = width;return width;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on* @param {Number} line* @param {Number} lineIndex*/_getWidthOfWords: function (ctx, line, lineIndex) {var width = 0;for (var charIndex = 0; charIndex < line.length; charIndex++) {var _char = line[charIndex];if (!_char.match(/\s/)) {width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex);}}return width;},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_getHeightOfLine: function(ctx, lineIndex) {if (this.__lineHeights[lineIndex]) {return this.__lineHeights[lineIndex];}var line = this._textLines[lineIndex],maxHeight = this._getHeightOfChar(ctx, line[0], lineIndex, 0);for (var i = 1, len = line.length; i < len; i++) {var currentCharHeight = this._getHeightOfChar(ctx, line[i], lineIndex, i);if (currentCharHeight > maxHeight) {maxHeight = currentCharHeight;}}this.__maxFontHeights[lineIndex] = maxHeight;this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult;return this.__lineHeights[lineIndex];},/*** @private* @param {CanvasRenderingContext2D} ctx Context to render on*/_getTextHeight: function(ctx) {var height = 0;for (var i = 0, len = this._textLines.length; i < len; i++) {height += this._getHeightOfLine(ctx, i);}return height;},/*** This method is overwritten to account for different top offset* @private*/_renderTextBoxBackground: function(ctx) {if (!this.backgroundColor) {return;}ctx.save();ctx.fillStyle = this.backgroundColor;ctx.fillRect(this._getLeftOffset(),this._getTopOffset(),this.width,this.height);ctx.restore();},/*** Returns object representation of an instance* @method toObject* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output* @return {Object} object representation of an instance*/toObject: function(propertiesToInclude) {return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {styles: clone(this.styles)});}});/*** Returns fabric.IText instance from an object representation* @static* @memberOf fabric.IText* @param {Object} object Object to create an instance from* @return {fabric.IText} instance of fabric.IText*/fabric.IText.fromObject = function(object) {return new fabric.IText(object.text, clone(object));};})();(function() {var clone = fabric.util.object.clone;fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {/*** Initializes all the interactive behavior of IText*/initBehavior: function() {this.initAddedHandler();this.initRemovedHandler();this.initCursorSelectionHandlers();this.initDoubleClickSimulation();},/*** Initializes "selected" event handler*/initSelectedHandler: function() {this.on('selected', function() {var _this = this;setTimeout(function() {_this.selected = true;}, 100);});},/*** Initializes "added" event handler*/initAddedHandler: function() {var _this = this;this.on('added', function() {if (this.canvas && !this.canvas._hasITextHandlers) {this.canvas._hasITextHandlers = true;this._initCanvasHandlers();}// Track IText instances per-canvas. Only register in this array once added// to a canvas; we don't want to leak a reference to the instance forever// simply because it existed at some point.//// (Might be added to a collection, but not on a canvas.)if (_this.canvas) {_this.canvas._iTextInstances = _this.canvas._iTextInstances || [];_this.canvas._iTextInstances.push(_this);}});},initRemovedHandler: function() {var _this = this;this.on('removed', function() {// (Might be removed from a collection, but not on a canvas.)if (_this.canvas) {_this.canvas._iTextInstances = _this.canvas._iTextInstances || [];fabric.util.removeFromArray(_this.canvas._iTextInstances, _this);}});},/*** @private*/_initCanvasHandlers: function() {var _this = this;this.canvas.on('selection:cleared', function() {fabric.IText.prototype.exitEditingOnOthers(_this.canvas);});this.canvas.on('mouse:up', function() {if (_this.canvas._iTextInstances) {_this.canvas._iTextInstances.forEach(function(obj) {obj.__isMousedown = false;});}});this.canvas.on('object:selected', function() {fabric.IText.prototype.exitEditingOnOthers(_this.canvas);});},/*** @private*/_tick: function() {this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete');},/*** @private*/_animateCursor: function(obj, targetOpacity, duration, completeMethod) {var tickState;tickState = {isAborted: false,abort: function() {this.isAborted = true;},};obj.animate('_currentCursorOpacity', targetOpacity, {duration: duration,onComplete: function() {if (!tickState.isAborted) {obj[completeMethod]();}},onChange: function() {if (obj.canvas) {obj.canvas.clearContext(obj.canvas.contextTop || obj.ctx);obj.renderCursorOrSelection();}},abort: function() {return tickState.isAborted;}});return tickState;},/*** @private*/_onTickComplete: function() {var _this = this;if (this._cursorTimeout1) {clearTimeout(this._cursorTimeout1);}this._cursorTimeout1 = setTimeout(function() {_this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick');}, 100);},/*** Initializes delayed cursor*/initDelayedCursor: function(restart) {var _this = this,delay = restart ? 0 : this.cursorDelay;this._currentTickState && this._currentTickState.abort();this._currentTickCompleteState && this._currentTickCompleteState.abort();clearTimeout(this._cursorTimeout1);this._currentCursorOpacity = 1;if (this.canvas) {this.canvas.clearContext(this.canvas.contextTop || this.ctx);this.renderCursorOrSelection();}if (this._cursorTimeout2) {clearTimeout(this._cursorTimeout2);}this._cursorTimeout2 = setTimeout(function() {_this._tick();}, delay);},/*** Aborts cursor animation and clears all timeouts*/abortCursorAnimation: function() {this._currentTickState && this._currentTickState.abort();this._currentTickCompleteState && this._currentTickCompleteState.abort();clearTimeout(this._cursorTimeout1);clearTimeout(this._cursorTimeout2);this._currentCursorOpacity = 0;this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx);},/*** Selects entire text*/selectAll: function() {this.setSelectionStart(0);this.setSelectionEnd(this.text.length);},/*** Returns selected text* @return {String}*/getSelectedText: function() {return this.text.slice(this.selectionStart, this.selectionEnd);},/*** Find new selection index representing start of current word according to current selection index* @param {Number} startFrom Surrent selection index* @return {Number} New selection index*/findWordBoundaryLeft: function(startFrom) {var offset = 0, index = startFrom - 1;// remove space before cursor firstif (this._reSpace.test(this.text.charAt(index))) {while (this._reSpace.test(this.text.charAt(index))) {offset++;index--;}}while (/\S/.test(this.text.charAt(index)) && index > -1) {offset++;index--;}return startFrom - offset;},/*** Find new selection index representing end of current word according to current selection index* @param {Number} startFrom Current selection index* @return {Number} New selection index*/findWordBoundaryRight: function(startFrom) {var offset = 0, index = startFrom;// remove space after cursor firstif (this._reSpace.test(this.text.charAt(index))) {while (this._reSpace.test(this.text.charAt(index))) {offset++;index++;}}while (/\S/.test(this.text.charAt(index)) && index < this.text.length) {offset++;index++;}return startFrom + offset;},/*** Find new selection index representing start of current line according to current selection index* @param {Number} startFrom Current selection index* @return {Number} New selection index*/findLineBoundaryLeft: function(startFrom) {var offset = 0, index = startFrom - 1;while (!/\n/.test(this.text.charAt(index)) && index > -1) {offset++;index--;}return startFrom - offset;},/*** Find new selection index representing end of current line according to current selection index* @param {Number} startFrom Current selection index* @return {Number} New selection index*/findLineBoundaryRight: function(startFrom) {var offset = 0, index = startFrom;while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) {offset++;index++;}return startFrom + offset;},/*** Returns number of newlines in selected text* @return {Number} Number of newlines in selected text*/getNumNewLinesInSelectedText: function() {var selectedText = this.getSelectedText(),numNewLines = 0;for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) {if (chars[i] === '\n') {numNewLines++;}}return numNewLines;},/*** Finds index corresponding to beginning or end of a word* @param {Number} selectionStart Index of a character* @param {Number} direction: 1 or -1* @return {Number} Index of the beginning or end of a word*/searchWordBoundary: function(selectionStart, direction) {var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart,_char = this.text.charAt(index),reNonWord = /[ \n\.,;!\?\-]/;while (!reNonWord.test(_char) && index > 0 && index < this.text.length) {index += direction;_char = this.text.charAt(index);}if (reNonWord.test(_char) && _char !== '\n') {index += direction === 1 ? 0 : 1;}return index;},/*** Selects a word based on the index* @param {Number} selectionStart Index of a character*/selectWord: function(selectionStart) {var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */this.setSelectionStart(newSelectionStart);this.setSelectionEnd(newSelectionEnd);},/*** Selects a line based on the index* @param {Number} selectionStart Index of a character*/selectLine: function(selectionStart) {var newSelectionStart = this.findLineBoundaryLeft(selectionStart),newSelectionEnd = this.findLineBoundaryRight(selectionStart);this.setSelectionStart(newSelectionStart);this.setSelectionEnd(newSelectionEnd);},/*** Enters editing state* @return {fabric.IText} thisArg* @chainable*/enterEditing: function() {if (this.isEditing || !this.editable) {return;}if (this.canvas) {this.exitEditingOnOthers(this.canvas);}this.isEditing = true;this.initHiddenTextarea();this.hiddenTextarea.focus();this._updateTextarea();this._saveEditingProps();this._setEditingProps();this._tick();this.fire('editing:entered');if (!this.canvas) {return this;}this.canvas.renderAll();this.canvas.fire('text:editing:entered', { target: this });this.initMouseMoveHandler();return this;},exitEditingOnOthers: function(canvas) {if (canvas._iTextInstances) {canvas._iTextInstances.forEach(function(obj) {obj.selected = false;if (obj.isEditing) {obj.exitEditing();}});}},/*** Initializes "mousemove" event handler*/initMouseMoveHandler: function() {var _this = this;this.canvas.on('mouse:move', function(options) {if (!_this.__isMousedown || !_this.isEditing) {return;}var newSelectionStart = _this.getSelectionStartFromPointer(options.e);if (newSelectionStart >= _this.__selectionStartOnMouseDown) {_this.setSelectionStart(_this.__selectionStartOnMouseDown);_this.setSelectionEnd(newSelectionStart);}else {_this.setSelectionStart(newSelectionStart);_this.setSelectionEnd(_this.__selectionStartOnMouseDown);}});},/*** @private*/_setEditingProps: function() {this.hoverCursor = 'text';if (this.canvas) {this.canvas.defaultCursor = this.canvas.moveCursor = 'text';}this.borderColor = this.editingBorderColor;this.hasControls = this.selectable = false;this.lockMovementX = this.lockMovementY = true;},/*** @private*/_updateTextarea: function() {if (!this.hiddenTextarea) {return;}this.hiddenTextarea.value = this.text;this.hiddenTextarea.selectionStart = this.selectionStart;this.hiddenTextarea.selectionEnd = this.selectionEnd;},/*** @private*/_saveEditingProps: function() {this._savedProps = {hasControls: this.hasControls,borderColor: this.borderColor,lockMovementX: this.lockMovementX,lockMovementY: this.lockMovementY,hoverCursor: this.hoverCursor,defaultCursor: this.canvas && this.canvas.defaultCursor,moveCursor: this.canvas && this.canvas.moveCursor};},/*** @private*/_restoreEditingProps: function() {if (!this._savedProps) {return;}this.hoverCursor = this._savedProps.overCursor;this.hasControls = this._savedProps.hasControls;this.borderColor = this._savedProps.borderColor;this.lockMovementX = this._savedProps.lockMovementX;this.lockMovementY = this._savedProps.lockMovementY;if (this.canvas) {this.canvas.defaultCursor = this._savedProps.defaultCursor;this.canvas.moveCursor = this._savedProps.moveCursor;}},/*** Exits from editing state* @return {fabric.IText} thisArg* @chainable*/exitEditing: function() {this.selected = false;this.isEditing = false;this.selectable = true;this.selectionEnd = this.selectionStart;this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea);this.hiddenTextarea = null;this.abortCursorAnimation();this._restoreEditingProps();this._currentCursorOpacity = 0;this.fire('editing:exited');this.canvas && this.canvas.fire('text:editing:exited', { target: this });return this;},/*** @private*/_removeExtraneousStyles: function() {for (var prop in this.styles) {if (!this._textLines[prop]) {delete this.styles[prop];}}},/*** @private*/_removeCharsFromTo: function(start, end) {var i = end;while (i !== start) {var prevIndex = this.get2DCursorLocation(i).charIndex;i--;var index = this.get2DCursorLocation(i).charIndex,isNewline = index > prevIndex;if (isNewline) {this.removeStyleObject(isNewline, i + 1);}else {this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i);}}this.text = this.text.slice(0, start) +this.text.slice(end);this._clearCache();},/*** Inserts a character where cursor is (replacing selection if one exists)* @param {String} _chars Characters to insert*/insertChars: function(_chars, useCopiedStyle) {var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n';this.text = this.text.slice(0, this.selectionStart) +_chars +this.text.slice(this.selectionEnd);if (this.selectionStart === this.selectionEnd) {this.insertStyleObjects(_chars, isEndOfLine, useCopiedStyle);}// else if (this.selectionEnd - this.selectionStart > 1) {// TODO: replace styles properly// console.log('replacing MORE than 1 char');// }this.setSelectionStart(this.selectionStart + _chars.length);this.setSelectionEnd(this.selectionStart);this._clearCache();this.canvas && this.canvas.renderAll();this.setCoords();this.fire('changed');this.canvas && this.canvas.fire('text:changed', { target: this });},/*** Inserts new style object* @param {Number} lineIndex Index of a line* @param {Number} charIndex Index of a char* @param {Boolean} isEndOfLine True if it's end of line*/insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {this.shiftLineStyles(lineIndex, +1);if (!this.styles[lineIndex + 1]) {this.styles[lineIndex + 1] = { };}var currentCharStyle = this.styles[lineIndex][charIndex - 1],newLineStyles = { };// if there's nothing after cursor,// we clone current char style onto the next (otherwise empty) lineif (isEndOfLine) {newLineStyles[0] = clone(currentCharStyle);this.styles[lineIndex + 1] = newLineStyles;}// otherwise we clone styles of all chars// after cursor onto the next line, from the beginningelse {for (var index in this.styles[lineIndex]) {if (parseInt(index, 10) >= charIndex) {newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index];// remove lines from the previous line since they're on a new line nowdelete this.styles[lineIndex][index];}}this.styles[lineIndex + 1] = newLineStyles;}this._clearCache();},/*** Inserts style object for a given line/char index* @param {Number} lineIndex Index of a line* @param {Number} charIndex Index of a char* @param {Object} [style] Style object to insert, if given*/insertCharStyleObject: function(lineIndex, charIndex, style) {var currentLineStyles = this.styles[lineIndex],currentLineStylesCloned = clone(currentLineStyles);if (charIndex === 0 && !style) {charIndex = 1;}// shift all char styles by 1 forward// 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4for (var index in currentLineStylesCloned) {var numericIndex = parseInt(index, 10);if (numericIndex >= charIndex) {currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex];//delete currentLineStyles[index];}}this.styles[lineIndex][charIndex] =style || clone(currentLineStyles[charIndex - 1]);this._clearCache();},/*** Inserts style object(s)* @param {String} _chars Characters at the location where style is inserted* @param {Boolean} isEndOfLine True if it's end of line* @param {Boolean} [useCopiedStyle] Style to insert*/insertStyleObjects: function(_chars, isEndOfLine, useCopiedStyle) {// removed shortcircuit over isEmptyStylesvar cursorLocation = this.get2DCursorLocation(),lineIndex = cursorLocation.lineIndex,charIndex = cursorLocation.charIndex;if (!this.styles[lineIndex]) {this.styles[lineIndex] = { };}if (_chars === '\n') {this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);}else {if (useCopiedStyle) {this._insertStyles(this.copiedStyles);}else {// TODO: support multiple style insertion if _chars.length > 1this.insertCharStyleObject(lineIndex, charIndex);}}},/*** @private*/_insertStyles: function(styles) {for (var i = 0, len = styles.length; i < len; i++) {var cursorLocation = this.get2DCursorLocation(this.selectionStart + i),lineIndex = cursorLocation.lineIndex,charIndex = cursorLocation.charIndex;this.insertCharStyleObject(lineIndex, charIndex, styles[i]);}},/*** Shifts line styles up or down* @param {Number} lineIndex Index of a line* @param {Number} offset Can be -1 or +1*/shiftLineStyles: function(lineIndex, offset) {// shift all line styles by 1 upwardvar clonedStyles = clone(this.styles);for (var line in this.styles) {var numericLine = parseInt(line, 10);if (numericLine > lineIndex) {this.styles[numericLine + offset] = clonedStyles[numericLine];}}},/*** Removes style object* @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line* @param {Number} [index] Optional index. When not given, current selectionStart is used.*/removeStyleObject: function(isBeginningOfLine, index) {var cursorLocation = this.get2DCursorLocation(index),lineIndex = cursorLocation.lineIndex,charIndex = cursorLocation.charIndex;if (isBeginningOfLine) {var textOnPreviousLine = this._textLines[lineIndex - 1],newCharIndexOnPrevLine = textOnPreviousLine? textOnPreviousLine.length: 0;if (!this.styles[lineIndex - 1]) {this.styles[lineIndex - 1] = { };}for (charIndex in this.styles[lineIndex]) {this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine]= this.styles[lineIndex][charIndex];}this.shiftLineStyles(lineIndex, -1);}else {var currentLineStyles = this.styles[lineIndex];if (currentLineStyles) {var offset = this.selectionStart === this.selectionEnd ? -1 : 0;delete currentLineStyles[charIndex + offset];// console.log('deleting', lineIndex, charIndex + offset);}var currentLineStylesCloned = clone(currentLineStyles);// shift all styles by 1 backwardsfor (var i in currentLineStylesCloned) {var numericIndex = parseInt(i, 10);if (numericIndex >= charIndex && numericIndex !== 0) {currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex];delete currentLineStyles[numericIndex];}}}},/*** Inserts new line*/insertNewline: function() {this.insertChars('\n');}});})();fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {/*** Initializes "dbclick" event handler*/initDoubleClickSimulation: function() {// for double clickthis.__lastClickTime = +new Date();// for triple clickthis.__lastLastClickTime = +new Date();this.__lastPointer = { };this.on('mousedown', this.onMouseDown.bind(this));},onMouseDown: function(options) {this.__newClickTime = +new Date();var newPointer = this.canvas.getPointer(options.e);if (this.isTripleClick(newPointer)) {this.fire('tripleclick', options);this._stopEvent(options.e);}else if (this.isDoubleClick(newPointer)) {this.fire('dblclick', options);this._stopEvent(options.e);}this.__lastLastClickTime = this.__lastClickTime;this.__lastClickTime = this.__newClickTime;this.__lastPointer = newPointer;this.__lastIsEditing = this.isEditing;this.__lastSelected = this.selected;},isDoubleClick: function(newPointer) {return this.__newClickTime - this.__lastClickTime < 500 &&this.__lastPointer.x === newPointer.x &&this.__lastPointer.y === newPointer.y && this.__lastIsEditing;},isTripleClick: function(newPointer) {return this.__newClickTime - this.__lastClickTime < 500 &&this.__lastClickTime - this.__lastLastClickTime < 500 &&this.__lastPointer.x === newPointer.x &&this.__lastPointer.y === newPointer.y;},/*** @private*/_stopEvent: function(e) {e.preventDefault && e.preventDefault();e.stopPropagation && e.stopPropagation();},/*** Initializes event handlers related to cursor or selection*/initCursorSelectionHandlers: function() {this.initSelectedHandler();this.initMousedownHandler();this.initMouseupHandler();this.initClicks();},/*** Initializes double and triple click event handlers*/initClicks: function() {this.on('dblclick', function(options) {this.selectWord(this.getSelectionStartFromPointer(options.e));});this.on('tripleclick', function(options) {this.selectLine(this.getSelectionStartFromPointer(options.e));});},/*** Initializes "mousedown" event handler*/initMousedownHandler: function() {this.on('mousedown', function(options) {var pointer = this.canvas.getPointer(options.e);this.__mousedownX = pointer.x;this.__mousedownY = pointer.y;this.__isMousedown = true;if (this.hiddenTextarea && this.canvas) {this.canvas.wrapperEl.appendChild(this.hiddenTextarea);}if (this.selected) {this.setCursorByClick(options.e);}if (this.isEditing) {this.__selectionStartOnMouseDown = this.selectionStart;this.initDelayedCursor(true);}});},/*** @private*/_isObjectMoved: function(e) {var pointer = this.canvas.getPointer(e);return this.__mousedownX !== pointer.x ||this.__mousedownY !== pointer.y;},/*** Initializes "mouseup" event handler*/initMouseupHandler: function() {this.on('mouseup', function(options) {this.__isMousedown = false;if (this._isObjectMoved(options.e)) {return;}if (this.__lastSelected) {this.enterEditing();this.initDelayedCursor(true);}this.selected = true;});},/*** Changes cursor location in a text depending on passed pointer (x/y) object* @param {Event} e Event object*/setCursorByClick: function(e) {var newSelectionStart = this.getSelectionStartFromPointer(e);if (e.shiftKey) {if (newSelectionStart < this.selectionStart) {this.setSelectionEnd(this.selectionStart);this.setSelectionStart(newSelectionStart);}else {this.setSelectionEnd(newSelectionStart);}}else {this.setSelectionStart(newSelectionStart);this.setSelectionEnd(newSelectionStart);}},/*** @private* @param {Event} e Event object* @return {Object} Coordinates of a pointer (x, y)*/_getLocalRotatedPointer: function(e) {var pointer = this.canvas.getPointer(e),pClicked = new fabric.Point(pointer.x, pointer.y),pLeftTop = new fabric.Point(this.left, this.top),rotated = fabric.util.rotatePoint(pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle));return this.getLocalPointer(e, rotated);},/*** Returns index of a character corresponding to where an object was clicked* @param {Event} e Event object* @return {Number} Index of a character*/getSelectionStartFromPointer: function(e) {var mouseOffset = this._getLocalRotatedPointer(e),prevWidth = 0,width = 0,height = 0,charIndex = 0,newSelectionStart,line;for (var i = 0, len = this._textLines.length; i < len; i++) {line = this._textLines[i].split('');height += this._getHeightOfLine(this.ctx, i) * this.scaleY;var widthOfLine = this._getLineWidth(this.ctx, i),lineLeftOffset = this._getLineLeftOffset(widthOfLine);width = lineLeftOffset * this.scaleX;if (this.flipX) {// when oject is horizontally flipped we reverse charsthis._textLines[i] = line.reverse().join('');}for (var j = 0, jlen = line.length; j < jlen; j++) {var _char = line[j];prevWidth = width;width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) *this.scaleX;if (height <= mouseOffset.y || width <= mouseOffset.x) {charIndex++;continue;}return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex + i, jlen);}if (mouseOffset.y < height) {return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex + i, jlen);}}// clicked somewhere after all chars, so set at the endif (typeof newSelectionStart === 'undefined') {return this.text.length;}},/*** @private*/_getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) {var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth,distanceBtwNextCharAndCursor = width - mouseOffset.x,offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1,newSelectionStart = index + offset;// if object is horizontally flipped, mirror cursor location from the endif (this.flipX) {newSelectionStart = jlen - newSelectionStart;}if (newSelectionStart > this.text.length) {newSelectionStart = this.text.length;}return newSelectionStart;}});fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {/*** Initializes hidden textarea (needed to bring up keyboard in iOS)*/initHiddenTextarea: function() {this.hiddenTextarea = fabric.document.createElement('textarea');this.hiddenTextarea.setAttribute('autocapitalize', 'off');this.hiddenTextarea.style.cssText = 'position: fixed; bottom: 20px; left: 0px; opacity: 0;'+ ' width: 0px; height: 0px; z-index: -999;';fabric.document.body.appendChild(this.hiddenTextarea);fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this));fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this));fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this));fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this));if (!this._clickHandlerInitialized && this.canvas) {fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this));this._clickHandlerInitialized = true;}},/*** @private*/_keysMap: {8: 'removeChars',9: 'exitEditing',27: 'exitEditing',13: 'insertNewline',33: 'moveCursorUp',34: 'moveCursorDown',35: 'moveCursorRight',36: 'moveCursorLeft',37: 'moveCursorLeft',38: 'moveCursorUp',39: 'moveCursorRight',40: 'moveCursorDown',46: 'forwardDelete'},/*** @private*/_ctrlKeysMap: {65: 'selectAll',88: 'cut'},onClick: function() {// No need to trigger click event here, focus is enough to have the keyboard appear on Androidthis.hiddenTextarea && this.hiddenTextarea.focus();},/*** Handles keyup event* @param {Event} e Event object*/onKeyDown: function(e) {if (!this.isEditing) {return;}if (e.keyCode in this._keysMap) {this[this._keysMap[e.keyCode]](e);}else if ((e.keyCode in this._ctrlKeysMap) && (e.ctrlKey || e.metaKey)) {this[this._ctrlKeysMap[e.keyCode]](e);}else {return;}e.stopImmediatePropagation();e.preventDefault();this.canvas && this.canvas.renderAll();},/*** Forward delete*/forwardDelete: function(e) {if (this.selectionStart === this.selectionEnd) {this.moveCursorRight(e);}this.removeChars(e);},/*** Copies selected text* @param {Event} e Event object*/copy: function(e) {var selectedText = this.getSelectedText(),clipboardData = this._getClipboardData(e);// Check for backward compatibility with old browsersif (clipboardData) {clipboardData.setData('text', selectedText);}this.copiedText = selectedText;this.copiedStyles = this.getSelectionStyles(this.selectionStart,this.selectionEnd);},/*** Pastes text* @param {Event} e Event object*/paste: function(e) {var copiedText = null,clipboardData = this._getClipboardData(e);// Check for backward compatibility with old browsersif (clipboardData) {copiedText = clipboardData.getData('text');}else {copiedText = this.copiedText;}if (copiedText) {this.insertChars(copiedText, true);}},/*** Cuts text* @param {Event} e Event object*/cut: function(e) {if (this.selectionStart === this.selectionEnd) {return;}this.copy();this.removeChars(e);},/*** @private* @param {Event} e Event object* @return {Object} Clipboard data object*/_getClipboardData: function(e) {return e && (e.clipboardData || fabric.window.clipboardData);},/*** Handles keypress event* @param {Event} e Event object*/onKeyPress: function(e) {if (!this.isEditing || e.metaKey || e.ctrlKey) {return;}if (e.which !== 0) {this.insertChars(String.fromCharCode(e.which));}e.stopPropagation();},/*** Gets start offset of a selection* @param {Event} e Event object* @param {Boolean} isRight* @return {Number}*/getDownCursorOffset: function(e, isRight) {var selectionProp = isRight ? this.selectionEnd : this.selectionStart,_char, lineLeftOffset,textBeforeCursor = this.text.slice(0, selectionProp),textAfterCursor = this.text.slice(selectionProp),textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1),textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1],textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || { })[1] || '',cursorLocation = this.get2DCursorLocation(selectionProp);// if on last line, down cursor goes to end of lineif (cursorLocation.lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) {// move to the end of a textreturn this.text.length - selectionProp;}var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex);lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor);var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,lineIndex = cursorLocation.lineIndex;for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {_char = textOnSameLineBeforeCursor[i];widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);}var indexOnNextLine = this._getIndexOnNextLine(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor);return textOnSameLineAfterCursor.length + 1 + indexOnNextLine;},/*** @private*/_getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) {var lineIndex = cursorLocation.lineIndex + 1,widthOfNextLine = this._getLineWidth(this.ctx, lineIndex),lineLeftOffset = this._getLineLeftOffset(widthOfNextLine),widthOfCharsOnNextLine = lineLeftOffset,indexOnNextLine = 0,foundMatch;for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) {var _char = textOnNextLine[j],widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);widthOfCharsOnNextLine += widthOfChar;if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) {foundMatch = true;var leftEdge = widthOfCharsOnNextLine - widthOfChar,rightEdge = widthOfCharsOnNextLine,offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor);indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j;break;}}// reached endif (!foundMatch) {indexOnNextLine = textOnNextLine.length;}return indexOnNextLine;},/*** Moves cursor down* @param {Event} e Event object*/moveCursorDown: function(e) {this.abortCursorAnimation();this._currentCursorOpacity = 1;var offset = this.getDownCursorOffset(e, this._selectionDirection === 'right');if (e.shiftKey) {this.moveCursorDownWithShift(offset);}else {this.moveCursorDownWithoutShift(offset);}this.initDelayedCursor();},/*** Moves cursor down without keeping selection* @param {Number} offset*/moveCursorDownWithoutShift: function(offset) {this._selectionDirection = 'right';this.setSelectionStart(this.selectionStart + offset);this.setSelectionEnd(this.selectionStart);},/*** private*/swapSelectionPoints: function() {var swapSel = this.selectionEnd;this.setSelectionEnd(this.selectionStart);this.setSelectionStart(swapSel);},/*** Moves cursor down while keeping selection* @param {Number} offset*/moveCursorDownWithShift: function(offset) {if (this.selectionEnd === this.selectionStart) {this._selectionDirection = 'right';}if (this._selectionDirection === 'right') {this.setSelectionEnd(this.selectionEnd + offset);}else {this.setSelectionStart(this.selectionStart + offset);}if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'left') {this.swapSelectionPoints();this._selectionDirection = 'right';}if (this.selectionEnd > this.text.length) {this.setSelectionEnd(this.text.length);}},/*** @param {Event} e Event object* @param {Boolean} isRight* @return {Number}*/getUpCursorOffset: function(e, isRight) {var selectionProp = isRight ? this.selectionEnd : this.selectionStart,cursorLocation = this.get2DCursorLocation(selectionProp);// if on first line, up cursor goes to start of lineif (cursorLocation.lineIndex === 0 || e.metaKey || e.keyCode === 33) {return selectionProp;}var textBeforeCursor = this.text.slice(0, selectionProp),textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1),textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '',_char,widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex),lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor),widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,lineIndex = cursorLocation.lineIndex;for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {_char = textOnSameLineBeforeCursor[i];widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);}var indexOnPrevLine = this._getIndexOnPrevLine(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor);return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length;},/*** @private*/_getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) {var lineIndex = cursorLocation.lineIndex - 1,widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex),lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine),widthOfCharsOnPreviousLine = lineLeftOffset,indexOnPrevLine = 0,foundMatch;for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) {var _char = textOnPreviousLine[j],widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);widthOfCharsOnPreviousLine += widthOfChar;if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) {foundMatch = true;var leftEdge = widthOfCharsOnPreviousLine - widthOfChar,rightEdge = widthOfCharsOnPreviousLine,offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor);indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1);break;}}// reached endif (!foundMatch) {indexOnPrevLine = textOnPreviousLine.length - 1;}return indexOnPrevLine;},/*** Moves cursor up* @param {Event} e Event object*/moveCursorUp: function(e) {this.abortCursorAnimation();this._currentCursorOpacity = 1;var offset = this.getUpCursorOffset(e, this._selectionDirection === 'right');if (e.shiftKey) {this.moveCursorUpWithShift(offset);}else {this.moveCursorUpWithoutShift(offset);}this.initDelayedCursor();},/*** Moves cursor up with shift* @param {Number} offset*/moveCursorUpWithShift: function(offset) {if (this.selectionEnd === this.selectionStart) {this._selectionDirection = 'left';}if (this._selectionDirection === 'right') {this.setSelectionEnd(this.selectionEnd - offset);}else {this.setSelectionStart(this.selectionStart - offset);}if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') {this.swapSelectionPoints();this._selectionDirection = 'left';}},/*** Moves cursor up without shift* @param {Number} offset*/moveCursorUpWithoutShift: function(offset) {if (this.selectionStart === this.selectionEnd) {this.setSelectionStart(this.selectionStart - offset);}this.setSelectionEnd(this.selectionStart);this._selectionDirection = 'left';},/*** Moves cursor left* @param {Event} e Event object*/moveCursorLeft: function(e) {if (this.selectionStart === 0 && this.selectionEnd === 0) {return;}this.abortCursorAnimation();this._currentCursorOpacity = 1;if (e.shiftKey) {this.moveCursorLeftWithShift(e);}else {this.moveCursorLeftWithoutShift(e);}this.initDelayedCursor();},/*** @private*/_move: function(e, prop, direction) {var propMethod = (prop === 'selectionStart' ? 'setSelectionStart' : 'setSelectionEnd');if (e.altKey) {this[propMethod](this['findWordBoundary' + direction](this[prop]));}else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) {this[propMethod](this['findLineBoundary' + direction](this[prop]));}else {this[propMethod](this[prop] + (direction === 'Left' ? -1 : 1));}},/*** @private*/_moveLeft: function(e, prop) {this._move(e, prop, 'Left');},/*** @private*/_moveRight: function(e, prop) {this._move(e, prop, 'Right');},/*** Moves cursor left without keeping selection* @param {Event} e*/moveCursorLeftWithoutShift: function(e) {this._selectionDirection = 'left';// only move cursor when there is no selection,// otherwise we discard it, and leave cursor on same placeif (this.selectionEnd === this.selectionStart) {this._moveLeft(e, 'selectionStart');}this.setSelectionEnd(this.selectionStart);},/*** Moves cursor left while keeping selection* @param {Event} e*/moveCursorLeftWithShift: function(e) {if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) {this._moveLeft(e, 'selectionEnd');}else {this._selectionDirection = 'left';this._moveLeft(e, 'selectionStart');// increase selection by one if it's a newlineif (this.text.charAt(this.selectionStart) === '\n') {this.setSelectionStart(this.selectionStart - 1);}}},/*** Moves cursor right* @param {Event} e Event object*/moveCursorRight: function(e) {if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {return;}this.abortCursorAnimation();this._currentCursorOpacity = 1;if (e.shiftKey) {this.moveCursorRightWithShift(e);}else {this.moveCursorRightWithoutShift(e);}this.initDelayedCursor();},/*** Moves cursor right while keeping selection* @param {Event} e*/moveCursorRightWithShift: function(e) {if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) {this._moveRight(e, 'selectionStart');}else {this._selectionDirection = 'right';this._moveRight(e, 'selectionEnd');// increase selection by one if it's a newlineif (this.text.charAt(this.selectionEnd - 1) === '\n') {this.setSelectionEnd(this.selectionEnd + 1);}}},/*** Moves cursor right without keeping selection* @param {Event} e Event object*/moveCursorRightWithoutShift: function(e) {this._selectionDirection = 'right';if (this.selectionStart === this.selectionEnd) {this._moveRight(e, 'selectionStart');this.setSelectionEnd(this.selectionStart);}else {this.setSelectionEnd(this.selectionEnd + this.getNumNewLinesInSelectedText());this.setSelectionStart(this.selectionEnd);}},/*** Removes characters selected by selection* @param {Event} e Event object*/removeChars: function(e) {if (this.selectionStart === this.selectionEnd) {this._removeCharsNearCursor(e);}else {this._removeCharsFromTo(this.selectionStart, this.selectionEnd);}this.setSelectionEnd(this.selectionStart);this._removeExtraneousStyles();this._clearCache();this.canvas && this.canvas.renderAll();this.setCoords();this.fire('changed');this.canvas && this.canvas.fire('text:changed', { target: this });},/*** @private* @param {Event} e Event object*/_removeCharsNearCursor: function(e) {if (this.selectionStart !== 0) {if (e.metaKey) {// remove all till the start of current linevar leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftLineBoundary, this.selectionStart);this.setSelectionStart(leftLineBoundary);}else if (e.altKey) {// remove all till the start of current wordvar leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftWordBoundary, this.selectionStart);this.setSelectionStart(leftWordBoundary);}else {var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n';this.removeStyleObject(isBeginningOfLine);this.setSelectionStart(this.selectionStart - 1);this.text = this.text.slice(0, this.selectionStart) +this.text.slice(this.selectionStart + 1);}}}});/* _TO_SVG_START_ */fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {/*** @private*/_setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) {if (!this.styles[lineIndex]) {this.callSuper('_setSVGTextLineText',lineIndex, textSpans, height, textLeftOffset, textTopOffset);}else {this._setSVGTextLineChars(lineIndex, textSpans, height, textLeftOffset, textBgRects);}},/*** @private*/_setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) {var chars = this._textLines[lineIndex].split(''),charOffset = 0,lineLeftOffset = this._getSVGLineLeftOffset(lineIndex) - this.width / 2,lineOffset = this._getSVGLineTopOffset(lineIndex),heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);for (var i = 0, len = chars.length; i < len; i++) {var styleDecl = this.styles[lineIndex][i] || { };textSpans.push(this._createTextCharSpan(chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset));var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);if (styleDecl.textBackgroundColor) {textBgRects.push(this._createTextCharBg(styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset));}charOffset += charWidth;}},/*** @private*/_getSVGLineLeftOffset: function(lineIndex) {return fabric.util.toFixed(this._getLineLeftOffset(this.__lineWidths[lineIndex]), 2);},/*** @private*/_getSVGLineTopOffset: function(lineIndex) {var lineTopOffset = 0, lastHeight = 0;for (var j = 0; j < lineIndex; j++) {lineTopOffset += this._getHeightOfLine(this.ctx, j);}lastHeight = this._getHeightOfLine(this.ctx, j);return {lineTop: lineTopOffset,offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult)};},/*** @private*/_createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) {return [//jscs:disable validateIndentation'<rect fill="', styleDecl.textBackgroundColor,'" x="', lineLeftOffset + charOffset,'" y="', lineTopOffset - this.height/2,'" width="', charWidth,'" height="', heightOfLine / this.lineHeight,'"></rect>'//jscs:enable validateIndentation].join('');},/*** @private*/_createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) {var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({visible: true,fill: this.fill,stroke: this.stroke,type: 'text'}, styleDecl));return [//jscs:disable validateIndentation'<tspan x="', lineLeftOffset + charOffset, '" y="',lineTopOffset - this.height/2, '" ',(styleDecl.fontFamily ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, '\'') + '" ': ''),(styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ': ''),(styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ': ''),(styleDecl.fontWeight ? 'font-weight="' + styleDecl.fontWeight + '" ': ''),(styleDecl.textDecoration ? 'text-decoration="' + styleDecl.textDecoration + '" ': ''),'style="', fillStyles, '">',fabric.util.string.escapeXml(_char),'</tspan>'//jscs:enable validateIndentation].join('');}});/* _TO_SVG_END_ */(function() {if (typeof document !== 'undefined' && typeof window !== 'undefined') {return;}var DOMParser = require('xmldom').DOMParser,URL = require('url'),HTTP = require('http'),HTTPS = require('https'),Canvas = require('canvas'),Image = require('canvas').Image;/** @private */function request(url, encoding, callback) {var oURL = URL.parse(url);// detect if http or https is usedif ( !oURL.port ) {oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80;}// assign request handler based on protocolvar reqHandler = (oURL.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP,req = reqHandler.request({hostname: oURL.hostname,port: oURL.port,path: oURL.path,method: 'GET'}, function(response) {var body = '';if (encoding) {response.setEncoding(encoding);}response.on('end', function () {callback(body);});response.on('data', function (chunk) {if (response.statusCode === 200) {body += chunk;}});});req.on('error', function(err) {if (err.errno === process.ECONNREFUSED) {fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port);}else {fabric.log(err.message);}});req.end();}/** @private */function requestFs(path, callback) {var fs = require('fs');fs.readFile(path, function (err, data) {if (err) {fabric.log(err);throw err;}else {callback(data);}});}fabric.util.loadImage = function(url, callback, context) {function createImageAndCallBack(data) {img.src = new Buffer(data, 'binary');// preserving original url, which seems to be lost in node-canvasimg._src = url;callback && callback.call(context, img);}var img = new Image();if (url && (url instanceof Buffer || url.indexOf('data') === 0)) {img.src = img._src = url;callback && callback.call(context, img);}else if (url && url.indexOf('http') !== 0) {requestFs(url, createImageAndCallBack);}else if (url) {request(url, 'binary', createImageAndCallBack);}else {callback && callback.call(context, url);}};fabric.loadSVGFromURL = function(url, callback, reviver) {url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();if (url.indexOf('http') !== 0) {requestFs(url, function(body) {fabric.loadSVGFromString(body.toString(), callback, reviver);});}else {request(url, '', function(body) {fabric.loadSVGFromString(body, callback, reviver);});}};fabric.loadSVGFromString = function(string, callback, reviver) {var doc = new DOMParser().parseFromString(string);fabric.parseSVGDocument(doc.documentElement, function(results, options) {callback && callback(results, options);}, reviver);};fabric.util.getScript = function(url, callback) {request(url, '', function(body) {eval(body);callback && callback();});};fabric.Image.fromObject = function(object, callback) {fabric.util.loadImage(object.src, function(img) {var oImg = new fabric.Image(img);oImg._initConfig(object);oImg._initFilters(object, function(filters) {oImg.filters = filters || [ ];callback && callback(oImg);});});};/*** Only available when running fabric on node.js* @param {Number} width Canvas width* @param {Number} height Canvas height* @param {Object} [options] Options to pass to FabricCanvas.* @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas.* @return {Object} wrapped canvas instance*/fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) {nodeCanvasOptions = nodeCanvasOptions || options;var canvasEl = fabric.document.createElement('canvas'),nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions);// jsdom doesn't create style on canvas element, so here be temp. workaroundcanvasEl.style = { };canvasEl.width = nodeCanvas.width;canvasEl.height = nodeCanvas.height;var FabricCanvas = fabric.Canvas || fabric.StaticCanvas,fabricCanvas = new FabricCanvas(canvasEl, options);fabricCanvas.contextContainer = nodeCanvas.getContext('2d');fabricCanvas.nodeCanvas = nodeCanvas;fabricCanvas.Font = Canvas.Font;return fabricCanvas;};/** @ignore */fabric.StaticCanvas.prototype.createPNGStream = function() {return this.nodeCanvas.createPNGStream();};fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {return this.nodeCanvas.createJPEGStream(opts);};var origSetWidth = fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth = function(width, options) {origSetWidth.call(this, width, options);this.nodeCanvas.width = width;return this;};if (fabric.Canvas) {fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth;}var origSetHeight = fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight = function(height, options) {origSetHeight.call(this, height, options);this.nodeCanvas.height = height;return this;};if (fabric.Canvas) {fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;}})();