diff --git a/.jshintrc b/.jshintrc
index fee95ca2..e5ac13da 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,5 +1,7 @@
{
"globals": {
+ "define": true,
+ "exports": true,
"fabric": true,
"Cufon": true,
"Event": true,
diff --git a/HEADER.js b/HEADER.js
index 0fbaa17f..db79d3e8 100644
--- a/HEADER.js
+++ b/HEADER.js
@@ -1,10 +1,10 @@
/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */
+var exports = exports || {};
var fabric = fabric || { version: "1.2.11" };
-if (typeof exports !== 'undefined') {
- exports.fabric = fabric;
-}
+// make sure exports.fabric is always defined when used as 'global' later scopes
+exports.fabric = fabric;
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
fabric.document = document;
diff --git a/README.md b/README.md
index d1149238..0f0dd068 100644
--- a/README.md
+++ b/README.md
@@ -84,6 +84,10 @@ Fabric.js started as a foundation for design editor on [printio.ru](http://print
# or Google Closure Compiler
$ node build.js modules=... minifier=closure
+4. Enable AMD support via require.js (requires uglify)
+
+ $ node build.js requirejs modules=...
+
### Demos
- [Demos](http://fabricjs.com/demos/)
@@ -114,6 +118,7 @@ These are the optional modules that could be specified for inclusion, when build
Additional flags for build script are:
+- **requirejs** — Makes fabric requirejs AMD-compatible in `dist/all.js`. *Note:* an unminified, requirejs-compatible version is always created in `dist/all.require.js`
- **no-strict** — Strips "use strict" directives from source
- **no-svg-export** — Removes svg exporting functionality
- **no-es5-compat** - Removes ES5 compat methods (Array.prototype.*, String.prototype.*, Function.prototype.*)
diff --git a/build.js b/build.js
index 22cd88fe..c45bd334 100644
--- a/build.js
+++ b/build.js
@@ -17,6 +17,21 @@ var modulesToExclude = buildArgsAsObject.exclude ? buildArgsAsObject.exclude.spl
var minifier = buildArgsAsObject.minifier || 'uglifyjs';
var mininfierCmd;
+var noStrict = 'no-strict' in buildArgsAsObject;
+var noSVGExport = 'no-svg-export' in buildArgsAsObject;
+var noES5Compat = 'no-es5-compat' in buildArgsAsObject;
+var requirejs = 'requirejs' in buildArgsAsObject ? 'requirejs' : false;
+
+// set amdLib var to encourage later support of other AMD systems
+var amdLib = requirejs;
+
+// if we want requirejs AMD support, use uglify
+var amdUglifyFlags = " -r 'require,exports,window,fabric' -e window:window,undefined ";
+if (amdLib === 'requirejs' && minifier !== 'uglifyjs') {
+ console.log('[notice]: require.js support requires uglifyjs as minifier; changed minifier to uglifyjs.');
+ minifier = 'uglifyjs';
+}
+
if (minifier === 'yui') {
mininfierCmd = 'java -jar lib/yuicompressor-2.4.6.jar dist/all.js -o dist/all.min.js';
}
@@ -24,13 +39,9 @@ else if (minifier === 'closure') {
mininfierCmd = 'java -jar lib/google_closure_compiler.jar --js dist/all.js --js_output_file dist/all.min.js';
}
else if (minifier === 'uglifyjs') {
- mininfierCmd = 'uglifyjs --output dist/all.min.js dist/all.js';
+ mininfierCmd = 'uglifyjs ' + amdUglifyFlags + ' --output dist/all.min.js dist/all.js';
}
-var noStrict = 'no-strict' in buildArgsAsObject;
-var noSVGExport = 'no-svg-export' in buildArgsAsObject;
-var noES5Compat = 'no-es5-compat' in buildArgsAsObject;
-
var buildSh = 'build-sh' in buildArgsAsObject;
var buildMinified = 'build-minified' in buildArgsAsObject;
@@ -45,6 +56,7 @@ var distFileContents =
(noStrict ? ' no-strict' : '') +
(noSVGExport ? ' no-svg-export' : '') +
(noES5Compat ? ' no-es5-compat' : '') +
+ (requirejs ? ' requirejs' : '') +
'` */';
function appendFileContents(fileNames, callback) {
@@ -101,6 +113,14 @@ function ifSpecifiedDependencyInclude(included, excluded, fileName) {
);
}
+function ifSpecifiedAMDInclude(amdLib) {
+ var supportedLibraries = ['requirejs'];
+ if (supportedLibraries.indexOf(amdLib) > -1) {
+ return 'src/amd/' + amdLib + '.js';
+ }
+ return '';
+}
+
var filesToInclude = [
'HEADER.js',
@@ -195,14 +215,16 @@ var filesToInclude = [
ifSpecifiedInclude('text', 'src/shapes/text.class.js'),
ifSpecifiedInclude('cufon', 'src/shapes/text.cufon.js'),
- ifSpecifiedInclude('node', 'src/node.js')
+ ifSpecifiedInclude('node', 'src/node.js'),
+
+ ifSpecifiedAMDInclude(amdLib)
];
if (buildMinified) {
for (var i = 0; i < filesToInclude.length; i++) {
if (!filesToInclude[i]) continue;
var fileNameWithoutSlashes = filesToInclude[i].replace(/\//g, '^');
- exec('uglifyjs -nc ' + filesToInclude[i] + ' > tmp/' + fileNameWithoutSlashes);
+ exec('uglifyjs -nc ' + amdUglifyFlags + filesToInclude[i] + ' > tmp/' + fileNameWithoutSlashes);
}
}
else if (buildSh) {
@@ -238,7 +260,16 @@ else {
throw err;
}
- console.log('Built distribution to dist/all.js');
+ // add js wrapping in AMD closure for requirejs if necessary
+ if (amdLib !== false) {
+ exec('uglifyjs dist/all.js ' + amdUglifyFlags + ' -b --output dist/all.js');
+ }
+
+ if (amdLib !== false) {
+ console.log('Built distribution to dist/all.js (' + amdLib + '-compatible)');
+ } else {
+ console.log('Built distribution to dist/all.js');
+ }
exec(mininfierCmd, function (error, output) {
if (error) {
@@ -251,6 +282,28 @@ else {
console.log('Gzipped to dist/all.min.js.gz');
});
});
+
+ // Always build requirejs AMD module in dist/all.require.js
+ // add necessary requirejs footer code to filesToInclude if we haven't before
+ if (amdLib === false) {
+ amdLib = "requirejs";
+ filesToInclude[filesToInclude.length] = ifSpecifiedAMDInclude(amdLib);
+ }
+
+ appendFileContents(filesToInclude, function() {
+ fs.writeFile('dist/all.require.js', distFileContents, function (err) {
+ if (err) {
+ console.log(err);
+ throw err;
+ }
+ exec('uglifyjs dist/all.require.js ' + amdUglifyFlags + ' -b --output dist/all.require.js');
+ console.log('Built distribution to dist/all.require.js (requirejs-compatible)');
+ });
+ });
+
});
});
+
+
+
}
diff --git a/dist/all.require.js b/dist/all.require.js
new file mode 100644
index 00000000..f8454892
--- /dev/null
+++ b/dist/all.require.js
@@ -0,0 +1,8070 @@
+(function(window, undefined) {
+ var exports = exports || {};
+ var fabric = fabric || {
+ version: "1.2.9"
+ };
+ exports.fabric = fabric;
+ if (typeof document !== "undefined" && typeof window !== "undefined") {
+ fabric.document = document;
+ fabric.window = window;
+ } else {
+ fabric.document = require("jsdom").jsdom("
");
+ fabric.window = fabric.document.createWindow();
+ }
+ fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
+ fabric.isLikelyNode = typeof Buffer !== "undefined" && typeof window === "undefined";
+ fabric.log = function() {};
+ fabric.warn = function() {};
+ if (typeof console !== "undefined") {
+ if (typeof console.log !== "undefined" && console.log.apply) {
+ fabric.log = function() {
+ return console.log.apply(console, arguments);
+ };
+ }
+ if (typeof console.warn !== "undefined" && console.warn.apply) {
+ fabric.warn = function() {
+ return console.warn.apply(console, arguments);
+ };
+ }
+ }
+ (function() {
+ function observe(eventName, handler) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = {};
+ }
+ if (arguments.length === 1) {
+ for (var prop in eventName) {
+ this.on(prop, eventName[prop]);
+ }
+ } else {
+ if (!this.__eventListeners[eventName]) {
+ this.__eventListeners[eventName] = [];
+ }
+ this.__eventListeners[eventName].push(handler);
+ }
+ }
+ function stopObserving(eventName, handler) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = {};
+ }
+ if (this.__eventListeners[eventName]) {
+ if (handler) {
+ fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
+ } else {
+ this.__eventListeners[eventName].length = 0;
+ }
+ }
+ }
+ function fire(eventName, options) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = {};
+ }
+ var listenersForEvent = this.__eventListeners[eventName];
+ if (!listenersForEvent) return;
+ for (var i = 0, len = listenersForEvent.length; i < len; i++) {
+ listenersForEvent[i](options || {});
+ }
+ }
+ fabric.Observable = {
+ observe: observe,
+ stopObserving: stopObserving,
+ fire: fire,
+ on: observe,
+ off: stopObserving,
+ trigger: fire
+ };
+ })();
+ fabric.Collection = {
+ add: function() {
+ this._objects.push.apply(this._objects, arguments);
+ for (var i = arguments.length; i--; ) {
+ this._onObjectAdded(arguments[i]);
+ }
+ this.renderOnAddRemove && this.renderAll();
+ return this;
+ },
+ 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;
+ },
+ remove: function(object) {
+ var objects = this.getObjects(), index = objects.indexOf(object);
+ if (index !== -1) {
+ objects.splice(index, 1);
+ this._onObjectRemoved(object);
+ }
+ this.renderOnAddRemove && this.renderAll();
+ return object;
+ },
+ forEachObject: function(callback, context) {
+ var objects = this.getObjects(), i = objects.length;
+ while (i--) {
+ callback.call(context, objects[i], i, objects);
+ }
+ return this;
+ },
+ item: function(index) {
+ return this.getObjects()[index];
+ },
+ isEmpty: function() {
+ return this.getObjects().length === 0;
+ },
+ size: function() {
+ return this.getObjects().length;
+ },
+ contains: function(object) {
+ return this.getObjects().indexOf(object) > -1;
+ },
+ complexity: function() {
+ return this.getObjects().reduce(function(memo, current) {
+ memo += current.complexity ? current.complexity() : 0;
+ return memo;
+ }, 0);
+ },
+ toGrayscale: function() {
+ return this.forEachObject(function(obj) {
+ obj.toGrayscale();
+ });
+ }
+ };
+ (function() {
+ var sqrt = Math.sqrt, atan2 = Math.atan2;
+ fabric.util = {};
+ function removeFromArray(array, value) {
+ var idx = array.indexOf(value);
+ if (idx !== -1) {
+ array.splice(idx, 1);
+ }
+ return array;
+ }
+ function getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+ var PiBy180 = Math.PI / 180;
+ function degreesToRadians(degrees) {
+ return degrees * PiBy180;
+ }
+ function radiansToDegrees(radians) {
+ return radians / PiBy180;
+ }
+ function rotatePoint(point, origin, radians) {
+ var sin = Math.sin(radians), cos = Math.cos(radians);
+ point.subtractEquals(origin);
+ var rx = point.x * cos - point.y * sin;
+ var ry = point.x * sin + point.y * cos;
+ return new fabric.Point(rx, ry).addEquals(origin);
+ }
+ function toFixed(number, fractionDigits) {
+ return parseFloat(Number(number).toFixed(fractionDigits));
+ }
+ function falseFunction() {
+ return false;
+ }
+ function getKlass(type, namespace) {
+ return resolveNamespace(namespace)[fabric.util.string.camelize(fabric.util.string.capitalize(type))];
+ }
+ function resolveNamespace(namespace) {
+ if (!namespace) return fabric;
+ var parts = namespace.split("."), len = parts.length, obj = fabric.window;
+ for (var i = 0; i < len; ++i) {
+ obj = obj[parts[i]];
+ }
+ return obj;
+ }
+ function loadImage(url, callback, context) {
+ if (url) {
+ var img = fabric.util.createImage();
+ img.onload = function() {
+ callback && callback.call(context, img);
+ img = img.onload = null;
+ };
+ img.src = url;
+ } else {
+ callback && callback.call(context, url);
+ }
+ }
+ function enlivenObjects(objects, callback, namespace) {
+ function onLoaded() {
+ if (++numLoadedObjects === numTotalObjects) {
+ if (callback) {
+ callback(enlivenedObjects);
+ }
+ }
+ }
+ var enlivenedObjects = [], numLoadedObjects = 0, numTotalObjects = objects.length;
+ objects.forEach(function(o, index) {
+ if (!o.type) {
+ return;
+ }
+ var klass = fabric.util.getKlass(o.type, namespace);
+ if (klass.async) {
+ klass.fromObject(o, function(o, error) {
+ if (!error) {
+ enlivenedObjects[index] = o;
+ }
+ onLoaded();
+ });
+ } else {
+ enlivenedObjects[index] = klass.fromObject(o);
+ onLoaded();
+ }
+ });
+ }
+ function groupSVGElements(elements, options, path) {
+ var object;
+ if (elements.length > 1) {
+ object = new fabric.PathGroup(elements, options);
+ } else {
+ object = elements[0];
+ }
+ if (typeof path !== "undefined") {
+ object.setSourcePath(path);
+ }
+ return object;
+ }
+ function populateWithProperties(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]];
+ }
+ }
+ }
+ }
+ function drawDashedLine(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();
+ }
+ function createCanvasElement(canvasEl) {
+ canvasEl || (canvasEl = fabric.document.createElement("canvas"));
+ if (!canvasEl.getContext && typeof G_vmlCanvasManager !== "undefined") {
+ G_vmlCanvasManager.initElement(canvasEl);
+ }
+ return canvasEl;
+ }
+ function createImage() {
+ return fabric.isLikelyNode ? new (require("canvas").Image)() : fabric.document.createElement("img");
+ }
+ function createAccessors(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;
+ if (!proto[getterName]) {
+ proto[getterName] = function(property) {
+ return new Function('return this.get("' + property + '")');
+ }(propName);
+ }
+ if (!proto[setterName]) {
+ proto[setterName] = function(property) {
+ return new Function("value", 'return this.set("' + property + '", value)');
+ }(propName);
+ }
+ }
+ }
+ function clipContext(receiver, ctx) {
+ ctx.save();
+ ctx.beginPath();
+ receiver.clipTo(ctx);
+ ctx.clip();
+ }
+ function multiplyTransformMatrices(matrixA, matrixB) {
+ var a = [ [ matrixA[0], matrixA[2], matrixA[4] ], [ matrixA[1], matrixA[3], matrixA[5] ], [ 0, 0, 1 ] ];
+ var b = [ [ matrixB[0], matrixB[2], matrixB[4] ], [ matrixB[1], matrixB[3], matrixB[5] ], [ 0, 0, 1 ] ];
+ var result = [];
+ for (var r = 0; r < 3; r++) {
+ result[r] = [];
+ for (var c = 0; c < 3; c++) {
+ var sum = 0;
+ for (var k = 0; k < 3; k++) {
+ sum += a[r][k] * b[k][c];
+ }
+ result[r][c] = sum;
+ }
+ }
+ return [ result[0][0], result[1][0], result[0][1], result[1][1], result[0][2], result[1][2] ];
+ }
+ function getFunctionBody(fn) {
+ return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
+ }
+ function drawArc(ctx, x, y, coords) {
+ var rx = coords[0];
+ var ry = coords[1];
+ var rot = coords[2];
+ var large = coords[3];
+ var sweep = coords[4];
+ var ex = coords[5];
+ var ey = coords[6];
+ var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
+ for (var i = 0; i < segs.length; i++) {
+ var bez = segmentToBezier.apply(this, segs[i]);
+ ctx.bezierCurveTo.apply(ctx, bez);
+ }
+ }
+ var arcToSegmentsCache = {}, segmentToBezierCache = {}, _join = Array.prototype.join, argsString;
+ function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
+ argsString = _join.call(arguments);
+ if (arcToSegmentsCache[argsString]) {
+ return arcToSegmentsCache[argsString];
+ }
+ var th = rotateX * (Math.PI / 180);
+ var sin_th = Math.sin(th);
+ var cos_th = Math.cos(th);
+ rx = Math.abs(rx);
+ ry = Math.abs(ry);
+ var px = cos_th * (ox - x) * .5 + sin_th * (oy - y) * .5;
+ var py = cos_th * (oy - y) * .5 - sin_th * (ox - x) * .5;
+ var pl = px * px / (rx * rx) + py * py / (ry * ry);
+ if (pl > 1) {
+ pl = Math.sqrt(pl);
+ rx *= pl;
+ ry *= pl;
+ }
+ var a00 = cos_th / rx;
+ var a01 = sin_th / rx;
+ var a10 = -sin_th / ry;
+ var a11 = cos_th / ry;
+ var x0 = a00 * ox + a01 * oy;
+ var y0 = a10 * ox + a11 * oy;
+ var x1 = a00 * x + a01 * y;
+ var y1 = a10 * x + a11 * y;
+ var d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
+ var sfactor_sq = 1 / d - .25;
+ if (sfactor_sq < 0) sfactor_sq = 0;
+ var sfactor = Math.sqrt(sfactor_sq);
+ if (sweep === large) sfactor = -sfactor;
+ var xc = .5 * (x0 + x1) - sfactor * (y1 - y0);
+ var yc = .5 * (y0 + y1) + sfactor * (x1 - x0);
+ var th0 = Math.atan2(y0 - yc, x0 - xc);
+ var th1 = Math.atan2(y1 - yc, x1 - xc);
+ var th_arc = th1 - th0;
+ if (th_arc < 0 && sweep === 1) {
+ th_arc += 2 * Math.PI;
+ } else if (th_arc > 0 && sweep === 0) {
+ th_arc -= 2 * Math.PI;
+ }
+ var segments = Math.ceil(Math.abs(th_arc / (Math.PI * .5 + .001)));
+ var result = [];
+ for (var i = 0; i < segments; i++) {
+ var th2 = th0 + i * th_arc / segments;
+ var th3 = th0 + (i + 1) * th_arc / segments;
+ result[i] = [ xc, yc, th2, th3, rx, ry, sin_th, cos_th ];
+ }
+ arcToSegmentsCache[argsString] = result;
+ return result;
+ }
+ function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
+ argsString = _join.call(arguments);
+ if (segmentToBezierCache[argsString]) {
+ return segmentToBezierCache[argsString];
+ }
+ var a00 = cos_th * rx;
+ var a01 = -sin_th * ry;
+ var a10 = sin_th * rx;
+ var a11 = cos_th * ry;
+ var th_half = .5 * (th1 - th0);
+ var t = 8 / 3 * Math.sin(th_half * .5) * Math.sin(th_half * .5) / Math.sin(th_half);
+ var x1 = cx + Math.cos(th0) - t * Math.sin(th0);
+ var y1 = cy + Math.sin(th0) + t * Math.cos(th0);
+ var x3 = cx + Math.cos(th1);
+ var y3 = cy + Math.sin(th1);
+ var x2 = x3 + t * Math.sin(th1);
+ var y2 = y3 - t * Math.cos(th1);
+ segmentToBezierCache[argsString] = [ a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 ];
+ return segmentToBezierCache[argsString];
+ }
+ fabric.util.removeFromArray = removeFromArray;
+ fabric.util.degreesToRadians = degreesToRadians;
+ fabric.util.radiansToDegrees = radiansToDegrees;
+ fabric.util.rotatePoint = rotatePoint;
+ fabric.util.toFixed = toFixed;
+ fabric.util.getRandomInt = getRandomInt;
+ fabric.util.falseFunction = falseFunction;
+ fabric.util.getKlass = getKlass;
+ fabric.util.resolveNamespace = resolveNamespace;
+ fabric.util.loadImage = loadImage;
+ fabric.util.enlivenObjects = enlivenObjects;
+ fabric.util.groupSVGElements = groupSVGElements;
+ fabric.util.populateWithProperties = populateWithProperties;
+ fabric.util.drawDashedLine = drawDashedLine;
+ fabric.util.createCanvasElement = createCanvasElement;
+ fabric.util.createImage = createImage;
+ fabric.util.createAccessors = createAccessors;
+ fabric.util.clipContext = clipContext;
+ fabric.util.multiplyTransformMatrices = multiplyTransformMatrices;
+ fabric.util.getFunctionBody = getFunctionBody;
+ fabric.util.drawArc = drawArc;
+ })();
+ (function() {
+ var slice = Array.prototype.slice;
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function(searchElement) {
+ 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) {
+ n = 0;
+ } else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+ for (;k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ return -1;
+ };
+ }
+ if (!Array.prototype.forEach) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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];
+ if (fn.call(context, val, i, this)) {
+ result.push(val);
+ }
+ }
+ }
+ return result;
+ };
+ }
+ if (!Array.prototype.reduce) {
+ Array.prototype.reduce = function(fn) {
+ 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 (++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;
+ };
+ }
+ 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;
+ }
+ function max(array, byProperty) {
+ if (!array || array.length === 0) return undefined;
+ var i = array.length - 1, result = byProperty ? array[i][byProperty] : array[i];
+ if (byProperty) {
+ while (i--) {
+ if (array[i][byProperty] >= result) {
+ result = array[i][byProperty];
+ }
+ }
+ } else {
+ while (i--) {
+ if (array[i] >= result) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+ function min(array, byProperty) {
+ if (!array || array.length === 0) return undefined;
+ var i = array.length - 1, result = byProperty ? array[i][byProperty] : array[i];
+ if (byProperty) {
+ while (i--) {
+ if (array[i][byProperty] < result) {
+ result = array[i][byProperty];
+ }
+ }
+ } else {
+ while (i--) {
+ if (array[i] < result) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+ fabric.util.array = {
+ invoke: invoke,
+ min: min,
+ max: max
+ };
+ })();
+ (function() {
+ function extend(destination, source) {
+ for (var property in source) {
+ destination[property] = source[property];
+ }
+ return destination;
+ }
+ function clone(object) {
+ return extend({}, object);
+ }
+ fabric.util.object = {
+ extend: extend,
+ clone: clone
+ };
+ })();
+ (function() {
+ if (!String.prototype.trim) {
+ String.prototype.trim = function() {
+ return this.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, "");
+ };
+ }
+ function camelize(string) {
+ return string.replace(/-+(.)?/g, function(match, character) {
+ return character ? character.toUpperCase() : "";
+ });
+ }
+ function capitalize(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+ }
+ function escapeXml(string) {
+ return string.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(//g, ">");
+ }
+ fabric.util.string = {
+ camelize: camelize,
+ capitalize: capitalize,
+ escapeXml: escapeXml
+ };
+ })();
+ (function() {
+ var slice = Array.prototype.slice, apply = Function.prototype.apply, Dummy = function() {};
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function(thisArg) {
+ var fn = this, args = slice.call(arguments, 1), bound;
+ if (args.length) {
+ bound = function() {
+ return apply.call(fn, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
+ };
+ } else {
+ bound = function() {
+ return apply.call(fn, this instanceof Dummy ? this : thisArg, arguments);
+ };
+ }
+ Dummy.prototype = this.prototype;
+ bound.prototype = new Dummy();
+ return bound;
+ };
+ }
+ })();
+ (function() {
+ var slice = Array.prototype.slice, emptyFunction = function() {};
+ var IS_DONTENUM_BUGGY = function() {
+ for (var p in {
+ toString: 1
+ }) {
+ if (p === "toString") return false;
+ }
+ return true;
+ }();
+ var addMethods = function(klass, source, parent) {
+ for (var property in source) {
+ if (property in klass.prototype && typeof klass.prototype[property] === "function" && (source[property] + "").indexOf("callSuper") > -1) {
+ klass.prototype[property] = function(property) {
+ return function() {
+ var superclass = this.constructor.superclass;
+ this.constructor.superclass = parent;
+ var returnValue = source[property].apply(this, arguments);
+ this.constructor.superclass = superclass;
+ if (property !== "initialize") {
+ return returnValue;
+ }
+ };
+ }(property);
+ } else {
+ klass.prototype[property] = source[property];
+ }
+ if (IS_DONTENUM_BUGGY) {
+ if (source.toString !== Object.prototype.toString) {
+ klass.prototype.toString = source.toString;
+ }
+ if (source.valueOf !== Object.prototype.valueOf) {
+ klass.prototype.valueOf = source.valueOf;
+ }
+ }
+ }
+ };
+ function Subclass() {}
+ function callSuper(methodName) {
+ var fn = this.constructor.superclass.prototype[methodName];
+ return arguments.length > 1 ? fn.apply(this, slice.call(arguments, 1)) : fn.call(this);
+ }
+ 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() {
+ function areHostMethods(object) {
+ var methodNames = Array.prototype.slice.call(arguments, 1), t, i, len = methodNames.length;
+ for (i = 0; i < len; i++) {
+ t = typeof object[methodNames[i]];
+ if (!/^(?:function|object|unknown)$/.test(t)) return false;
+ }
+ return true;
+ }
+ var getUniqueId = function() {
+ var uid = 0;
+ return function(element) {
+ return element.__uniqueID || (element.__uniqueID = "uniqueID__" + uid++);
+ };
+ }();
+ var getElement, setElement;
+ (function() {
+ var elements = {};
+ getElement = function(uid) {
+ return elements[uid];
+ };
+ setElement = function(uid, element) {
+ elements[uid] = element;
+ };
+ })();
+ function createListener(uid, handler) {
+ return {
+ handler: handler,
+ wrappedHandler: createWrappedHandler(uid, handler)
+ };
+ }
+ function createWrappedHandler(uid, handler) {
+ return function(e) {
+ handler.call(getElement(uid), e || fabric.window.event);
+ };
+ }
+ 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"), listeners = {}, handlers = {}, addListener, removeListener;
+ if (shouldUseAddListenerRemoveListener) {
+ addListener = function(element, eventName, handler) {
+ element.addEventListener(eventName, handler, false);
+ };
+ removeListener = function(element, eventName, handler) {
+ element.removeEventListener(eventName, handler, false);
+ };
+ } else if (shouldUseAttachEventDetachEvent) {
+ 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);
+ };
+ 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 {
+ 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);
+ };
+ 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);
+ }
+ }
+ }
+ };
+ }
+ fabric.util.addListener = addListener;
+ fabric.util.removeListener = removeListener;
+ function getPointer(event, upperCanvasEl) {
+ event || (event = fabric.window.event);
+ var element = event.target || (typeof event.srcElement !== "unknown" ? event.srcElement : null), body = fabric.document.body || {
+ scrollLeft: 0,
+ scrollTop: 0
+ }, docElement = fabric.document.documentElement, orgElement = element, scrollLeft = 0, scrollTop = 0, firstFixedAncestor;
+ while (element && element.parentNode && !firstFixedAncestor) {
+ element = element.parentNode;
+ if (element !== fabric.document && fabric.util.getElementStyle(element, "position") === "fixed") {
+ firstFixedAncestor = element;
+ }
+ if (element !== fabric.document && orgElement !== upperCanvasEl && fabric.util.getElementStyle(element, "position") === "absolute") {
+ scrollLeft = 0;
+ scrollTop = 0;
+ } else if (element === fabric.document) {
+ scrollLeft = body.scrollLeft || docElement.scrollLeft || 0;
+ scrollTop = body.scrollTop || docElement.scrollTop || 0;
+ } else {
+ scrollLeft += element.scrollLeft || 0;
+ scrollTop += element.scrollTop || 0;
+ }
+ }
+ return {
+ x: pointerX(event) + scrollLeft,
+ y: pointerY(event) + scrollTop
+ };
+ }
+ var pointerX = function(event) {
+ return typeof event.clientX !== "unknown" ? event.clientX : 0;
+ };
+ var pointerY = function(event) {
+ return typeof event.clientY !== "unknown" ? event.clientY : 0;
+ };
+ if (fabric.isTouchSupported) {
+ pointerX = function(event) {
+ if (event.type !== "touchend") {
+ return event.touches && event.touches[0] ? event.touches[0].pageX - (event.touches[0].pageX - event.touches[0].clientX) || event.clientX : event.clientX;
+ }
+ return event.changedTouches && event.changedTouches[0] ? event.changedTouches[0].pageX - (event.changedTouches[0].pageX - event.changedTouches[0].clientX) || event.clientX : event.clientX;
+ };
+ pointerY = function(event) {
+ if (event.type !== "touchend") {
+ return event.touches && event.touches[0] ? event.touches[0].pageY - (event.touches[0].pageY - event.touches[0].clientY) || event.clientY : event.clientY;
+ }
+ return event.changedTouches && event.changedTouches[0] ? event.changedTouches[0].pageY - (event.changedTouches[0].pageY - event.changedTouches[0].clientY) || event.clientY : event.clientY;
+ };
+ }
+ fabric.util.getPointer = getPointer;
+ fabric.util.object.extend(fabric.util, fabric.Observable);
+ })();
+ (function() {
+ function setStyle(element, styles) {
+ var elementStyle = element.style;
+ if (!elementStyle) {
+ return element;
+ }
+ if (typeof styles === "string") {
+ element.style.cssText += ";" + styles;
+ return styles.indexOf("opacity") > -1 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+ }
+ for (var property in styles) {
+ if (property === "opacity") {
+ setOpacity(element, styles[property]);
+ } else {
+ var normalizedProperty = property === "float" || property === "cssFloat" ? typeof elementStyle.styleFloat === "undefined" ? "cssFloat" : "styleFloat" : property;
+ elementStyle[normalizedProperty] = styles[property];
+ }
+ }
+ return element;
+ }
+ var parseEl = fabric.document.createElement("div"), supportsOpacity = typeof parseEl.style.opacity === "string", supportsFilters = typeof parseEl.style.filter === "string", reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, setOpacity = function(element) {
+ return element;
+ };
+ if (supportsOpacity) {
+ setOpacity = function(element, value) {
+ element.style.opacity = value;
+ return element;
+ };
+ } else if (supportsFilters) {
+ setOpacity = function(element, value) {
+ var es = element.style;
+ if (element.currentStyle && !element.currentStyle.hasLayout) {
+ es.zoom = 1;
+ }
+ if (reOpacity.test(es.filter)) {
+ value = value >= .9999 ? "" : "alpha(opacity=" + value * 100 + ")";
+ es.filter = es.filter.replace(reOpacity, value);
+ } else {
+ es.filter += " alpha(opacity=" + value * 100 + ")";
+ }
+ return element;
+ };
+ }
+ fabric.util.setStyle = setStyle;
+ })();
+ (function() {
+ var _slice = Array.prototype.slice;
+ function getById(id) {
+ return typeof id === "string" ? fabric.document.getElementById(id) : id;
+ }
+ var toArray = function(arrayLike) {
+ return _slice.call(arrayLike, 0);
+ };
+ var sliceCanConvertNodelists;
+ try {
+ sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
+ } catch (err) {}
+ if (!sliceCanConvertNodelists) {
+ toArray = function(arrayLike) {
+ var arr = new Array(arrayLike.length), i = arrayLike.length;
+ while (i--) {
+ arr[i] = arrayLike[i];
+ }
+ return arr;
+ };
+ }
+ function makeElement(tagName, attributes) {
+ var el = fabric.document.createElement(tagName);
+ for (var prop in attributes) {
+ if (prop === "class") {
+ el.className = attributes[prop];
+ } else if (prop === "for") {
+ el.htmlFor = attributes[prop];
+ } else {
+ el.setAttribute(prop, attributes[prop]);
+ }
+ }
+ return el;
+ }
+ function addClass(element, className) {
+ if ((" " + element.className + " ").indexOf(" " + className + " ") === -1) {
+ element.className += (element.className ? " " : "") + className;
+ }
+ }
+ function wrapElement(element, wrapper, attributes) {
+ if (typeof wrapper === "string") {
+ wrapper = makeElement(wrapper, attributes);
+ }
+ if (element.parentNode) {
+ element.parentNode.replaceChild(wrapper, element);
+ }
+ wrapper.appendChild(element);
+ return wrapper;
+ }
+ function getElementOffset(element) {
+ var docElem, win, box = {
+ left: 0,
+ top: 0
+ }, doc = element && element.ownerDocument, offset = {
+ left: 0,
+ top: 0
+ }, 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();
+ }
+ if (doc != null && doc === doc.window) {
+ win = doc;
+ } else {
+ win = doc.nodeType === 9 && (doc.defaultView || doc.parentWindow);
+ }
+ return {
+ left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0) + offset.left,
+ top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0) + offset.top
+ };
+ }
+ function getElementStyle(element, attr) {
+ if (!element.style) {
+ element.style = {};
+ }
+ if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
+ return fabric.document.defaultView.getComputedStyle(element, null)[attr];
+ } else {
+ var value = element.style[attr];
+ if (!value && element.currentStyle) value = element.currentStyle[attr];
+ return value;
+ }
+ }
+ (function() {
+ var style = fabric.document.documentElement.style;
+ var selectProp = "userSelect" in style ? "userSelect" : "MozUserSelect" in style ? "MozUserSelect" : "WebkitUserSelect" in style ? "WebkitUserSelect" : "KhtmlUserSelect" in style ? "KhtmlUserSelect" : "";
+ function makeElementUnselectable(element) {
+ if (typeof element.onselectstart !== "undefined") {
+ element.onselectstart = fabric.util.falseFunction;
+ }
+ if (selectProp) {
+ element.style[selectProp] = "none";
+ } else if (typeof element.unselectable === "string") {
+ element.unselectable = "on";
+ }
+ return element;
+ }
+ function makeElementSelectable(element) {
+ if (typeof element.onselectstart !== "undefined") {
+ element.onselectstart = null;
+ }
+ if (selectProp) {
+ element.style[selectProp] = "";
+ } else if (typeof element.unselectable === "string") {
+ element.unselectable = "";
+ }
+ return element;
+ }
+ fabric.util.makeElementUnselectable = makeElementUnselectable;
+ fabric.util.makeElementSelectable = makeElementSelectable;
+ })();
+ (function() {
+ function getScript(url, callback) {
+ var headEl = fabric.document.getElementsByTagName("head")[0], scriptEl = fabric.document.createElement("script"), loading = true;
+ scriptEl.onload = scriptEl.onreadystatechange = function(e) {
+ if (loading) {
+ if (typeof this.readyState === "string" && this.readyState !== "loaded" && this.readyState !== "complete") return;
+ loading = false;
+ callback(e || fabric.window.event);
+ scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
+ }
+ };
+ scriptEl.src = url;
+ headEl.appendChild(scriptEl);
+ }
+ fabric.util.getScript = getScript;
+ })();
+ fabric.util.getById = getById;
+ fabric.util.toArray = toArray;
+ fabric.util.makeElement = makeElement;
+ fabric.util.addClass = addClass;
+ fabric.util.wrapElement = wrapElement;
+ 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() {}
+ function request(url, options) {
+ options || (options = {});
+ var method = options.method ? options.method.toUpperCase() : "GET", onComplete = options.onComplete || function() {}, xhr = makeXHR(), body;
+ 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;
+ })();
+ (function() {
+ function animate(options) {
+ options || (options = {});
+ var start = +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() {
+ time = +new Date();
+ var currentTime = time > finish ? duration : time - start;
+ onChange(easing(currentTime, startValue, byValue, duration));
+ if (time > finish || abort()) {
+ options.onComplete && options.onComplete();
+ return;
+ }
+ requestAnimFrame(tick);
+ })();
+ }
+ var _requestAnimFrame = fabric.window.requestAnimationFrame || fabric.window.webkitRequestAnimationFrame || fabric.window.mozRequestAnimationFrame || fabric.window.oRequestAnimationFrame || fabric.window.msRequestAnimationFrame || function(callback) {
+ fabric.window.setTimeout(callback, 1e3 / 60);
+ };
+ var requestAnimFrame = function() {
+ return _requestAnimFrame.apply(fabric.window, arguments);
+ };
+ fabric.util.animate = animate;
+ fabric.util.requestAnimFrame = requestAnimFrame;
+ })();
+ (function() {
+ function easeInQuad(t, b, c, d) {
+ return c * (t /= d) * t + b;
+ }
+ function easeOutQuad(t, b, c, d) {
+ return -c * (t /= d) * (t - 2) + b;
+ }
+ function easeInOutQuad(t, b, c, d) {
+ t /= d / 2;
+ if (t < 1) return c / 2 * t * t + b;
+ return -c / 2 * (--t * (t - 2) - 1) + b;
+ }
+ function easeInCubic(t, b, c, d) {
+ return c * (t /= d) * t * t + b;
+ }
+ function easeOutCubic(t, b, c, d) {
+ return c * ((t = t / d - 1) * t * t + 1) + b;
+ }
+ function easeInOutCubic(t, b, c, d) {
+ t /= d / 2;
+ if (t < 1) return c / 2 * t * t * t + b;
+ return c / 2 * ((t -= 2) * t * t + 2) + b;
+ }
+ function easeInQuart(t, b, c, d) {
+ return c * (t /= d) * t * t * t + b;
+ }
+ function easeOutQuart(t, b, c, d) {
+ return -c * ((t = t / d - 1) * t * t * t - 1) + b;
+ }
+ function easeInOutQuart(t, b, c, d) {
+ t /= d / 2;
+ if (t < 1) return c / 2 * t * t * t * t + b;
+ return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
+ }
+ function easeInQuint(t, b, c, d) {
+ return c * (t /= d) * t * t * t * t + b;
+ }
+ function easeOutQuint(t, b, c, d) {
+ return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
+ }
+ function easeInOutQuint(t, b, c, d) {
+ t /= d / 2;
+ if (t < 1) return c / 2 * t * t * t * t * t + b;
+ return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
+ }
+ function easeInSine(t, b, c, d) {
+ return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
+ }
+ function easeOutSine(t, b, c, d) {
+ return c * Math.sin(t / d * (Math.PI / 2)) + b;
+ }
+ function easeInOutSine(t, b, c, d) {
+ return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
+ }
+ function easeInExpo(t, b, c, d) {
+ return t === 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
+ }
+ function easeOutExpo(t, b, c, d) {
+ return t === d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
+ }
+ 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;
+ }
+ function easeInCirc(t, b, c, d) {
+ return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
+ }
+ function easeOutCirc(t, b, c, d) {
+ return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
+ }
+ 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;
+ }
+ function easeInElastic(t, b, c, d) {
+ var s = 1.70158;
+ var p = 0;
+ var a = c;
+ if (t === 0) return b;
+ t /= d;
+ if (t === 1) return b + c;
+ if (!p) p = d * .3;
+ if (a < Math.abs(c)) {
+ a = c;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(c / a);
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * 2 * Math.PI / p)) + b;
+ }
+ function easeOutElastic(t, b, c, d) {
+ var s = 1.70158;
+ var p = 0;
+ var a = c;
+ if (t === 0) return b;
+ t /= d;
+ if (t === 1) return b + c;
+ if (!p) p = d * .3;
+ if (a < Math.abs(c)) {
+ a = c;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(c / a);
+ return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * 2 * Math.PI / p) + c + b;
+ }
+ function easeInOutElastic(t, b, c, d) {
+ var s = 1.70158;
+ var p = 0;
+ var a = c;
+ if (t === 0) return b;
+ t /= d / 2;
+ if (t === 2) return b + c;
+ if (!p) p = d * .3 * 1.5;
+ if (a < Math.abs(c)) {
+ a = c;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(c / a);
+ if (t < 1) return -.5 * a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * 2 * Math.PI / p) + b;
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * 2 * Math.PI / p) * .5 + c + b;
+ }
+ function easeInBack(t, b, c, d, s) {
+ if (s === undefined) s = 1.70158;
+ return c * (t /= d) * t * ((s + 1) * t - s) + b;
+ }
+ function easeOutBack(t, b, c, d, s) {
+ if (s === undefined) s = 1.70158;
+ return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
+ }
+ 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;
+ }
+ function easeInBounce(t, b, c, d) {
+ return c - easeOutBounce(d - t, 0, c, d) + b;
+ }
+ function easeOutBounce(t, b, c, d) {
+ if ((t /= d) < 1 / 2.75) {
+ return c * 7.5625 * t * t + b;
+ } else if (t < 2 / 2.75) {
+ return c * (7.5625 * (t -= 1.5 / 2.75) * t + .75) + b;
+ } else if (t < 2.5 / 2.75) {
+ return c * (7.5625 * (t -= 2.25 / 2.75) * t + .9375) + b;
+ } else {
+ return c * (7.5625 * (t -= 2.625 / 2.75) * t + .984375) + b;
+ }
+ }
+ function easeInOutBounce(t, b, c, d) {
+ if (t < d / 2) return easeInBounce(t * 2, 0, c, d) * .5 + b;
+ return easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
+ }
+ fabric.util.ease = {
+ easeInQuad: easeInQuad,
+ easeOutQuad: easeOutQuad,
+ easeInOutQuad: easeInOutQuad,
+ easeInCubic: easeInCubic,
+ easeOutCubic: easeOutCubic,
+ easeInOutCubic: easeInOutCubic,
+ easeInQuart: easeInQuart,
+ easeOutQuart: easeOutQuart,
+ easeInOutQuart: easeInOutQuart,
+ easeInQuint: easeInQuint,
+ easeOutQuint: easeOutQuint,
+ easeInOutQuint: easeInOutQuint,
+ easeInSine: easeInSine,
+ easeOutSine: easeOutSine,
+ easeInOutSine: easeInOutSine,
+ easeInExpo: easeInExpo,
+ easeOutExpo: easeOutExpo,
+ easeInOutExpo: easeInOutExpo,
+ easeInCirc: easeInCirc,
+ easeOutCirc: easeOutCirc,
+ easeInOutCirc: easeInOutCirc,
+ easeInElastic: easeInElastic,
+ easeOutElastic: easeOutElastic,
+ easeInOutElastic: easeInOutElastic,
+ easeInBack: easeInBack,
+ easeOutBack: easeOutBack,
+ easeInOutBack: easeInOutBack,
+ easeInBounce: easeInBounce,
+ easeOutBounce: easeOutBounce,
+ easeInOutBounce: easeInOutBounce
+ };
+ })();
+ (function(global) {
+ "use strict";
+ var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, multiplyTransformMatrices = fabric.util.multiplyTransformMatrices;
+ fabric.SHARED_ATTRIBUTES = [ "transform", "fill", "fill-opacity", "fill-rule", "opacity", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width" ];
+ var attributesMap = {
+ "fill-opacity": "fillOpacity",
+ "fill-rule": "fillRule",
+ "font-family": "fontFamily",
+ "font-size": "fontSize",
+ "font-style": "fontStyle",
+ "font-weight": "fontWeight",
+ cx: "left",
+ x: "left",
+ r: "radius",
+ "stroke-dasharray": "strokeDashArray",
+ "stroke-linecap": "strokeLineCap",
+ "stroke-linejoin": "strokeLineJoin",
+ "stroke-miterlimit": "strokeMiterLimit",
+ "stroke-opacity": "strokeOpacity",
+ "stroke-width": "strokeWidth",
+ "text-decoration": "textDecoration",
+ cy: "top",
+ y: "top",
+ transform: "transformMatrix"
+ };
+ var colorAttributes = {
+ stroke: "strokeOpacity",
+ fill: "fillOpacity"
+ };
+ function normalizeAttr(attr) {
+ if (attr in attributesMap) {
+ return attributesMap[attr];
+ }
+ return attr;
+ }
+ function normalizeValue(attr, value, parentAttributes) {
+ var isArray;
+ if ((attr === "fill" || attr === "stroke") && value === "none") {
+ value = "";
+ } else if (attr === "fillRule") {
+ value = value === "evenodd" ? "destination-over" : value;
+ } else if (attr === "strokeDashArray") {
+ value = value.replace(/,/g, " ").split(/\s+/);
+ } else if (attr === "transformMatrix") {
+ if (parentAttributes && parentAttributes.transformMatrix) {
+ value = multiplyTransformMatrices(parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
+ } else {
+ value = fabric.parseTransformAttribute(value);
+ }
+ }
+ isArray = Object.prototype.toString.call(value) === "[object Array]";
+ var parsed = isArray ? value.map(parseFloat) : parseFloat(value);
+ return !isArray && isNaN(parsed) ? value : parsed;
+ }
+ function _setStrokeFillOpacity(attributes) {
+ for (var attr in colorAttributes) {
+ if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === "undefined") continue;
+ if (attributes[attr].indexOf("url(") === 0) continue;
+ var color = new fabric.Color(attributes[attr]);
+ attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
+ delete attributes[colorAttributes[attr]];
+ }
+ return attributes;
+ }
+ function parseAttributes(element, attributes) {
+ if (!element) {
+ return;
+ }
+ var value, parentAttributes = {};
+ if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) {
+ parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
+ }
+ var ownAttributes = attributes.reduce(function(memo, attr) {
+ value = element.getAttribute(attr);
+ if (value) {
+ attr = normalizeAttr(attr);
+ value = normalizeValue(attr, value, parentAttributes);
+ memo[attr] = value;
+ }
+ return memo;
+ }, {});
+ ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element)));
+ return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
+ }
+ fabric.parseTransformAttribute = function() {
+ function rotateMatrix(matrix, args) {
+ var angle = args[0];
+ matrix[0] = Math.cos(angle);
+ matrix[1] = Math.sin(angle);
+ matrix[2] = -Math.sin(angle);
+ matrix[3] = Math.cos(angle);
+ }
+ function scaleMatrix(matrix, args) {
+ var multiplierX = args[0], multiplierY = args.length === 2 ? args[1] : args[0];
+ matrix[0] = multiplierX;
+ matrix[3] = multiplierY;
+ }
+ function skewXMatrix(matrix, args) {
+ matrix[2] = args[0];
+ }
+ function skewYMatrix(matrix, args) {
+ matrix[1] = args[0];
+ }
+ function translateMatrix(matrix, args) {
+ matrix[4] = args[0];
+ if (args.length === 2) {
+ matrix[5] = args[1];
+ }
+ }
+ var iMatrix = [ 1, 0, 0, 1, 0, 0 ], number = "(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)", comma_wsp = "(?:\\s+,?\\s*|,\\s*)", skewX = "(?:(skewX)\\s*\\(\\s*(" + number + ")\\s*\\))", skewY = "(?:(skewY)\\s*\\(\\s*(" + number + ")\\s*\\))", rotate = "(?:(rotate)\\s*\\(\\s*(" + number + ")(?:" + comma_wsp + "(" + number + ")" + comma_wsp + "(" + number + "))?\\s*\\))", scale = "(?:(scale)\\s*\\(\\s*(" + number + ")(?:" + comma_wsp + "(" + number + "))?\\s*\\))", translate = "(?:(translate)\\s*\\(\\s*(" + number + ")(?:" + comma_wsp + "(" + number + "))?\\s*\\))", matrix = "(?:(matrix)\\s*\\(\\s*" + "(" + number + ")" + comma_wsp + "(" + number + ")" + comma_wsp + "(" + number + ")" + comma_wsp + "(" + number + ")" + comma_wsp + "(" + number + ")" + comma_wsp + "(" + number + ")" + "\\s*\\))", transform = "(?:" + matrix + "|" + translate + "|" + scale + "|" + rotate + "|" + skewX + "|" + skewY + ")", transforms = "(?:" + transform + "(?:" + comma_wsp + transform + ")*" + ")", transform_list = "^\\s*(?:" + transforms + "?)\\s*$", reTransformList = new RegExp(transform_list), reTransform = new RegExp(transform, "g");
+ return function(attributeValue) {
+ var matrix = iMatrix.concat();
+ var matrices = [];
+ if (!attributeValue || attributeValue && !reTransformList.test(attributeValue)) {
+ return matrix;
+ }
+ attributeValue.replace(reTransform, function(match) {
+ var m = new RegExp(transform).exec(match).filter(function(match) {
+ return match !== "" && match != null;
+ }), operation = m[1], args = m.slice(2).map(parseFloat);
+ switch (operation) {
+ case "translate":
+ translateMatrix(matrix, args);
+ break;
+
+ case "rotate":
+ rotateMatrix(matrix, args);
+ break;
+
+ case "scale":
+ scaleMatrix(matrix, args);
+ break;
+
+ case "skewX":
+ skewXMatrix(matrix, args);
+ break;
+
+ case "skewY":
+ skewYMatrix(matrix, args);
+ break;
+
+ case "matrix":
+ matrix = args;
+ break;
+ }
+ matrices.push(matrix.concat());
+ matrix = iMatrix.concat();
+ });
+ var combinedMatrix = matrices[0];
+ while (matrices.length > 1) {
+ matrices.shift();
+ combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
+ }
+ return combinedMatrix;
+ };
+ }();
+ function parsePointsAttribute(points) {
+ if (!points) return null;
+ points = points.trim();
+ var asPairs = points.indexOf(",") > -1;
+ points = points.split(/\s+/);
+ var parsedPoints = [], i, len;
+ if (asPairs) {
+ i = 0;
+ len = points.length;
+ for (;i < len; i++) {
+ var pair = points[i].split(",");
+ parsedPoints.push({
+ x: parseFloat(pair[0]),
+ y: parseFloat(pair[1])
+ });
+ }
+ } else {
+ i = 0;
+ len = points.length;
+ for (;i < len; i += 2) {
+ parsedPoints.push({
+ x: parseFloat(points[i]),
+ y: parseFloat(points[i + 1])
+ });
+ }
+ }
+ if (parsedPoints.length % 2 !== 0) {}
+ return parsedPoints;
+ }
+ function parseFontDeclaration(value, oStyle) {
+ var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);
+ if (!match) return;
+ var fontStyle = match[1];
+ var fontWeight = match[3];
+ var fontSize = match[4];
+ var lineHeight = match[5];
+ var fontFamily = match[6];
+ if (fontStyle) {
+ oStyle.fontStyle = fontStyle;
+ }
+ if (fontWeight) {
+ oStyle.fontSize = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
+ }
+ if (fontSize) {
+ oStyle.fontSize = parseFloat(fontSize);
+ }
+ if (fontFamily) {
+ oStyle.fontFamily = fontFamily;
+ }
+ if (lineHeight) {
+ oStyle.lineHeight = lineHeight === "normal" ? 1 : lineHeight;
+ }
+ }
+ function parseStyleAttribute(element) {
+ var oStyle = {}, style = element.getAttribute("style"), attr, value;
+ if (!style) return oStyle;
+ if (typeof style === "string") {
+ style.replace(/;$/, "").split(";").forEach(function(chunk) {
+ var pair = chunk.split(":");
+ attr = normalizeAttr(pair[0].trim().toLowerCase());
+ value = normalizeValue(attr, pair[1].trim());
+ if (attr === "font") {
+ parseFontDeclaration(value, oStyle);
+ } else {
+ oStyle[attr] = value;
+ }
+ });
+ } else {
+ for (var prop in style) {
+ if (typeof style[prop] === "undefined") continue;
+ attr = normalizeAttr(prop.toLowerCase());
+ value = normalizeValue(attr, style[prop]);
+ if (attr === "font") {
+ parseFontDeclaration(value, oStyle);
+ } else {
+ oStyle[attr] = value;
+ }
+ }
+ }
+ return oStyle;
+ }
+ function resolveGradients(instances) {
+ for (var i = instances.length; i--; ) {
+ var instanceFillValue = instances[i].get("fill");
+ if (/^url\(/.test(instanceFillValue)) {
+ var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
+ if (fabric.gradientDefs[gradientId]) {
+ instances[i].set("fill", fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i]));
+ }
+ }
+ }
+ }
+ function parseElements(elements, callback, options, reviver) {
+ var instances = new Array(elements.length), i = elements.length;
+ function checkIfDone() {
+ if (--i === 0) {
+ instances = instances.filter(function(el) {
+ return el != null;
+ });
+ resolveGradients(instances);
+ callback(instances);
+ }
+ }
+ for (var index = 0, el, len = elements.length; index < len; index++) {
+ el = elements[index];
+ var klass = fabric[capitalize(el.tagName)];
+ if (klass && klass.fromElement) {
+ try {
+ if (klass.async) {
+ klass.fromElement(el, function(index, el) {
+ return function(obj) {
+ reviver && reviver(el, obj);
+ instances.splice(index, 0, obj);
+ checkIfDone();
+ };
+ }(index, el), options);
+ } else {
+ var obj = klass.fromElement(el, options);
+ reviver && reviver(el, obj);
+ instances.splice(index, 0, obj);
+ checkIfDone();
+ }
+ } catch (err) {
+ fabric.log(err);
+ }
+ } else {
+ checkIfDone();
+ }
+ }
+ }
+ function getCSSRules(doc) {
+ var styles = doc.getElementsByTagName("style"), allRules = {}, rules;
+ for (var i = 0, len = styles.length; i < len; i++) {
+ var styleContents = styles[0].textContent;
+ styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, "");
+ rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
+ rules = rules.map(function(rule) {
+ return rule.trim();
+ });
+ rules.forEach(function(rule) {
+ var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/);
+ rule = match[1];
+ var declaration = match[2].trim(), propertyValuePairs = declaration.replace(/;$/, "").split(/\s*;\s*/);
+ if (!allRules[rule]) {
+ allRules[rule] = {};
+ }
+ for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
+ var pair = propertyValuePairs[i].split(/\s*:\s*/), property = pair[0], value = pair[1];
+ allRules[rule][property] = value;
+ }
+ });
+ }
+ return allRules;
+ }
+ function getGlobalStylesForElement(element) {
+ var nodeName = element.nodeName, className = element.getAttribute("class"), id = element.getAttribute("id"), styles = {};
+ for (var rule in fabric.cssRules) {
+ var ruleMatchesElement = className && new RegExp("^\\." + className).test(rule) || id && new RegExp("^#" + id).test(rule) || new RegExp("^" + nodeName).test(rule);
+ if (ruleMatchesElement) {
+ for (var property in fabric.cssRules[rule]) {
+ styles[property] = fabric.cssRules[rule][property];
+ }
+ }
+ }
+ return styles;
+ }
+ fabric.parseSVGDocument = function() {
+ var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/;
+ var reNum = "(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)";
+ var reViewBoxAttrValue = new RegExp("^" + "\\s*(" + reNum + "+)\\s*,?" + "\\s*(" + reNum + "+)\\s*,?" + "\\s*(" + reNum + "+)\\s*,?" + "\\s*(" + reNum + "+)\\s*" + "$");
+ function hasAncestorWithNodeName(element, nodeName) {
+ while (element && (element = element.parentNode)) {
+ if (nodeName.test(element.nodeName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return function(doc, callback, reviver) {
+ if (!doc) return;
+ var startTime = new Date(), descendants = fabric.util.toArray(doc.getElementsByTagName("*"));
+ if (descendants.length === 0) {
+ descendants = doc.selectNodes("//*[name(.)!='svg']");
+ var arr = [];
+ for (var i = 0, len = descendants.length; i < len; i++) {
+ arr[i] = descendants[i];
+ }
+ descendants = arr;
+ }
+ var elements = descendants.filter(function(el) {
+ return reAllowedSVGTagNames.test(el.tagName) && !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/);
+ });
+ if (!elements || elements && !elements.length) return;
+ var viewBoxAttr = doc.getAttribute("viewBox"), widthAttr = doc.getAttribute("width"), heightAttr = doc.getAttribute("height"), width = null, height = null, minX, minY;
+ if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
+ minX = parseInt(viewBoxAttr[1], 10);
+ minY = parseInt(viewBoxAttr[2], 10);
+ width = parseInt(viewBoxAttr[3], 10);
+ height = parseInt(viewBoxAttr[4], 10);
+ }
+ width = widthAttr ? parseFloat(widthAttr) : width;
+ height = heightAttr ? parseFloat(heightAttr) : height;
+ var options = {
+ width: width,
+ height: height
+ };
+ fabric.gradientDefs = fabric.getGradientDefs(doc);
+ fabric.cssRules = getCSSRules(doc);
+ fabric.parseElements(elements, function(instances) {
+ fabric.documentParsingTime = new Date() - startTime;
+ if (callback) {
+ callback(instances, options);
+ }
+ }, clone(options), reviver);
+ };
+ }();
+ var svgCache = {
+ has: function(name, callback) {
+ callback(false);
+ },
+ get: function() {},
+ set: function() {}
+ };
+ function loadSVGFromURL(url, callback, reviver) {
+ url = url.replace(/^\n\s*/, "").trim();
+ svgCache.has(url, function(hasUrl) {
+ if (hasUrl) {
+ svgCache.get(url, function(value) {
+ var enlivedRecord = _enlivenCachedObject(value);
+ callback(enlivedRecord.objects, enlivedRecord.options);
+ });
+ } else {
+ new fabric.util.request(url, {
+ method: "get",
+ onComplete: onComplete
+ });
+ }
+ });
+ function onComplete(r) {
+ var xml = r.responseXML;
+ if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
+ xml = new ActiveXObject("Microsoft.XMLDOM");
+ xml.async = "false";
+ xml.loadXML(r.responseText.replace(//i, ""));
+ }
+ if (!xml.documentElement) return;
+ fabric.parseSVGDocument(xml.documentElement, function(results, options) {
+ svgCache.set(url, {
+ objects: fabric.util.array.invoke(results, "toObject"),
+ options: options
+ });
+ callback(results, options);
+ }, reviver);
+ }
+ }
+ 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
+ };
+ }
+ function loadSVGFromString(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";
+ doc.loadXML(string.replace(//i, ""));
+ }
+ fabric.parseSVGDocument(doc.documentElement, function(results, options) {
+ callback(results, options);
+ }, reviver);
+ }
+ function createSVGFontFacesMarkup(objects) {
+ var markup = "";
+ for (var i = 0, len = objects.length; i < len; i++) {
+ if (objects[i].type !== "text" || !objects[i].path) continue;
+ markup += [ "@font-face {", "font-family: ", objects[i].fontFamily, "; ", "src: url('", objects[i].path, "')", "}" ].join("");
+ }
+ if (markup) {
+ markup = [ '" ].join("");
+ }
+ return markup;
+ }
+ function createSVGRefElementsMarkup(canvas) {
+ var markup = "";
+ if (canvas.backgroundColor && canvas.backgroundColor.source) {
+ markup = [ '', '' ].join("");
+ }
+ return markup;
+ }
+ function getGradientDefs(doc) {
+ var linearGradientEls = doc.getElementsByTagName("linearGradient"), radialGradientEls = doc.getElementsByTagName("radialGradient"), el, i, gradientDefs = {};
+ i = linearGradientEls.length;
+ for (;i--; ) {
+ el = linearGradientEls[i];
+ gradientDefs[el.getAttribute("id")] = el;
+ }
+ i = radialGradientEls.length;
+ for (;i--; ) {
+ el = radialGradientEls[i];
+ gradientDefs[el.getAttribute("id")] = el;
+ }
+ return gradientDefs;
+ }
+ extend(fabric, {
+ parseAttributes: parseAttributes,
+ parseElements: parseElements,
+ parseStyleAttribute: parseStyleAttribute,
+ parsePointsAttribute: parsePointsAttribute,
+ getCSSRules: getCSSRules,
+ loadSVGFromURL: loadSVGFromURL,
+ loadSVGFromString: loadSVGFromString,
+ createSVGFontFacesMarkup: createSVGFontFacesMarkup,
+ createSVGRefElementsMarkup: createSVGRefElementsMarkup,
+ getGradientDefs: getGradientDefs
+ });
+ })(typeof exports !== "undefined" ? exports : this);
+ (function(global) {
+ "use strict";
+ var fabric = global.fabric || (global.fabric = {});
+ if (fabric.Point) {
+ fabric.warn("fabric.Point is already defined");
+ return;
+ }
+ fabric.Point = Point;
+ function Point(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+ Point.prototype = {
+ constructor: Point,
+ add: function(that) {
+ return new Point(this.x + that.x, this.y + that.y);
+ },
+ addEquals: function(that) {
+ this.x += that.x;
+ this.y += that.y;
+ return this;
+ },
+ scalarAdd: function(scalar) {
+ return new Point(this.x + scalar, this.y + scalar);
+ },
+ scalarAddEquals: function(scalar) {
+ this.x += scalar;
+ this.y += scalar;
+ return this;
+ },
+ subtract: function(that) {
+ return new Point(this.x - that.x, this.y - that.y);
+ },
+ subtractEquals: function(that) {
+ this.x -= that.x;
+ this.y -= that.y;
+ return this;
+ },
+ scalarSubtract: function(scalar) {
+ return new Point(this.x - scalar, this.y - scalar);
+ },
+ scalarSubtractEquals: function(scalar) {
+ this.x -= scalar;
+ this.y -= scalar;
+ return this;
+ },
+ multiply: function(scalar) {
+ return new Point(this.x * scalar, this.y * scalar);
+ },
+ multiplyEquals: function(scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ return this;
+ },
+ divide: function(scalar) {
+ return new Point(this.x / scalar, this.y / scalar);
+ },
+ divideEquals: function(scalar) {
+ this.x /= scalar;
+ this.y /= scalar;
+ return this;
+ },
+ eq: function(that) {
+ return this.x === that.x && this.y === that.y;
+ },
+ lt: function(that) {
+ return this.x < that.x && this.y < that.y;
+ },
+ lte: function(that) {
+ return this.x <= that.x && this.y <= that.y;
+ },
+ gt: function(that) {
+ return this.x > that.x && this.y > that.y;
+ },
+ gte: function(that) {
+ return this.x >= that.x && this.y >= that.y;
+ },
+ lerp: function(that, t) {
+ return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
+ },
+ distanceFrom: function(that) {
+ var dx = this.x - that.x, dy = this.y - that.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+ midPointFrom: function(that) {
+ return new Point(this.x + (that.x - this.x) / 2, this.y + (that.y - this.y) / 2);
+ },
+ min: function(that) {
+ return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
+ },
+ max: function(that) {
+ return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
+ },
+ toString: function() {
+ return this.x + "," + this.y;
+ },
+ setXY: function(x, y) {
+ this.x = x;
+ this.y = y;
+ },
+ setFromPoint: function(that) {
+ this.x = that.x;
+ this.y = that.y;
+ },
+ 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";
+ var fabric = global.fabric || (global.fabric = {});
+ if (fabric.Intersection) {
+ fabric.warn("fabric.Intersection is already defined");
+ return;
+ }
+ function Intersection(status) {
+ this.status = status;
+ this.points = [];
+ }
+ fabric.Intersection = Intersection;
+ fabric.Intersection.prototype = {
+ appendPoint: function(point) {
+ this.points.push(point);
+ },
+ appendPoints: function(points) {
+ this.points = this.points.concat(points);
+ }
+ };
+ fabric.Intersection.intersectLineLine = function(a1, a2, b1, b2) {
+ var result, ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
+ if (u_b !== 0) {
+ var ua = ua_t / u_b, ub = ub_t / u_b;
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
+ result = new Intersection("Intersection");
+ result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
+ } else {
+ result = new Intersection();
+ }
+ } else {
+ if (ua_t === 0 || ub_t === 0) {
+ result = new Intersection("Coincident");
+ } else {
+ result = new Intersection("Parallel");
+ }
+ }
+ return result;
+ };
+ 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;
+ };
+ 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;
+ };
+ fabric.Intersection.intersectPolygonRectangle = function(points, r1, r2) {
+ var min = r1.min(r2), max = r1.max(r2), topRight = new fabric.Point(max.x, min.y), bottomLeft = new fabric.Point(min.x, max.y), inter1 = Intersection.intersectLinePolygon(min, topRight, points), inter2 = Intersection.intersectLinePolygon(topRight, max, points), inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), result = new Intersection();
+ result.appendPoints(inter1.points);
+ result.appendPoints(inter2.points);
+ result.appendPoints(inter3.points);
+ result.appendPoints(inter4.points);
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+ })(typeof exports !== "undefined" ? exports : this);
+ (function(global) {
+ "use strict";
+ var fabric = global.fabric || (global.fabric = {});
+ if (fabric.Color) {
+ fabric.warn("fabric.Color is already defined.");
+ return;
+ }
+ function Color(color) {
+ if (!color) {
+ this.setSource([ 0, 0, 0, 1 ]);
+ } else {
+ this._tryParsingColor(color);
+ }
+ }
+ fabric.Color = Color;
+ fabric.Color.prototype = {
+ _tryParsingColor: function(color) {
+ var source;
+ if (color in Color.colorNameMap) {
+ color = Color.colorNameMap[color];
+ }
+ source = Color.sourceFromHex(color);
+ if (!source) {
+ source = Color.sourceFromRgb(color);
+ }
+ if (!source) {
+ source = Color.sourceFromHsl(color);
+ }
+ if (source) {
+ this.setSource(source);
+ }
+ },
+ _rgbToHsl: function(r, g, b) {
+ r /= 255, g /= 255, b /= 255;
+ var h, s, l, max = fabric.util.array.max([ r, g, b ]), min = fabric.util.array.min([ r, g, b ]);
+ l = (max + min) / 2;
+ if (max === min) {
+ h = s = 0;
+ } else {
+ var d = max - min;
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+
+ case g:
+ h = (b - r) / d + 2;
+ break;
+
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+ return [ Math.round(h * 360), Math.round(s * 100), Math.round(l * 100) ];
+ },
+ getSource: function() {
+ return this._source;
+ },
+ setSource: function(source) {
+ this._source = source;
+ },
+ toRgb: function() {
+ var source = this.getSource();
+ return "rgb(" + source[0] + "," + source[1] + "," + source[2] + ")";
+ },
+ toRgba: function() {
+ var source = this.getSource();
+ return "rgba(" + source[0] + "," + source[1] + "," + source[2] + "," + source[3] + ")";
+ },
+ toHsl: function() {
+ var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]);
+ return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)";
+ },
+ toHsla: function() {
+ var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]);
+ return "hsla(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%," + source[3] + ")";
+ },
+ toHex: function() {
+ var source = this.getSource();
+ var r = source[0].toString(16);
+ r = r.length === 1 ? "0" + r : r;
+ var g = source[1].toString(16);
+ g = g.length === 1 ? "0" + g : g;
+ var b = source[2].toString(16);
+ b = b.length === 1 ? "0" + b : b;
+ return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
+ },
+ getAlpha: function() {
+ return this.getSource()[3];
+ },
+ setAlpha: function(alpha) {
+ var source = this.getSource();
+ source[3] = alpha;
+ this.setSource(source);
+ return this;
+ },
+ toGrayscale: function() {
+ var source = this.getSource(), average = parseInt((source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), 10), currentAlpha = source[3];
+ this.setSource([ average, average, average, currentAlpha ]);
+ return this;
+ },
+ toBlackWhite: function(threshold) {
+ var source = this.getSource(), average = (source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), currentAlpha = source[3];
+ threshold = threshold || 127;
+ average = Number(average) < Number(threshold) ? 0 : 255;
+ this.setSource([ average, average, average, currentAlpha ]);
+ return this;
+ },
+ overlayWith: function(otherColor) {
+ if (!(otherColor instanceof Color)) {
+ otherColor = new Color(otherColor);
+ }
+ var result = [], alpha = this.getAlpha(), otherAlpha = .5, source = this.getSource(), otherSource = otherColor.getSource();
+ 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;
+ }
+ };
+ fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
+ fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
+ fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
+ 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"
+ };
+ 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;
+ }
+ fabric.Color.fromRgb = function(color) {
+ return Color.fromSource(Color.sourceFromRgb(color));
+ };
+ fabric.Color.sourceFromRgb = function(color) {
+ var match = color.match(Color.reRGBa);
+ if (match) {
+ var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1);
+ return [ parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), match[4] ? parseFloat(match[4]) : 1 ];
+ }
+ };
+ fabric.Color.fromRgba = Color.fromRgb;
+ fabric.Color.fromHsl = function(color) {
+ return Color.fromSource(Color.sourceFromHsl(color));
+ };
+ fabric.Color.sourceFromHsl = function(color) {
+ var match = color.match(Color.reHSLa);
+ if (!match) return;
+ var h = (parseFloat(match[1]) % 360 + 360) % 360 / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), r, g, b;
+ if (s === 0) {
+ r = g = b = l;
+ } else {
+ var q = l <= .5 ? l * (s + 1) : l + s - l * s;
+ var p = l * 2 - q;
+ r = hue2rgb(p, q, h + 1 / 3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1 / 3);
+ }
+ return [ Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), match[4] ? parseFloat(match[4]) : 1 ];
+ };
+ fabric.Color.fromHsla = Color.fromHsl;
+ fabric.Color.fromHex = function(color) {
+ return Color.fromSource(Color.sourceFromHex(color));
+ };
+ fabric.Color.sourceFromHex = function(color) {
+ if (color.match(Color.reHex)) {
+ var value = color.slice(color.indexOf("#") + 1), isShortNotation = value.length === 3, 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 ];
+ }
+ };
+ fabric.Color.fromSource = function(source) {
+ var oColor = new Color();
+ oColor.setSource(source);
+ return oColor;
+ };
+ })(typeof exports !== "undefined" ? exports : this);
+ (function() {
+ function getColorStop(el) {
+ var style = el.getAttribute("style"), offset = el.getAttribute("offset"), color, opacity;
+ offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
+ if (style) {
+ var keyValuePairs = style.split(/\s*;\s*/);
+ if (keyValuePairs[keyValuePairs.length - 1] === "") {
+ keyValuePairs.pop();
+ }
+ for (var i = keyValuePairs.length; i--; ) {
+ var split = keyValuePairs[i].split(/\s*:\s*/), key = split[0].trim(), value = split[1].trim();
+ if (key === "stop-color") {
+ color = value;
+ } else if (key === "stop-opacity") {
+ opacity = value;
+ }
+ }
+ }
+ if (!color) {
+ color = el.getAttribute("stop-color") || "rgb(0,0,0)";
+ }
+ if (!opacity) {
+ opacity = el.getAttribute("stop-opacity");
+ }
+ color = new fabric.Color(color).toRgb();
+ return {
+ offset: offset,
+ color: color,
+ opacity: isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity)
+ };
+ }
+ fabric.Gradient = fabric.util.createClass({
+ initialize: function(options) {
+ options || (options = {});
+ var coords = {};
+ this.id = fabric.Object.__uid++;
+ this.type = options.type || "linear";
+ coords = {
+ x1: options.coords.x1 || 0,
+ y1: options.coords.y1 || 0,
+ x2: options.coords.x2 || 0,
+ y2: options.coords.y2 || 0
+ };
+ if (this.type === "radial") {
+ coords.r1 = options.coords.r1 || 0;
+ coords.r2 = options.coords.r2 || 0;
+ }
+ this.coords = coords;
+ this.gradientUnits = options.gradientUnits || "objectBoundingBox";
+ this.colorStops = options.colorStops.slice();
+ },
+ 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;
+ },
+ toObject: function() {
+ return {
+ type: this.type,
+ coords: this.coords,
+ gradientUnits: this.gradientUnits,
+ colorStops: this.colorStops
+ };
+ },
+ toSVG: function(object, normalize) {
+ var coords = fabric.util.object.clone(this.coords), markup;
+ this.colorStops.sort(function(a, b) {
+ return a.offset - b.offset;
+ });
+ if (normalize && this.gradientUnits === "userSpaceOnUse") {
+ coords.x1 += object.width / 2;
+ coords.y1 += object.height / 2;
+ coords.x2 += object.width / 2;
+ coords.y2 += object.height / 2;
+ } else if (this.gradientUnits === "objectBoundingBox") {
+ _convertValuesToPercentUnits(object, coords);
+ }
+ if (this.type === "linear") {
+ markup = [ "' ];
+ } else if (this.type === "radial") {
+ markup = [ "' ];
+ }
+ for (var i = 0; i < this.colorStops.length; i++) {
+ markup.push("');
+ }
+ markup.push(this.type === "linear" ? "" : "");
+ return markup.join("");
+ },
+ toLive: function(ctx) {
+ var gradient;
+ if (!this.type) return;
+ if (this.type === "linear") {
+ gradient = ctx.createLinearGradient(this.coords.x1, this.coords.y1, this.coords.x2, this.coords.y2);
+ } else if (this.type === "radial") {
+ gradient = ctx.createRadialGradient(this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2);
+ }
+ for (var i = 0, len = this.colorStops.length; i < len; i++) {
+ var color = this.colorStops[i].color, opacity = this.colorStops[i].opacity, offset = this.colorStops[i].offset;
+ if (typeof opacity !== "undefined") {
+ color = new fabric.Color(color).setAlpha(opacity).toRgba();
+ }
+ gradient.addColorStop(parseFloat(offset), color);
+ }
+ return gradient;
+ }
+ });
+ fabric.util.object.extend(fabric.Gradient, {
+ fromElement: function(el, instance) {
+ var colorStopEls = el.getElementsByTagName("stop"), type = el.nodeName === "linearGradient" ? "linear" : "radial", gradientUnits = el.getAttribute("gradientUnits") || "objectBoundingBox", colorStops = [], coords = {};
+ if (type === "linear") {
+ coords = {
+ x1: el.getAttribute("x1") || 0,
+ y1: el.getAttribute("y1") || 0,
+ x2: el.getAttribute("x2") || "100%",
+ y2: el.getAttribute("y2") || 0
+ };
+ } else if (type === "radial") {
+ coords = {
+ 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%"
+ };
+ }
+ for (var i = colorStopEls.length; i--; ) {
+ colorStops.push(getColorStop(colorStopEls[i]));
+ }
+ _convertPercentUnitsToValues(instance, coords);
+ return new fabric.Gradient({
+ type: type,
+ coords: coords,
+ gradientUnits: gradientUnits,
+ colorStops: colorStops
+ });
+ },
+ forObject: function(obj, options) {
+ options || (options = {});
+ _convertPercentUnitsToValues(obj, options);
+ return new fabric.Gradient(options);
+ }
+ });
+ function _convertPercentUnitsToValues(object, options) {
+ for (var prop in options) {
+ if (typeof options[prop] === "string" && /^\d+%$/.test(options[prop])) {
+ var percents = parseFloat(options[prop], 10);
+ if (prop === "x1" || prop === "x2" || prop === "r2") {
+ options[prop] = fabric.util.toFixed(object.width * percents / 100, 2);
+ } else if (prop === "y1" || prop === "y2") {
+ options[prop] = fabric.util.toFixed(object.height * percents / 100, 2);
+ }
+ }
+ if (prop === "x1" || prop === "x2") {
+ options[prop] -= fabric.util.toFixed(object.width / 2, 2);
+ } else if (prop === "y1" || prop === "y2") {
+ options[prop] -= fabric.util.toFixed(object.height / 2, 2);
+ }
+ }
+ }
+ function _convertValuesToPercentUnits(object, options) {
+ for (var prop in options) {
+ if (prop === "x1" || prop === "x2") {
+ options[prop] += fabric.util.toFixed(object.width / 2, 2);
+ } else if (prop === "y1" || prop === "y2") {
+ options[prop] += fabric.util.toFixed(object.height / 2, 2);
+ }
+ if (prop === "x1" || prop === "x2" || prop === "r2") {
+ options[prop] = fabric.util.toFixed(options[prop] / object.width * 100, 2) + "%";
+ } else if (prop === "y1" || prop === "y2") {
+ options[prop] = fabric.util.toFixed(options[prop] / object.height * 100, 2) + "%";
+ }
+ }
+ }
+ })();
+ fabric.Pattern = fabric.util.createClass({
+ repeat: "repeat",
+ offsetX: 0,
+ offsetY: 0,
+ initialize: function(options) {
+ options || (options = {});
+ this.id = fabric.Object.__uid++;
+ if (options.source) {
+ if (typeof options.source === "string") {
+ if (typeof fabric.util.getFunctionBody(options.source) !== "undefined") {
+ this.source = new Function(fabric.util.getFunctionBody(options.source));
+ } else {
+ var _this = this;
+ this.source = fabric.util.createImage();
+ fabric.util.loadImage(options.source, function(img) {
+ _this.source = img;
+ });
+ }
+ } else {
+ this.source = options.source;
+ }
+ }
+ if (options.repeat) {
+ this.repeat = options.repeat;
+ }
+ if (options.offsetX) {
+ this.offsetX = options.offsetX;
+ }
+ if (options.offsetY) {
+ this.offsetY = options.offsetY;
+ }
+ },
+ toObject: function() {
+ var source;
+ if (typeof this.source === "function") {
+ source = String(this.source);
+ } else if (typeof this.source.src === "string") {
+ source = this.source.src;
+ }
+ return {
+ source: source,
+ repeat: this.repeat,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ },
+ toSVG: function(object) {
+ var patternSource = typeof this.source === "function" ? this.source() : this.source;
+ var patternWidth = patternSource.width / object.getWidth();
+ var patternHeight = patternSource.height / object.getHeight();
+ var patternImgSrc = "";
+ if (patternSource.src) {
+ patternImgSrc = patternSource.src;
+ } else if (patternSource.toDataURL) {
+ patternImgSrc = patternSource.toDataURL();
+ }
+ return '' + '' + "";
+ },
+ toLive: function(ctx) {
+ var source = typeof this.source === "function" ? this.source() : this.source;
+ return ctx.createPattern(source, this.repeat);
+ }
+ });
+ fabric.Shadow = fabric.util.createClass({
+ color: "rgb(0,0,0)",
+ blur: 0,
+ offsetX: 0,
+ offsetY: 0,
+ affectStroke: false,
+ initialize: function(options) {
+ for (var prop in options) {
+ this[prop] = options[prop];
+ }
+ this.id = fabric.Object.__uid++;
+ },
+ toSVG: function(object) {
+ var mode = "SourceAlpha";
+ if (object.fill === this.color || object.stroke === this.color) {
+ mode = "SourceGraphic";
+ }
+ return '' + '' + '' + "" + "" + '' + "" + "";
+ },
+ toObject: function() {
+ return {
+ color: this.color,
+ blur: this.blur,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ }
+ });
+ (function() {
+ "use strict";
+ if (fabric.StaticCanvas) {
+ fabric.warn("fabric.StaticCanvas is already defined.");
+ return;
+ }
+ var extend = fabric.util.object.extend, getElementOffset = fabric.util.getElementOffset, removeFromArray = fabric.util.removeFromArray, removeListener = fabric.util.removeListener, CANVAS_INIT_ERROR = new Error("Could not initialize `canvas` element");
+ fabric.StaticCanvas = function(el, options) {
+ options || (options = {});
+ this._initStatic(el, options);
+ fabric.StaticCanvas.activeInstance = this;
+ };
+ extend(fabric.StaticCanvas.prototype, fabric.Observable);
+ extend(fabric.StaticCanvas.prototype, fabric.Collection);
+ extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter);
+ extend(fabric.StaticCanvas.prototype, {
+ backgroundColor: "",
+ backgroundImage: "",
+ backgroundImageOpacity: 1,
+ backgroundImageStretch: true,
+ overlayImage: "",
+ overlayImageLeft: 0,
+ overlayImageTop: 0,
+ includeDefaultValues: true,
+ stateful: true,
+ renderOnAddRemove: true,
+ clipTo: null,
+ controlsAboveOverlay: false,
+ allowTouchScrolling: false,
+ onBeforeScaleRotate: function() {},
+ _initStatic: function(el, options) {
+ this._objects = [];
+ this._createLowerCanvas(el);
+ this._initOptions(options);
+ if (options.overlayImage) {
+ this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
+ }
+ if (options.backgroundImage) {
+ this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
+ }
+ if (options.backgroundColor) {
+ this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
+ }
+ this.calcOffset();
+ },
+ calcOffset: function() {
+ this._offset = getElementOffset(this.lowerCanvasEl);
+ return this;
+ },
+ setOverlayImage: function(url, callback, options) {
+ fabric.util.loadImage(url, function(img) {
+ this.overlayImage = img;
+ if (options && "overlayImageLeft" in options) {
+ this.overlayImageLeft = options.overlayImageLeft;
+ }
+ if (options && "overlayImageTop" in options) {
+ this.overlayImageTop = options.overlayImageTop;
+ }
+ callback && callback();
+ }, this);
+ return this;
+ },
+ setBackgroundImage: function(url, callback, options) {
+ fabric.util.loadImage(url, function(img) {
+ this.backgroundImage = img;
+ if (options && "backgroundImageOpacity" in options) {
+ this.backgroundImageOpacity = options.backgroundImageOpacity;
+ }
+ if (options && "backgroundImageStretch" in options) {
+ this.backgroundImageStretch = options.backgroundImageStretch;
+ }
+ callback && callback();
+ }, this);
+ return this;
+ },
+ setBackgroundColor: function(backgroundColor, callback) {
+ if (backgroundColor.source) {
+ var _this = this;
+ fabric.util.loadImage(backgroundColor.source, function(img) {
+ _this.backgroundColor = new fabric.Pattern({
+ source: img,
+ repeat: backgroundColor.repeat
+ });
+ callback && callback();
+ });
+ } else {
+ this.backgroundColor = backgroundColor;
+ callback && callback();
+ }
+ return this;
+ },
+ _createCanvasElement: function() {
+ var element = fabric.document.createElement("canvas");
+ if (!element.style) {
+ element.style = {};
+ }
+ if (!element) {
+ throw CANVAS_INIT_ERROR;
+ }
+ this._initCanvasElement(element);
+ return element;
+ },
+ _initCanvasElement: function(element) {
+ fabric.util.createCanvasElement(element);
+ if (typeof element.getContext === "undefined") {
+ throw CANVAS_INIT_ERROR;
+ }
+ },
+ _initOptions: function(options) {
+ for (var prop in options) {
+ this[prop] = options[prop];
+ }
+ this.width = parseInt(this.lowerCanvasEl.width, 10) || 0;
+ this.height = parseInt(this.lowerCanvasEl.height, 10) || 0;
+ if (!this.lowerCanvasEl.style) return;
+ this.lowerCanvasEl.style.width = this.width + "px";
+ this.lowerCanvasEl.style.height = this.height + "px";
+ },
+ _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");
+ },
+ getWidth: function() {
+ return this.width;
+ },
+ getHeight: function() {
+ return this.height;
+ },
+ setWidth: function(value) {
+ return this._setDimension("width", value);
+ },
+ setHeight: function(value) {
+ return this._setDimension("height", value);
+ },
+ setDimensions: function(dimensions) {
+ for (var prop in dimensions) {
+ this._setDimension(prop, dimensions[prop]);
+ }
+ return this;
+ },
+ _setDimension: function(prop, value) {
+ this.lowerCanvasEl[prop] = value;
+ this.lowerCanvasEl.style[prop] = value + "px";
+ if (this.upperCanvasEl) {
+ this.upperCanvasEl[prop] = value;
+ this.upperCanvasEl.style[prop] = value + "px";
+ }
+ if (this.cacheCanvasEl) {
+ this.cacheCanvasEl[prop] = value;
+ }
+ if (this.wrapperEl) {
+ this.wrapperEl.style[prop] = value + "px";
+ }
+ this[prop] = value;
+ this.calcOffset();
+ this.renderAll();
+ return this;
+ },
+ getElement: function() {
+ return this.lowerCanvasEl;
+ },
+ getActiveObject: function() {
+ return null;
+ },
+ getActiveGroup: function() {
+ return null;
+ },
+ _draw: function(ctx, object) {
+ if (!object) return;
+ if (this.controlsAboveOverlay) {
+ var hasBorders = object.hasBorders, hasControls = object.hasControls;
+ object.hasBorders = object.hasControls = false;
+ object.render(ctx);
+ object.hasBorders = hasBorders;
+ object.hasControls = hasControls;
+ } else {
+ object.render(ctx);
+ }
+ },
+ _onObjectAdded: function(obj) {
+ this.stateful && obj.setupState();
+ obj.setCoords();
+ obj.canvas = this;
+ this.fire("object:added", {
+ target: obj
+ });
+ obj.fire("added");
+ },
+ _onObjectRemoved: function(obj) {
+ this.fire("object:removed", {
+ target: obj
+ });
+ obj.fire("removed");
+ },
+ getObjects: function() {
+ return this._objects;
+ },
+ clearContext: function(ctx) {
+ ctx.clearRect(0, 0, this.width, this.height);
+ return this;
+ },
+ getContext: function() {
+ return this.contextContainer;
+ },
+ 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;
+ },
+ renderAll: function(allOnTop) {
+ var canvasToDrawOn = this[allOnTop === true && this.interactive ? "contextTop" : "contextContainer"];
+ 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);
+ }
+ if (this.backgroundColor) {
+ canvasToDrawOn.fillStyle = this.backgroundColor.toLive ? this.backgroundColor.toLive(canvasToDrawOn) : this.backgroundColor;
+ canvasToDrawOn.fillRect(this.backgroundColor.offsetX || 0, this.backgroundColor.offsetY || 0, this.width, this.height);
+ }
+ if (typeof this.backgroundImage === "object") {
+ this._drawBackroundImage(canvasToDrawOn);
+ }
+ var activeGroup = this.getActiveGroup();
+ for (var i = 0, length = this._objects.length; i < length; ++i) {
+ if (!activeGroup || activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i])) {
+ this._draw(canvasToDrawOn, this._objects[i]);
+ }
+ }
+ if (activeGroup) {
+ var sortedObjects = [];
+ this.forEachObject(function(object) {
+ if (activeGroup.contains(object)) {
+ sortedObjects.push(object);
+ }
+ });
+ activeGroup._set("objects", sortedObjects);
+ this._draw(canvasToDrawOn, activeGroup);
+ }
+ if (this.clipTo) {
+ canvasToDrawOn.restore();
+ }
+ if (this.overlayImage) {
+ canvasToDrawOn.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
+ }
+ if (this.controlsAboveOverlay && this.interactive) {
+ this.drawControls(canvasToDrawOn);
+ }
+ this.fire("after:render");
+ return this;
+ },
+ _drawBackroundImage: function(canvasToDrawOn) {
+ canvasToDrawOn.save();
+ canvasToDrawOn.globalAlpha = this.backgroundImageOpacity;
+ if (this.backgroundImageStretch) {
+ canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height);
+ } else {
+ canvasToDrawOn.drawImage(this.backgroundImage, 0, 0);
+ }
+ canvasToDrawOn.restore();
+ },
+ renderTop: function() {
+ var ctx = this.contextTop || this.contextContainer;
+ this.clearContext(ctx);
+ if (this.selection && this._groupSelector) {
+ this._drawSelection();
+ }
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ activeGroup.render(ctx);
+ }
+ if (this.overlayImage) {
+ ctx.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
+ }
+ this.fire("after:render");
+ return this;
+ },
+ getCenter: function() {
+ return {
+ top: this.getHeight() / 2,
+ left: this.getWidth() / 2
+ };
+ },
+ centerObjectH: function(object) {
+ object.set("left", this.getCenter().left);
+ this.renderAll();
+ return this;
+ },
+ centerObjectV: function(object) {
+ object.set("top", this.getCenter().top);
+ this.renderAll();
+ return this;
+ },
+ centerObject: function(object) {
+ return this.centerObjectH(object).centerObjectV(object);
+ },
+ toDatalessJSON: function(propertiesToInclude) {
+ return this.toDatalessObject(propertiesToInclude);
+ },
+ toObject: function(propertiesToInclude) {
+ return this._toObjectMethod("toObject", propertiesToInclude);
+ },
+ toDatalessObject: function(propertiesToInclude) {
+ return this._toObjectMethod("toDatalessObject", propertiesToInclude);
+ },
+ _toObjectMethod: function(methodName, propertiesToInclude) {
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ this.discardActiveGroup();
+ }
+ var data = {
+ objects: this.getObjects().map(function(instance) {
+ var originalValue;
+ if (!this.includeDefaultValues) {
+ originalValue = instance.includeDefaultValues;
+ instance.includeDefaultValues = false;
+ }
+ var object = instance[methodName](propertiesToInclude);
+ if (!this.includeDefaultValues) {
+ instance.includeDefaultValues = originalValue;
+ }
+ return object;
+ }, this),
+ background: this.backgroundColor && this.backgroundColor.toObject ? this.backgroundColor.toObject() : this.backgroundColor
+ };
+ if (this.backgroundImage) {
+ data.backgroundImage = this.backgroundImage.src;
+ data.backgroundImageOpacity = this.backgroundImageOpacity;
+ data.backgroundImageStretch = this.backgroundImageStretch;
+ }
+ if (this.overlayImage) {
+ data.overlayImage = this.overlayImage.src;
+ data.overlayImageLeft = this.overlayImageLeft;
+ data.overlayImageTop = this.overlayImageTop;
+ }
+ fabric.util.populateWithProperties(this, data, propertiesToInclude);
+ if (activeGroup) {
+ this.setActiveGroup(new fabric.Group(activeGroup.getObjects()));
+ activeGroup.forEachObject(function(o) {
+ o.set("active", true);
+ });
+ }
+ return data;
+ },
+ toSVG: function(options) {
+ options || (options = {});
+ var markup = [];
+ if (!options.suppressPreamble) {
+ markup.push('', '\n');
+ }
+ markup.push("");
+ return markup.join("");
+ },
+ remove: function(object) {
+ if (this.getActiveObject() === object) {
+ this.fire("before:selection:cleared", {
+ target: object
+ });
+ this.discardActiveObject();
+ this.fire("selection:cleared");
+ }
+ return fabric.Collection.remove.call(this, object);
+ },
+ sendToBack: function(object) {
+ removeFromArray(this._objects, object);
+ this._objects.unshift(object);
+ return this.renderAll && this.renderAll();
+ },
+ bringToFront: function(object) {
+ removeFromArray(this._objects, object);
+ this._objects.push(object);
+ return this.renderAll && this.renderAll();
+ },
+ sendBackwards: function(object, intersecting) {
+ var idx = this._objects.indexOf(object);
+ if (idx !== 0) {
+ var newIdx;
+ if (intersecting) {
+ newIdx = idx;
+ for (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;
+ }
+ removeFromArray(this._objects, object);
+ this._objects.splice(newIdx, 0, object);
+ this.renderAll && this.renderAll();
+ }
+ return this;
+ },
+ bringForward: function(object, intersecting) {
+ var idx = this._objects.indexOf(object);
+ if (idx !== this._objects.length - 1) {
+ var newIdx;
+ if (intersecting) {
+ newIdx = idx;
+ for (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;
+ }
+ removeFromArray(this._objects, object);
+ this._objects.splice(newIdx, 0, object);
+ this.renderAll && this.renderAll();
+ }
+ return this;
+ },
+ moveTo: function(object, index) {
+ removeFromArray(this._objects, object);
+ this._objects.splice(index, 0, object);
+ return this.renderAll && this.renderAll();
+ },
+ dispose: function() {
+ this.clear();
+ if (!this.interactive) return this;
+ if (fabric.isTouchSupported) {
+ removeListener(this.upperCanvasEl, "touchstart", this._onMouseDown);
+ removeListener(this.upperCanvasEl, "touchmove", this._onMouseMove);
+ if (typeof Event !== "undefined" && "remove" in Event) {
+ Event.remove(this.upperCanvasEl, "gesture", this._onGesture);
+ }
+ } else {
+ removeListener(this.upperCanvasEl, "mousedown", this._onMouseDown);
+ removeListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
+ removeListener(fabric.window, "resize", this._onResize);
+ }
+ return this;
+ }
+ });
+ fabric.StaticCanvas.prototype.toString = function() {
+ return "#";
+ };
+ extend(fabric.StaticCanvas, {
+ EMPTY_JSON: '{"objects": [], "background": "white"}',
+ toGrayscale: function(canvasEl) {
+ var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = imageData.width, jLen = imageData.height, index, average, i, j;
+ for (i = 0; i < iLen; i++) {
+ for (j = 0; j < jLen; j++) {
+ index = i * 4 * jLen + j * 4;
+ average = (data[index] + data[index + 1] + data[index + 2]) / 3;
+ data[index] = average;
+ data[index + 1] = average;
+ data[index + 2] = average;
+ }
+ }
+ context.putImageData(imageData, 0, 0);
+ },
+ 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;
+ }
+ }
+ });
+ fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
+ })();
+ fabric.BaseBrush = fabric.util.createClass({
+ color: "rgb(0, 0, 0)",
+ width: 1,
+ shadowBlur: 0,
+ shadowColor: "",
+ shadowOffsetX: 0,
+ shadowOffsetY: 0,
+ strokeLineCap: "round",
+ strokeLineJoin: "round",
+ setBrushStyles: function() {
+ var ctx = this.canvas.contextTop;
+ ctx.strokeStyle = this.color;
+ ctx.lineWidth = this.width;
+ ctx.lineCap = this.strokeLineCap;
+ ctx.lineJoin = this.strokeLineJoin;
+ },
+ setShadowStyles: function() {
+ if (!this.shadowColor) return;
+ var ctx = this.canvas.contextTop;
+ ctx.shadowBlur = this.shadowBlur;
+ ctx.shadowColor = this.shadowColor;
+ ctx.shadowOffsetX = this.shadowOffsetX;
+ ctx.shadowOffsetY = this.shadowOffsetY;
+ },
+ removeShadowStyles: function() {
+ var ctx = this.canvas.contextTop;
+ ctx.shadowColor = "";
+ ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
+ }
+ });
+ (function() {
+ var utilMin = fabric.util.array.min, utilMax = fabric.util.array.max;
+ fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, {
+ initialize: function(canvas) {
+ this.canvas = canvas;
+ this._points = [];
+ },
+ onMouseDown: function(pointer) {
+ this._prepareForDrawing(pointer);
+ this._captureDrawingPath(pointer);
+ this._render();
+ },
+ onMouseMove: function(pointer) {
+ this._captureDrawingPath(pointer);
+ this.canvas.clearContext(this.canvas.contextTop);
+ this._render();
+ },
+ onMouseUp: function() {
+ this._finalizeAndAddPath();
+ },
+ _prepareForDrawing: function(pointer) {
+ var p = new fabric.Point(pointer.x, pointer.y);
+ this._reset();
+ this._addPoint(p);
+ this.canvas.contextTop.moveTo(p.x, p.y);
+ },
+ _addPoint: function(point) {
+ this._points.push(point);
+ },
+ _reset: function() {
+ this._points.length = 0;
+ this.setBrushStyles();
+ this.setShadowStyles();
+ },
+ _captureDrawingPath: function(pointer) {
+ var pointerPoint = new fabric.Point(pointer.x, pointer.y);
+ this._addPoint(pointerPoint);
+ },
+ _render: function() {
+ var ctx = this.canvas.contextTop;
+ ctx.beginPath();
+ var p1 = this._points[0];
+ var p2 = this._points[1];
+ if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {
+ p1.x -= .5;
+ p2.x += .5;
+ }
+ ctx.moveTo(p1.x, p1.y);
+ for (var i = 1, len = this._points.length; i < len; i++) {
+ var midPoint = p1.midPointFrom(p2);
+ ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
+ p1 = this._points[i];
+ p2 = this._points[i + 1];
+ }
+ ctx.lineTo(p1.x, p1.y);
+ ctx.stroke();
+ },
+ _getSVGPathData: function() {
+ this.box = this.getPathBoundingBox(this._points);
+ return this.convertPointsToSVGPath(this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy);
+ },
+ getPathBoundingBox: function(points) {
+ var xBounds = [], yBounds = [], p1 = points[0], p2 = points[1], startPoint = p1;
+ for (var i = 1, len = points.length; i < len; i++) {
+ var midPoint = p1.midPointFrom(p2);
+ xBounds.push(startPoint.x);
+ xBounds.push(midPoint.x);
+ yBounds.push(startPoint.y);
+ yBounds.push(midPoint.y);
+ p1 = points[i];
+ p2 = points[i + 1];
+ startPoint = midPoint;
+ }
+ xBounds.push(p1.x);
+ yBounds.push(p1.y);
+ return {
+ minx: utilMin(xBounds),
+ miny: utilMin(yBounds),
+ maxx: utilMax(xBounds),
+ maxy: utilMax(yBounds)
+ };
+ },
+ convertPointsToSVGPath: function(points, minX, maxX, minY) {
+ var path = [];
+ var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY);
+ var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY);
+ path.push("M ", points[0].x - minX, " ", points[0].y - minY, " ");
+ for (var i = 1, len = points.length; i < len; i++) {
+ var midPoint = p1.midPointFrom(p2);
+ path.push("Q ", p1.x, " ", p1.y, " ", midPoint.x, " ", midPoint.y, " ");
+ p1 = new fabric.Point(points[i].x - minX, points[i].y - minY);
+ if (i + 1 < points.length) {
+ p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY);
+ }
+ }
+ path.push("L ", p1.x, " ", p1.y, " ");
+ return path;
+ },
+ createPath: function(pathData) {
+ var path = new fabric.Path(pathData);
+ path.fill = null;
+ path.stroke = this.color;
+ path.strokeWidth = this.width;
+ path.strokeLineCap = this.strokeLineCap;
+ path.strokeLineJoin = this.strokeLineJoin;
+ if (this.shadowColor) {
+ path.setShadow({
+ color: this.shadowColor,
+ blur: this.shadowBlur,
+ offsetX: this.shadowOffsetX,
+ offsetY: this.shadowOffsetY,
+ affectStroke: true
+ });
+ }
+ return path;
+ },
+ _finalizeAndAddPath: function() {
+ var ctx = this.canvas.contextTop;
+ ctx.closePath();
+ var pathData = this._getSVGPathData().join("");
+ if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") {
+ this.canvas.renderAll();
+ return;
+ }
+ var originLeft = this.box.minx + (this.box.maxx - this.box.minx) / 2;
+ var originTop = this.box.miny + (this.box.maxy - this.box.miny) / 2;
+ this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false);
+ var path = this.createPath(pathData);
+ path.set({
+ left: originLeft,
+ top: originTop
+ });
+ this.canvas.add(path);
+ path.setCoords();
+ this.canvas.clearContext(this.canvas.contextTop);
+ this.removeShadowStyles();
+ this.canvas.renderAll();
+ this.canvas.fire("path:created", {
+ path: path
+ });
+ }
+ });
+ })();
+ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, {
+ width: 10,
+ initialize: function(canvas) {
+ this.canvas = canvas;
+ this.points = [];
+ },
+ drawDot: function(pointer) {
+ var point = this.addPoint(pointer);
+ var ctx = this.canvas.contextTop;
+ ctx.fillStyle = point.fill;
+ ctx.beginPath();
+ ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
+ ctx.closePath();
+ ctx.fill();
+ },
+ onMouseDown: function(pointer) {
+ this.points.length = 0;
+ this.canvas.clearContext(this.canvas.contextTop);
+ this.setShadowStyles();
+ this.drawDot(pointer);
+ },
+ onMouseMove: function(pointer) {
+ this.drawDot(pointer);
+ },
+ onMouseUp: function() {
+ var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
+ this.canvas.renderOnAddRemove = false;
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ var point = this.points[i];
+ var circle = new fabric.Circle({
+ radius: point.radius,
+ left: point.x,
+ top: point.y,
+ fill: point.fill
+ });
+ if (this.shadowColor) {
+ circle.setShadow({
+ color: this.shadowColor,
+ blur: this.shadowBlur,
+ offsetX: this.shadowOffsetX,
+ offsetY: this.shadowOffsetY
+ });
+ }
+ this.canvas.add(circle);
+ this.canvas.fire("path:created", {
+ path: circle
+ });
+ }
+ this.canvas.clearContext(this.canvas.contextTop);
+ this.removeShadowStyles();
+ this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
+ this.canvas.renderAll();
+ },
+ addPoint: function(pointer) {
+ var pointerPoint = new fabric.Point(pointer.x, pointer.y);
+ var circleRadius = fabric.util.getRandomInt(Math.max(0, this.width - 20), this.width + 20) / 2;
+ var circleColor = new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0, 100) / 100).toRgba();
+ pointerPoint.radius = circleRadius;
+ pointerPoint.fill = circleColor;
+ this.points.push(pointerPoint);
+ return pointerPoint;
+ }
+ });
+ fabric.SprayBrush = fabric.util.createClass(fabric.BaseBrush, {
+ width: 10,
+ density: 20,
+ dotWidth: 1,
+ dotWidthVariance: 1,
+ randomOpacity: false,
+ initialize: function(canvas) {
+ this.canvas = canvas;
+ this.sprayChunks = [];
+ },
+ onMouseDown: function(pointer) {
+ this.sprayChunks.length = 0;
+ this.canvas.clearContext(this.canvas.contextTop);
+ this.setShadowStyles();
+ this.addSprayChunk(pointer);
+ this.render();
+ },
+ onMouseMove: function(pointer) {
+ this.addSprayChunk(pointer);
+ this.render();
+ },
+ onMouseUp: function() {
+ var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
+ this.canvas.renderOnAddRemove = false;
+ 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,
+ fill: this.color
+ });
+ if (this.shadowColor) {
+ rect.setShadow({
+ color: this.shadowColor,
+ blur: this.shadowBlur,
+ offsetX: this.shadowOffsetX,
+ offsetY: this.shadowOffsetY
+ });
+ }
+ this.canvas.add(rect);
+ this.canvas.fire("path:created", {
+ path: rect
+ });
+ }
+ }
+ this.canvas.clearContext(this.canvas.contextTop);
+ this.removeShadowStyles();
+ this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
+ this.canvas.renderAll();
+ },
+ render: function() {
+ var ctx = this.canvas.contextTop;
+ ctx.fillStyle = this.color;
+ ctx.save();
+ 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();
+ },
+ 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(Math.max(1, this.dotWidth - this.dotWidthVariance), this.dotWidth + this.dotWidthVariance);
+ } else {
+ width = this.dotWidth;
+ }
+ var point = {
+ x: x,
+ y: y,
+ width: width
+ };
+ if (this.randomOpacity) {
+ point.opacity = fabric.util.getRandomInt(0, 100) / 100;
+ }
+ this.sprayChunkPoints.push(point);
+ }
+ this.sprayChunks.push(this.sprayChunkPoints);
+ }
+ });
+ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, {
+ getPatternSrc: function() {
+ var dotWidth = 20, dotDistance = 5, patternCanvas = fabric.document.createElement("canvas"), patternCtx = patternCanvas.getContext("2d");
+ patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;
+ patternCtx.fillStyle = this.color;
+ patternCtx.beginPath();
+ patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false);
+ patternCtx.closePath();
+ patternCtx.fill();
+ return patternCanvas;
+ },
+ getPatternSrcFunction: function() {
+ return String(this.getPatternSrc).replace("this.color", '"' + this.color + '"');
+ },
+ getPattern: function() {
+ return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), "repeat");
+ },
+ setBrushStyles: function() {
+ this.callSuper("setBrushStyles");
+ this.canvas.contextTop.strokeStyle = this.getPattern();
+ },
+ createPath: function(pathData) {
+ var path = this.callSuper("createPath", pathData);
+ path.stroke = new fabric.Pattern({
+ source: this.source || this.getPatternSrcFunction()
+ });
+ return path;
+ }
+ });
+ (function() {
+ var extend = fabric.util.object.extend, getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, atan2 = Math.atan2, abs = Math.abs, min = Math.min, max = Math.max, STROKE_OFFSET = .5;
+ fabric.Canvas = function(el, options) {
+ options || (options = {});
+ this._initStatic(el, options);
+ this._initInteractive();
+ this._createCacheCanvas();
+ fabric.Canvas.activeInstance = this;
+ };
+ function ProtoProxy() {}
+ ProtoProxy.prototype = fabric.StaticCanvas.prototype;
+ fabric.Canvas.prototype = new ProtoProxy();
+ var InteractiveMethods = {
+ uniScaleTransform: false,
+ centerTransform: false,
+ interactive: true,
+ selection: true,
+ selectionColor: "rgba(100, 100, 255, 0.3)",
+ selectionDashArray: [],
+ selectionBorderColor: "rgba(255, 255, 255, 0.3)",
+ selectionLineWidth: 1,
+ hoverCursor: "move",
+ moveCursor: "move",
+ defaultCursor: "default",
+ freeDrawingCursor: "crosshair",
+ rotationCursor: "crosshair",
+ containerClass: "canvas-container",
+ perPixelTargetFind: false,
+ targetFindTolerance: 0,
+ skipTargetFind: false,
+ _initInteractive: function() {
+ this._currentTransform = null;
+ this._groupSelector = null;
+ this._initWrapperElement();
+ this._createUpperCanvas();
+ this._initEvents();
+ this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);
+ this.calcOffset();
+ },
+ _resetCurrentTransform: function(e) {
+ var t = this._currentTransform;
+ t.target.set("scaleX", t.original.scaleX);
+ t.target.set("scaleY", t.original.scaleY);
+ t.target.set("left", t.original.left);
+ t.target.set("top", t.original.top);
+ if (e.altKey || this.centerTransform || t.target.centerTransform) {
+ if (t.originX !== "center") {
+ if (t.originX === "right") {
+ t.mouseXSign = -1;
+ } else {
+ t.mouseXSign = 1;
+ }
+ }
+ if (t.originY !== "center") {
+ if (t.originY === "bottom") {
+ t.mouseYSign = -1;
+ } else {
+ t.mouseYSign = 1;
+ }
+ }
+ t.originX = "center";
+ t.originY = "center";
+ } else {
+ t.originX = t.original.originX;
+ t.originY = t.original.originY;
+ }
+ },
+ containsPoint: function(e, target) {
+ var pointer = this.getPointer(e), xy = this._normalizePointer(target, pointer);
+ return target.containsPoint(xy) || target._findTargetCorner(e, this._offset);
+ },
+ _normalizePointer: function(object, pointer) {
+ var activeGroup = this.getActiveGroup(), x = pointer.x, y = pointer.y;
+ var isObjectInGroup = activeGroup && object.type !== "group" && activeGroup.contains(object);
+ if (isObjectInGroup) {
+ x -= activeGroup.left;
+ y -= activeGroup.top;
+ }
+ return {
+ x: x,
+ y: y
+ };
+ },
+ isTargetTransparent: function(target, x, y) {
+ var cacheContext = this.contextCache;
+ var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners;
+ target.hasBorders = target.transparentCorners = false;
+ this._draw(cacheContext, target);
+ target.hasBorders = hasBorders;
+ target.transparentCorners = transparentCorners;
+ if (this.targetFindTolerance > 0) {
+ if (x > this.targetFindTolerance) {
+ x -= this.targetFindTolerance;
+ } else {
+ x = 0;
+ }
+ if (y > this.targetFindTolerance) {
+ y -= this.targetFindTolerance;
+ } else {
+ y = 0;
+ }
+ }
+ var isTransparent = true;
+ var imageData = cacheContext.getImageData(x, y, this.targetFindTolerance * 2 || 1, this.targetFindTolerance * 2 || 1);
+ for (var i = 3, l = imageData.data.length; i < l; i += 4) {
+ var temp = imageData.data[i];
+ isTransparent = temp <= 0;
+ if (isTransparent === false) break;
+ }
+ imageData = null;
+ this.clearContext(cacheContext);
+ return isTransparent;
+ },
+ _shouldClearSelection: function(e, target) {
+ var activeGroup = this.getActiveGroup();
+ return !target || target && activeGroup && !activeGroup.contains(target) && activeGroup !== target && !e.shiftKey || target && !target.selectable;
+ },
+ _setupCurrentTransform: function(e, target) {
+ if (!target) return;
+ var action = "drag", corner, pointer = getPointer(e, target.canvas.upperCanvasEl);
+ corner = target._findTargetCorner(e, this._offset);
+ if (corner) {
+ action = corner === "ml" || corner === "mr" ? "scaleX" : corner === "mt" || corner === "mb" ? "scaleY" : corner === "mtr" ? "rotate" : "scale";
+ }
+ var originX = "center", originY = "center";
+ if (corner === "ml" || corner === "tl" || corner === "bl") {
+ originX = "right";
+ } else if (corner === "mr" || corner === "tr" || corner === "br") {
+ originX = "left";
+ }
+ if (corner === "tl" || corner === "mt" || corner === "tr") {
+ originY = "bottom";
+ } else if (corner === "bl" || corner === "mb" || corner === "br") {
+ originY = "top";
+ }
+ if (corner === "mtr") {
+ originX = "center";
+ originY = "center";
+ }
+ this._currentTransform = {
+ target: target,
+ action: action,
+ scaleX: target.scaleX,
+ scaleY: target.scaleY,
+ offsetX: pointer.x - target.left,
+ offsetY: pointer.y - target.top,
+ originX: originX,
+ originY: originY,
+ 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: originX,
+ originY: originY
+ };
+ this._resetCurrentTransform(e);
+ },
+ _shouldHandleGroupLogic: function(e, target) {
+ var activeObject = this.getActiveObject();
+ return e.shiftKey && (this.getActiveGroup() || activeObject && activeObject !== target) && this.selection;
+ },
+ _handleGroupLogic: function(e, target) {
+ if (target === this.getActiveGroup()) {
+ target = this.findTarget(e, true);
+ if (!target || target.isType("group")) {
+ return;
+ }
+ }
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ if (activeGroup.contains(target)) {
+ activeGroup.removeWithUpdate(target);
+ this._resetObjectTransform(activeGroup);
+ target.set("active", false);
+ if (activeGroup.size() === 1) {
+ this.discardActiveGroup();
+ }
+ } else {
+ activeGroup.addWithUpdate(target);
+ this._resetObjectTransform(activeGroup);
+ }
+ this.fire("selection:created", {
+ target: activeGroup,
+ e: e
+ });
+ activeGroup.set("active", true);
+ } else {
+ if (this._activeObject) {
+ if (target !== this._activeObject) {
+ var objects = this.getObjects();
+ var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target);
+ var group = new fabric.Group(isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]);
+ this.setActiveGroup(group);
+ this._activeObject = null;
+ activeGroup = this.getActiveGroup();
+ this.fire("selection:created", {
+ target: activeGroup,
+ e: e
+ });
+ }
+ }
+ target.set("active", true);
+ }
+ if (activeGroup) {
+ activeGroup.saveCoords();
+ }
+ },
+ _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);
+ }
+ },
+ _scaleObject: function(x, y, by) {
+ var t = this._currentTransform, offset = this._offset, target = t.target;
+ var lockScalingX = target.get("lockScalingX"), lockScalingY = target.get("lockScalingY");
+ if (lockScalingX && lockScalingY) return;
+ var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY);
+ var localMouse = target.toLocalPoint(new fabric.Point(x - offset.left, y - offset.top), t.originX, t.originY);
+ if (t.originX === "right") {
+ localMouse.x *= -1;
+ } else if (t.originX === "center") {
+ localMouse.x *= t.mouseXSign * 2;
+ if (localMouse.x < 0) {
+ t.mouseXSign = -t.mouseXSign;
+ }
+ }
+ if (t.originY === "bottom") {
+ localMouse.y *= -1;
+ } else if (t.originY === "center") {
+ localMouse.y *= t.mouseYSign * 2;
+ if (localMouse.y < 0) {
+ t.mouseYSign = -t.mouseYSign;
+ }
+ }
+ if (abs(localMouse.x) > target.padding) {
+ if (localMouse.x < 0) {
+ localMouse.x += target.padding;
+ } else {
+ localMouse.x -= target.padding;
+ }
+ } else {
+ localMouse.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;
+ }
+ var newScaleX = target.scaleX, newScaleY = target.scaleY;
+ if (by === "equally" && !lockScalingX && !lockScalingY) {
+ var dist = localMouse.y + localMouse.x;
+ var lastDist = (target.height + target.strokeWidth) * t.original.scaleY + (target.width + target.strokeWidth) * t.original.scaleX;
+ newScaleX = t.original.scaleX * dist / lastDist;
+ newScaleY = t.original.scaleY * dist / lastDist;
+ target.set("scaleX", newScaleX);
+ target.set("scaleY", newScaleY);
+ } else if (!by) {
+ newScaleX = localMouse.x / (target.width + target.strokeWidth);
+ newScaleY = localMouse.y / (target.height + target.strokeWidth);
+ lockScalingX || target.set("scaleX", newScaleX);
+ lockScalingY || target.set("scaleY", newScaleY);
+ } else if (by === "x" && !target.get("lockUniScaling")) {
+ newScaleX = localMouse.x / (target.width + target.strokeWidth);
+ lockScalingX || target.set("scaleX", newScaleX);
+ } else if (by === "y" && !target.get("lockUniScaling")) {
+ newScaleY = localMouse.y / (target.height + target.strokeWidth);
+ lockScalingY || target.set("scaleY", newScaleY);
+ }
+ if (newScaleX < 0) {
+ if (t.originX === "left") t.originX = "right"; else if (t.originX === "right") t.originX = "left";
+ }
+ if (newScaleY < 0) {
+ if (t.originY === "top") t.originY = "bottom"; else if (t.originY === "bottom") t.originY = "top";
+ }
+ target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
+ },
+ _rotateObject: function(x, y) {
+ var t = this._currentTransform, o = this._offset;
+ if (t.target.get("lockRotation")) return;
+ var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left), curAngle = atan2(y - t.top - o.top, x - t.left - o.left), angle = radiansToDegrees(curAngle - lastAngle + t.theta);
+ if (angle < 0) {
+ angle = 360 + angle;
+ }
+ t.target.angle = angle;
+ },
+ _setCursor: function(value) {
+ this.upperCanvasEl.style.cursor = value;
+ },
+ _resetObjectTransform: function(target) {
+ target.scaleX = 1;
+ target.scaleY = 1;
+ target.setAngle(0);
+ },
+ _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;
+ if (this.selectionDashArray.length > 1) {
+ var px = groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft);
+ var 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);
+ }
+ },
+ _findSelectedObjects: function(e) {
+ var group = [], x1 = this._groupSelector.ex, y1 = this._groupSelector.ey, x2 = x1 + this._groupSelector.left, y2 = y1 + this._groupSelector.top, currentObject, 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) continue;
+ if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || currentObject.containsPoint(selectionX1Y1) || currentObject.containsPoint(selectionX2Y2)) {
+ if (this.selection && currentObject.selectable) {
+ currentObject.set("active", true);
+ group.push(currentObject);
+ if (isClick) break;
+ }
+ }
+ }
+ if (group.length === 1) {
+ this.setActiveObject(group[0], e);
+ } else if (group.length > 1) {
+ group = new fabric.Group(group.reverse());
+ this.setActiveGroup(group);
+ group.saveCoords();
+ this.fire("selection:created", {
+ target: group
+ });
+ this.renderAll();
+ }
+ },
+ findTarget: function(e, skipGroup) {
+ if (this.skipTargetFind) return;
+ var target, pointer = this.getPointer(e);
+ if (this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)) {
+ target = this.lastRenderedObjectWithControlsAboveOverlay;
+ return target;
+ }
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
+ target = activeGroup;
+ return target;
+ }
+ var possibleTargets = [];
+ for (var i = this._objects.length; i--; ) {
+ if (this._objects[i] && this._objects[i].visible && this.containsPoint(e, this._objects[i])) {
+ if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) {
+ possibleTargets[possibleTargets.length] = this._objects[i];
+ } else {
+ target = this._objects[i];
+ this.relatedTarget = target;
+ break;
+ }
+ }
+ }
+ for (var j = 0, len = possibleTargets.length; j < len; j++) {
+ pointer = this.getPointer(e);
+ var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y);
+ if (!isTransparent) {
+ target = possibleTargets[j];
+ this.relatedTarget = target;
+ break;
+ }
+ }
+ return target;
+ },
+ getPointer: function(e) {
+ var pointer = getPointer(e, this.upperCanvasEl);
+ return {
+ x: pointer.x - this._offset.left,
+ y: pointer.y - this._offset.top
+ };
+ },
+ _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");
+ },
+ _createCacheCanvas: function() {
+ this.cacheCanvasEl = this._createCanvasElement();
+ this.cacheCanvasEl.setAttribute("width", this.width);
+ this.cacheCanvasEl.setAttribute("height", this.height);
+ this.contextCache = this.cacheCanvasEl.getContext("2d");
+ },
+ _initWrapperElement: function() {
+ this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, "div", {
+ "class": this.containerClass
+ });
+ fabric.util.setStyle(this.wrapperEl, {
+ width: this.getWidth() + "px",
+ height: this.getHeight() + "px",
+ position: "relative"
+ });
+ fabric.util.makeElementUnselectable(this.wrapperEl);
+ },
+ _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);
+ },
+ _copyCanvasStyle: function(fromEl, toEl) {
+ toEl.style.cssText = fromEl.style.cssText;
+ },
+ getSelectionContext: function() {
+ return this.contextTop;
+ },
+ getSelectionElement: function() {
+ return this.upperCanvasEl;
+ },
+ setActiveObject: function(object, e) {
+ if (this._activeObject) {
+ this._activeObject.set("active", false);
+ }
+ this._activeObject = object;
+ object.set("active", true);
+ this.renderAll();
+ this.fire("object:selected", {
+ target: object,
+ e: e
+ });
+ object.fire("selected", {
+ e: e
+ });
+ return this;
+ },
+ getActiveObject: function() {
+ return this._activeObject;
+ },
+ discardActiveObject: function() {
+ if (this._activeObject) {
+ this._activeObject.set("active", false);
+ }
+ this._activeObject = null;
+ return this;
+ },
+ setActiveGroup: function(group) {
+ this._activeGroup = group;
+ if (group) {
+ group.canvas = this;
+ group.set("active", true);
+ }
+ return this;
+ },
+ getActiveGroup: function() {
+ return this._activeGroup;
+ },
+ discardActiveGroup: function() {
+ var g = this.getActiveGroup();
+ if (g) {
+ g.destroy();
+ }
+ return this.setActiveGroup(null);
+ },
+ 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;
+ },
+ deactivateAllWithDispatch: function() {
+ var activeObject = this.getActiveGroup() || this.getActiveObject();
+ if (activeObject) {
+ this.fire("before:selection:cleared", {
+ target: activeObject
+ });
+ }
+ this.deactivateAll();
+ if (activeObject) {
+ this.fire("selection:cleared");
+ }
+ return this;
+ },
+ drawControls: function(ctx) {
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ ctx.save();
+ fabric.Group.prototype.transform.call(activeGroup, ctx);
+ activeGroup.drawBorders(ctx).drawControls(ctx);
+ ctx.restore();
+ } else {
+ for (var i = 0, len = this._objects.length; i < len; ++i) {
+ if (!this._objects[i] || !this._objects[i].active) continue;
+ ctx.save();
+ fabric.Object.prototype.transform.call(this._objects[i], ctx);
+ this._objects[i].drawBorders(ctx).drawControls(ctx);
+ ctx.restore();
+ this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
+ }
+ }
+ }
+ };
+ fabric.Canvas.prototype.toString = fabric.StaticCanvas.prototype.toString;
+ extend(fabric.Canvas.prototype, InteractiveMethods);
+ for (var prop in fabric.StaticCanvas) {
+ if (prop !== "prototype") {
+ fabric.Canvas[prop] = fabric.StaticCanvas[prop];
+ }
+ }
+ if (fabric.isTouchSupported) {
+ fabric.Canvas.prototype._setCursorFromEvent = function() {};
+ }
+ fabric.Element = fabric.Canvas;
+ })();
+ (function() {
+ var cursorMap = [ "n-resize", "ne-resize", "e-resize", "se-resize", "s-resize", "sw-resize", "w-resize", "nw-resize" ], cursorOffset = {
+ mt: 0,
+ tr: 1,
+ mr: 2,
+ br: 3,
+ mb: 4,
+ bl: 5,
+ ml: 6,
+ tl: 7
+ }, addListener = fabric.util.addListener, removeListener = fabric.util.removeListener, getPointer = fabric.util.getPointer;
+ fabric.util.object.extend(fabric.Canvas.prototype, {
+ _initEvents: function() {
+ var _this = this;
+ 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 = function(e, s) {
+ _this.__onTransformGesture(e, s);
+ };
+ addListener(fabric.window, "resize", this._onResize);
+ if (fabric.isTouchSupported) {
+ addListener(this.upperCanvasEl, "touchstart", this._onMouseDown);
+ addListener(this.upperCanvasEl, "touchmove", this._onMouseMove);
+ if (typeof Event !== "undefined" && "add" in Event) {
+ Event.add(this.upperCanvasEl, "gesture", this._onGesture);
+ }
+ } else {
+ addListener(this.upperCanvasEl, "mousedown", this._onMouseDown);
+ addListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
+ }
+ },
+ _onMouseDown: function(e) {
+ this.__onMouseDown(e);
+ !fabric.isTouchSupported && addListener(fabric.document, "mouseup", this._onMouseUp);
+ fabric.isTouchSupported && addListener(fabric.document, "touchend", this._onMouseUp);
+ !fabric.isTouchSupported && addListener(fabric.document, "mousemove", this._onMouseMove);
+ fabric.isTouchSupported && addListener(fabric.document, "touchmove", this._onMouseMove);
+ !fabric.isTouchSupported && removeListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
+ fabric.isTouchSupported && removeListener(this.upperCanvasEl, "touchmove", this._onMouseMove);
+ },
+ _onMouseUp: function(e) {
+ this.__onMouseUp(e);
+ !fabric.isTouchSupported && removeListener(fabric.document, "mouseup", this._onMouseUp);
+ fabric.isTouchSupported && removeListener(fabric.document, "touchend", this._onMouseUp);
+ !fabric.isTouchSupported && removeListener(fabric.document, "mousemove", this._onMouseMove);
+ fabric.isTouchSupported && removeListener(fabric.document, "touchmove", this._onMouseMove);
+ !fabric.isTouchSupported && addListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
+ fabric.isTouchSupported && addListener(this.upperCanvasEl, "touchmove", this._onMouseMove);
+ },
+ _onMouseMove: function(e) {
+ !this.allowTouchScrolling && e.preventDefault && e.preventDefault();
+ this.__onMouseMove(e);
+ },
+ _onResize: function() {
+ this.calcOffset();
+ },
+ __onMouseUp: function(e) {
+ var target;
+ if (this.isDrawingMode && this._isCurrentlyDrawing) {
+ this._isCurrentlyDrawing = false;
+ if (this.clipTo) {
+ this.contextTop.restore();
+ }
+ this.freeDrawingBrush.onMouseUp();
+ this.fire("mouse:up", {
+ e: e
+ });
+ return;
+ }
+ if (this._currentTransform) {
+ var transform = this._currentTransform;
+ target = transform.target;
+ if (target._scaling) {
+ target._scaling = false;
+ }
+ target.isMoving = false;
+ target.setCoords();
+ if (this.stateful && target.hasStateChanged()) {
+ this.fire("object:modified", {
+ target: target
+ });
+ target.fire("modified");
+ }
+ if (this._previousOriginX) {
+ this._currentTransform.target.adjustPosition(this._previousOriginX);
+ this._previousOriginX = null;
+ }
+ }
+ this._currentTransform = null;
+ if (this.selection && this._groupSelector) {
+ this._findSelectedObjects(e);
+ }
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ activeGroup.setObjectsCoords();
+ activeGroup.set("isMoving", false);
+ this._setCursor(this.defaultCursor);
+ }
+ this._groupSelector = null;
+ this.renderAll();
+ this._setCursorFromEvent(e, target);
+ var _this = this;
+ setTimeout(function() {
+ _this._setCursorFromEvent(e, target);
+ }, 50);
+ this.fire("mouse:up", {
+ target: target,
+ e: e
+ });
+ target && target.fire("mouseup", {
+ e: e
+ });
+ },
+ __onMouseDown: function(e) {
+ var pointer;
+ var isLeftClick = "which" in e ? e.which === 1 : e.button === 1;
+ if (!isLeftClick && !fabric.isTouchSupported) return;
+ if (this.isDrawingMode) {
+ pointer = this.getPointer(e);
+ this._isCurrentlyDrawing = true;
+ this.discardActiveObject().renderAll();
+ if (this.clipTo) {
+ fabric.util.clipContext(this, this.contextTop);
+ }
+ this.freeDrawingBrush.onMouseDown(pointer);
+ this.fire("mouse:down", {
+ e: e
+ });
+ return;
+ }
+ if (this._currentTransform) return;
+ var target = this.findTarget(e), corner;
+ pointer = this.getPointer(e);
+ if (this._shouldClearSelection(e, target)) {
+ this._groupSelector = {
+ ex: pointer.x,
+ ey: pointer.y,
+ top: 0,
+ left: 0
+ };
+ this.deactivateAllWithDispatch();
+ target && target.selectable && this.setActiveObject(target, e);
+ } else if (this._shouldHandleGroupLogic(e, target)) {
+ this._handleGroupLogic(e, target);
+ target = this.getActiveGroup();
+ } else {
+ this.stateful && target.saveState();
+ if (corner = target._findTargetCorner(e, this._offset)) {
+ this.onBeforeScaleRotate(target);
+ }
+ if (target !== this.getActiveGroup() && target !== this.getActiveObject()) {
+ this.deactivateAll();
+ this.setActiveObject(target, e);
+ }
+ this._setupCurrentTransform(e, target);
+ }
+ this.renderAll();
+ this.fire("mouse:down", {
+ target: target,
+ e: e
+ });
+ target && target.fire("mousedown", {
+ e: e
+ });
+ if (corner === "mtr") {
+ this._previousOriginX = this._currentTransform.target.originX;
+ this._currentTransform.target.adjustPosition("center");
+ this._currentTransform.left = this._currentTransform.target.left;
+ this._currentTransform.top = this._currentTransform.target.top;
+ }
+ },
+ __onMouseMove: function(e) {
+ var target, pointer;
+ if (this.isDrawingMode) {
+ if (this._isCurrentlyDrawing) {
+ pointer = this.getPointer(e);
+ this.freeDrawingBrush.onMouseMove(pointer);
+ }
+ this.upperCanvasEl.style.cursor = this.freeDrawingCursor;
+ this.fire("mouse:move", {
+ e: e
+ });
+ return;
+ }
+ var groupSelector = this._groupSelector;
+ if (groupSelector) {
+ pointer = getPointer(e, this.upperCanvasEl);
+ groupSelector.left = pointer.x - this._offset.left - groupSelector.ex;
+ groupSelector.top = pointer.y - this._offset.top - groupSelector.ey;
+ this.renderTop();
+ } else if (!this._currentTransform) {
+ var style = this.upperCanvasEl.style;
+ target = this.findTarget(e);
+ if (!target || target && !target.selectable) {
+ for (var i = this._objects.length; i--; ) {
+ if (this._objects[i] && !this._objects[i].active) {
+ this._objects[i].set("active", false);
+ }
+ }
+ style.cursor = this.defaultCursor;
+ } else {
+ this._setCursorFromEvent(e, target);
+ }
+ } else {
+ pointer = getPointer(e, this.upperCanvasEl);
+ var x = pointer.x, y = pointer.y, reset = false, transform = this._currentTransform;
+ target = transform.target;
+ target.isMoving = true;
+ if ((transform.action === "scale" || transform.action === "scaleX" || transform.action === "scaleY") && (e.altKey && (transform.originX !== "center" || transform.originY !== "center") || !e.altKey && transform.originX === "center" && transform.originY === "center")) {
+ this._resetCurrentTransform(e);
+ reset = true;
+ }
+ if (transform.action === "rotate") {
+ this._rotateObject(x, y);
+ this.fire("object:rotating", {
+ target: target,
+ e: e
+ });
+ target.fire("rotating", {
+ e: e
+ });
+ } else if (transform.action === "scale") {
+ if ((e.shiftKey || this.uniScaleTransform) && !target.get("lockUniScaling")) {
+ transform.currentAction = "scale";
+ this._scaleObject(x, y);
+ } else {
+ if (!reset && transform.currentAction === "scale") {
+ this._resetCurrentTransform(e);
+ }
+ transform.currentAction = "scaleEqually";
+ this._scaleObject(x, y, "equally");
+ }
+ this.fire("object:scaling", {
+ target: target,
+ e: e
+ });
+ target.fire("scaling", {
+ e: e
+ });
+ } else if (transform.action === "scaleX") {
+ this._scaleObject(x, y, "x");
+ this.fire("object:scaling", {
+ target: target,
+ e: e
+ });
+ target.fire("scaling", {
+ e: e
+ });
+ } else if (transform.action === "scaleY") {
+ this._scaleObject(x, y, "y");
+ this.fire("object:scaling", {
+ target: target,
+ e: e
+ });
+ target.fire("scaling", {
+ e: e
+ });
+ } else {
+ this._translateObject(x, y);
+ this.fire("object:moving", {
+ target: target,
+ e: e
+ });
+ target.fire("moving", {
+ e: e
+ });
+ this._setCursor(this.moveCursor);
+ }
+ this.renderAll();
+ }
+ this.fire("mouse:move", {
+ target: target,
+ e: e
+ });
+ target && target.fire("mousemove", {
+ e: e
+ });
+ },
+ _setCursorFromEvent: function(e, target) {
+ var s = this.upperCanvasEl.style;
+ if (!target) {
+ s.cursor = this.defaultCursor;
+ return false;
+ } else {
+ var activeGroup = this.getActiveGroup();
+ var corner = target._findTargetCorner && (!activeGroup || !activeGroup.contains(target)) && target._findTargetCorner(e, this._offset);
+ if (!corner) {
+ s.cursor = this.hoverCursor;
+ } else {
+ if (corner in cursorOffset) {
+ var n = Math.round(target.getAngle() % 360 / 45);
+ if (n < 0) {
+ n += 8;
+ }
+ n += cursorOffset[corner];
+ n %= 8;
+ s.cursor = cursorMap[n];
+ } else if (corner === "mtr" && target.hasRotatingPoint) {
+ s.cursor = this.rotationCursor;
+ } else {
+ s.cursor = this.defaultCursor;
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ });
+ })();
+ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
+ toDataURL: function(options) {
+ options || (options = {});
+ var format = options.format || "png", quality = options.quality || 1, multiplier = options.multiplier || 1;
+ if (multiplier !== 1) {
+ return this.__toDataURLWithMultiplier(format, quality, multiplier);
+ } else {
+ return this.__toDataURL(format, quality);
+ }
+ },
+ __toDataURL: function(format, quality) {
+ this.renderAll(true);
+ var canvasEl = this.upperCanvasEl || this.lowerCanvasEl;
+ var data = fabric.StaticCanvas.supports("toDataURLWithQuality") ? canvasEl.toDataURL("image/" + format, quality) : canvasEl.toDataURL("image/" + format);
+ this.contextTop && this.clearContext(this.contextTop);
+ this.renderAll();
+ return data;
+ },
+ __toDataURLWithMultiplier: function(format, quality, 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;
+ this.setWidth(scaledWidth).setHeight(scaledHeight);
+ ctx.scale(multiplier, multiplier);
+ if (activeGroup) {
+ this._tempRemoveBordersControlsFromGroup(activeGroup);
+ } else if (activeObject && this.deactivateAll) {
+ this.deactivateAll();
+ }
+ this.width = origWidth;
+ this.height = origHeight;
+ this.renderAll(true);
+ var data = this.__toDataURL(format, quality);
+ 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;
+ },
+ toDataURLWithMultiplier: function(format, multiplier, quality) {
+ return this.toDataURL({
+ format: format,
+ multiplier: multiplier,
+ quality: quality
+ });
+ },
+ _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)";
+ });
+ },
+ _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, {
+ loadFromDatalessJSON: function(json, callback) {
+ return this.loadFromJSON(json, callback);
+ },
+ loadFromJSON: function(json, callback) {
+ if (!json) return;
+ var serialized = typeof json === "string" ? JSON.parse(json) : json;
+ this.clear();
+ var _this = this;
+ this._enlivenObjects(serialized.objects, function() {
+ _this._setBgOverlayImages(serialized, callback);
+ });
+ return this;
+ },
+ _setBgOverlayImages: function(serialized, callback) {
+ var _this = this, backgroundPatternLoaded, backgroundImageLoaded, overlayImageLoaded;
+ var cbIfLoaded = function() {
+ callback && backgroundImageLoaded && overlayImageLoaded && backgroundPatternLoaded && callback();
+ };
+ if (serialized.backgroundImage) {
+ this.setBackgroundImage(serialized.backgroundImage, function() {
+ _this.backgroundImageOpacity = serialized.backgroundImageOpacity;
+ _this.backgroundImageStretch = serialized.backgroundImageStretch;
+ _this.renderAll();
+ backgroundImageLoaded = true;
+ cbIfLoaded();
+ });
+ } else {
+ backgroundImageLoaded = true;
+ }
+ if (serialized.overlayImage) {
+ this.setOverlayImage(serialized.overlayImage, function() {
+ _this.overlayImageLeft = serialized.overlayImageLeft || 0;
+ _this.overlayImageTop = serialized.overlayImageTop || 0;
+ _this.renderAll();
+ overlayImageLoaded = true;
+ cbIfLoaded();
+ });
+ } else {
+ overlayImageLoaded = true;
+ }
+ if (serialized.background) {
+ this.setBackgroundColor(serialized.background, function() {
+ _this.renderAll();
+ backgroundPatternLoaded = true;
+ cbIfLoaded();
+ });
+ } else {
+ backgroundPatternLoaded = true;
+ }
+ if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background) {
+ callback && callback();
+ }
+ },
+ _enlivenObjects: function(objects, callback) {
+ var _this = this;
+ if (objects.length === 0) {
+ callback && callback();
+ }
+ 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();
+ });
+ },
+ _toDataURL: function(format, callback) {
+ this.clone(function(clone) {
+ callback(clone.toDataURL(format));
+ });
+ },
+ _toDataURLWithMultiplier: function(format, multiplier, callback) {
+ this.clone(function(clone) {
+ callback(clone.toDataURLWithMultiplier(format, multiplier));
+ });
+ },
+ clone: function(callback) {
+ var data = JSON.stringify(this);
+ this.cloneWithoutData(function(clone) {
+ clone.loadFromJSON(data, function() {
+ callback && callback(clone);
+ });
+ });
+ },
+ 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;
+ }
+ fabric.Object = fabric.util.createClass({
+ type: "object",
+ originX: "center",
+ originY: "center",
+ top: 0,
+ left: 0,
+ width: 0,
+ height: 0,
+ scaleX: 1,
+ scaleY: 1,
+ flipX: false,
+ flipY: false,
+ opacity: 1,
+ angle: 0,
+ cornerSize: 12,
+ transparentCorners: true,
+ padding: 0,
+ borderColor: "rgba(102,153,255,0.75)",
+ cornerColor: "rgba(102,153,255,0.5)",
+ centerTransform: false,
+ fill: "rgb(0,0,0)",
+ fillRule: "source-over",
+ overlayFill: null,
+ stroke: null,
+ strokeWidth: 1,
+ strokeDashArray: null,
+ strokeLineCap: "butt",
+ strokeLineJoin: "miter",
+ strokeMiterLimit: 10,
+ shadow: null,
+ borderOpacityWhenMoving: .4,
+ borderScaleFactor: 1,
+ transformMatrix: null,
+ minScaleLimit: .01,
+ selectable: true,
+ visible: true,
+ hasControls: true,
+ hasBorders: true,
+ hasRotatingPoint: true,
+ rotatingPointOffset: 40,
+ perPixelTargetFind: false,
+ includeDefaultValues: true,
+ clipTo: null,
+ lockMovementX: false,
+ lockMovementY: false,
+ lockRotation: false,
+ lockScalingX: false,
+ lockScalingY: false,
+ lockUniScaling: false,
+ stateProperties: ("top left width height scaleX scaleY flipX flipY " + "angle opacity cornerSize fill overlayFill originX originY " + "stroke strokeWidth strokeDashArray fillRule " + "borderScaleFactor transformMatrix selectable shadow visible").split(" "),
+ initialize: function(options) {
+ if (options) {
+ this.setOptions(options);
+ }
+ },
+ _initGradient: function(options) {
+ if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) {
+ this.set("fill", new fabric.Gradient(options.fill));
+ }
+ },
+ _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));
+ }
+ },
+ _initShadow: function(options) {
+ if (options.shadow && !(options.shadow instanceof fabric.Shadow)) {
+ this.setShadow(options.shadow);
+ }
+ },
+ _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);
+ }
+ },
+ setOptions: function(options) {
+ for (var prop in options) {
+ this.set(prop, options[prop]);
+ }
+ this._initGradient(options);
+ this._initPattern(options);
+ this._initShadow(options);
+ this._initClipping(options);
+ },
+ transform: function(ctx, fromLeft) {
+ ctx.globalAlpha = this.opacity;
+ 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));
+ },
+ toObject: function(propertiesToInclude) {
+ var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
+ var 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,
+ overlayFill: this.overlayFill,
+ 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),
+ selectable: this.selectable,
+ hasControls: this.hasControls,
+ hasBorders: this.hasBorders,
+ hasRotatingPoint: this.hasRotatingPoint,
+ transparentCorners: this.transparentCorners,
+ perPixelTargetFind: this.perPixelTargetFind,
+ shadow: this.shadow && this.shadow.toObject ? this.shadow.toObject() : this.shadow,
+ visible: this.visible,
+ clipTo: this.clipTo && String(this.clipTo)
+ };
+ if (!this.includeDefaultValues) {
+ object = this._removeDefaultValues(object);
+ }
+ fabric.util.populateWithProperties(this, object, propertiesToInclude);
+ return object;
+ },
+ toDatalessObject: function(propertiesToInclude) {
+ return this.toObject(propertiesToInclude);
+ },
+ getSvgStyles: function() {
+ var fill = this.fill ? this.fill.toLive ? "url(#SVGID_" + this.fill.id + ")" : this.fill : "none";
+ var stroke = this.stroke ? this.stroke.toLive ? "url(#SVGID_" + this.stroke.id + ")" : this.stroke : "none";
+ var strokeWidth = this.strokeWidth ? this.strokeWidth : "0";
+ var strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(" ") : "";
+ var strokeLineCap = this.strokeLineCap ? this.strokeLineCap : "butt";
+ var strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : "miter";
+ var strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : "4";
+ var opacity = typeof this.opacity !== "undefined" ? this.opacity : "1";
+ var visibility = this.visible ? "" : " visibility: hidden;";
+ var 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, "; ", "opacity: ", opacity, ";", filter, visibility ].join("");
+ },
+ getSvgTransform: function() {
+ var angle = this.getAngle();
+ var center = this.getCenterPoint();
+ var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
+ var translatePart = "translate(" + toFixed(center.x, NUM_FRACTION_DIGITS) + " " + toFixed(center.y, NUM_FRACTION_DIGITS) + ")";
+ var anglePart = angle !== 0 ? " rotate(" + toFixed(angle, NUM_FRACTION_DIGITS) + ")" : "";
+ var scalePart = this.scaleX === 1 && this.scaleY === 1 ? "" : " scale(" + toFixed(this.scaleX, NUM_FRACTION_DIGITS) + " " + toFixed(this.scaleY, NUM_FRACTION_DIGITS) + ")";
+ var flipXPart = this.flipX ? "matrix(-1 0 0 1 0 0) " : "";
+ var flipYPart = this.flipY ? "matrix(1 0 0 -1 0 0)" : "";
+ return [ translatePart, anglePart, scalePart, flipXPart, flipYPart ].join("");
+ },
+ _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;
+ },
+ _removeDefaultValues: function(object) {
+ var defaultOptions = fabric.Object.prototype.options;
+ if (defaultOptions) {
+ this.stateProperties.forEach(function(prop) {
+ if (object[prop] === defaultOptions[prop]) {
+ delete object[prop];
+ }
+ });
+ }
+ return object;
+ },
+ toString: function() {
+ return "#";
+ },
+ get: function(property) {
+ return this[property];
+ },
+ set: function(key, value) {
+ if (typeof key === "object") {
+ for (var prop in key) {
+ this._set(prop, key[prop]);
+ }
+ } else {
+ if (typeof value === "function" && key !== "clipTo") {
+ this._set(key, value(this.get(key)));
+ } else {
+ this._set(key, value);
+ }
+ }
+ return this;
+ },
+ _set: function(key, value) {
+ 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(.1, 1 / Math.max(this.width, this.height)), 2);
+ }
+ this[key] = value;
+ return this;
+ },
+ toggle: function(property) {
+ var value = this.get(property);
+ if (typeof value === "boolean") {
+ this.set(property, !value);
+ }
+ return this;
+ },
+ setSourcePath: function(value) {
+ this.sourcePath = value;
+ return this;
+ },
+ render: function(ctx, noTransform) {
+ if (this.width === 0 || this.height === 0 || !this.visible) return;
+ ctx.save();
+ var m = this.transformMatrix;
+ if (m && !this.group) {
+ ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ if (!noTransform) {
+ this.transform(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.stroke;
+ }
+ if (this.overlayFill) {
+ ctx.fillStyle = this.overlayFill;
+ } else if (this.fill) {
+ ctx.fillStyle = this.fill.toLive ? this.fill.toLive(ctx) : this.fill;
+ }
+ if (m && this.group) {
+ ctx.translate(-this.group.width / 2, -this.group.height / 2);
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ this._setShadow(ctx);
+ this.clipTo && fabric.util.clipContext(this, ctx);
+ this._render(ctx, noTransform);
+ this.clipTo && ctx.restore();
+ this._removeShadow(ctx);
+ if (this.active && !noTransform) {
+ this.drawBorders(ctx);
+ this.drawControls(ctx);
+ }
+ ctx.restore();
+ },
+ _setShadow: function(ctx) {
+ if (!this.shadow) return;
+ ctx.shadowColor = this.shadow.color;
+ ctx.shadowBlur = this.shadow.blur;
+ ctx.shadowOffsetX = this.shadow.offsetX;
+ ctx.shadowOffsetY = this.shadow.offsetY;
+ },
+ _removeShadow: function(ctx) {
+ ctx.shadowColor = "";
+ ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
+ },
+ _renderFill: function(ctx) {
+ if (!this.fill) return;
+ if (this.fill.toLive) {
+ ctx.save();
+ ctx.translate(-this.width / 2 + this.fill.offsetX || 0, -this.height / 2 + this.fill.offsetY || 0);
+ }
+ ctx.fill();
+ if (this.fill.toLive) {
+ ctx.restore();
+ }
+ if (this.shadow && !this.shadow.affectStroke) {
+ this._removeShadow(ctx);
+ }
+ },
+ _renderStroke: function(ctx) {
+ if (!this.stroke) return;
+ ctx.save();
+ if (this.strokeDashArray) {
+ if (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 {
+ this._stroke ? this._stroke(ctx) : ctx.stroke();
+ }
+ this._removeShadow(ctx);
+ ctx.restore();
+ },
+ clone: function(callback, propertiesToInclude) {
+ if (this.constructor.fromObject) {
+ return this.constructor.fromObject(this.toObject(propertiesToInclude), callback);
+ }
+ return new fabric.Object(this.toObject(propertiesToInclude));
+ },
+ cloneAsImage: function(callback) {
+ var dataUrl = this.toDataURL();
+ fabric.util.loadImage(dataUrl, function(img) {
+ if (callback) {
+ callback(new fabric.Image(img));
+ }
+ });
+ return this;
+ },
+ toDataURL: function(options) {
+ options || (options = {});
+ var el = fabric.util.createCanvasElement();
+ el.width = this.getBoundingRectWidth();
+ el.height = this.getBoundingRectHeight();
+ fabric.util.wrapElement(el, "div");
+ var canvas = new fabric.Canvas(el);
+ if (options.format === "jpeg") {
+ canvas.backgroundColor = "#fff";
+ }
+ var origParams = {
+ active: this.get("active"),
+ left: this.getLeft(),
+ top: this.getTop()
+ };
+ this.set({
+ active: false,
+ left: el.width / 2,
+ top: el.height / 2
+ });
+ canvas.add(this);
+ var data = canvas.toDataURL(options);
+ this.set(origParams).setCoords();
+ canvas.dispose();
+ canvas = null;
+ return data;
+ },
+ isType: function(type) {
+ return this.type === type;
+ },
+ toGrayscale: function() {
+ var fillValue = this.get("fill");
+ if (fillValue) {
+ this.set("overlayFill", new fabric.Color(fillValue).toGrayscale().toRgb());
+ }
+ return this;
+ },
+ complexity: function() {
+ return 0;
+ },
+ toJSON: function(propertiesToInclude) {
+ return this.toObject(propertiesToInclude);
+ },
+ 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()
+ });
+ }
+ this.set(property, fabric.Gradient.forObject(this, gradient));
+ },
+ setPatternFill: function(options) {
+ return this.set("fill", new fabric.Pattern(options));
+ },
+ setShadow: function(options) {
+ return this.set("shadow", new fabric.Shadow(options));
+ },
+ centerH: function() {
+ this.canvas.centerObjectH(this);
+ return this;
+ },
+ centerV: function() {
+ this.canvas.centerObjectV(this);
+ return this;
+ },
+ center: function() {
+ return this.centerH().centerV();
+ },
+ remove: function() {
+ return this.canvas.remove(this);
+ },
+ sendToBack: function() {
+ if (this.group) {
+ fabric.StaticCanvas.prototype.sendToBack.call(this.group, this);
+ } else {
+ this.canvas.sendToBack(this);
+ }
+ return this;
+ },
+ bringToFront: function() {
+ if (this.group) {
+ fabric.StaticCanvas.prototype.bringToFront.call(this.group, this);
+ } else {
+ this.canvas.bringToFront(this);
+ }
+ return this;
+ },
+ sendBackwards: function(intersecting) {
+ if (this.group) {
+ fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting);
+ } else {
+ this.canvas.sendBackwards(this, intersecting);
+ }
+ return this;
+ },
+ bringForward: function(intersecting) {
+ if (this.group) {
+ fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting);
+ } else {
+ this.canvas.bringForward(this, intersecting);
+ }
+ return this;
+ },
+ moveTo: function(index) {
+ if (this.group) {
+ fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index);
+ } else {
+ this.canvas.moveTo(this, index);
+ }
+ return this;
+ }
+ });
+ fabric.util.createAccessors(fabric.Object);
+ fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;
+ extend(fabric.Object.prototype, fabric.Observable);
+ fabric.Object.NUM_FRACTION_DIGITS = 2;
+ fabric.Object.__uid = 0;
+ })(typeof exports !== "undefined" ? exports : this);
+ (function() {
+ var degreesToRadians = fabric.util.degreesToRadians;
+ fabric.util.object.extend(fabric.Object.prototype, {
+ translateToCenterPoint: function(point, originX, originY) {
+ var cx = point.x, cy = point.y;
+ if (originX === "left") {
+ cx = point.x + (this.getWidth() + this.strokeWidth * this.scaleX) / 2;
+ } else if (originX === "right") {
+ cx = point.x - (this.getWidth() + this.strokeWidth * this.scaleX) / 2;
+ }
+ if (originY === "top") {
+ cy = point.y + (this.getHeight() + this.strokeWidth * this.scaleY) / 2;
+ } else if (originY === "bottom") {
+ cy = point.y - (this.getHeight() + this.strokeWidth * this.scaleY) / 2;
+ }
+ return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle));
+ },
+ translateToOriginPoint: function(center, originX, originY) {
+ var x = center.x, y = center.y;
+ if (originX === "left") {
+ x = center.x - (this.getWidth() + this.strokeWidth * this.scaleX) / 2;
+ } else if (originX === "right") {
+ x = center.x + (this.getWidth() + this.strokeWidth * this.scaleX) / 2;
+ }
+ if (originY === "top") {
+ y = center.y - (this.getHeight() + this.strokeWidth * this.scaleY) / 2;
+ } else if (originY === "bottom") {
+ y = center.y + (this.getHeight() + this.strokeWidth * this.scaleY) / 2;
+ }
+ return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle));
+ },
+ getCenterPoint: function() {
+ return this.translateToCenterPoint(new fabric.Point(this.left, this.top), this.originX, this.originY);
+ },
+ toLocalPoint: function(point, originX, originY) {
+ var center = this.getCenterPoint();
+ var x, y;
+ if (originX !== undefined && originY !== undefined) {
+ if (originX === "left") {
+ x = center.x - (this.getWidth() + this.strokeWidth * this.scaleX) / 2;
+ } else if (originX === "right") {
+ x = center.x + (this.getWidth() + this.strokeWidth * this.scaleX) / 2;
+ } else {
+ x = center.x;
+ }
+ if (originY === "top") {
+ y = center.y - (this.getHeight() + this.strokeWidth * this.scaleY) / 2;
+ } else if (originY === "bottom") {
+ y = center.y + (this.getHeight() + this.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));
+ },
+ setPositionByOrigin: function(pos, originX, originY) {
+ var center = this.translateToCenterPoint(pos, originX, originY);
+ var position = this.translateToOriginPoint(center, this.originX, this.originY);
+ this.set("left", position.x);
+ this.set("top", position.y);
+ },
+ adjustPosition: function(to) {
+ var angle = degreesToRadians(this.angle);
+ var hypotHalf = this.getWidth() / 2;
+ var xHalf = Math.cos(angle) * hypotHalf;
+ var yHalf = Math.sin(angle) * hypotHalf;
+ var hypotFull = this.getWidth();
+ var xFull = Math.cos(angle) * hypotFull;
+ var yFull = Math.sin(angle) * hypotFull;
+ if (this.originX === "center" && to === "left" || this.originX === "right" && to === "center") {
+ this.left -= xHalf;
+ this.top -= yHalf;
+ } else if (this.originX === "left" && to === "center" || this.originX === "center" && to === "right") {
+ this.left += xHalf;
+ this.top += yHalf;
+ } else if (this.originX === "left" && to === "right") {
+ this.left += xFull;
+ this.top += yFull;
+ } else if (this.originX === "right" && to === "left") {
+ this.left -= xFull;
+ this.top -= yFull;
+ }
+ this.setCoords();
+ this.originX = to;
+ },
+ _getLeftTopCoords: function() {
+ var angle = degreesToRadians(this.angle);
+ var hypotHalf = this.getWidth() / 2;
+ var xHalf = Math.cos(angle) * hypotHalf;
+ var yHalf = Math.sin(angle) * hypotHalf;
+ var x = this.left;
+ var y = this.top;
+ if (this.originX === "center" || this.originX === "right") {
+ x -= xHalf;
+ }
+ if (this.originY === "center" || this.originY === "bottom") {
+ y -= yHalf;
+ }
+ return {
+ x: x,
+ y: y
+ };
+ }
+ });
+ })();
+ (function() {
+ var degreesToRadians = fabric.util.degreesToRadians;
+ fabric.util.object.extend(fabric.Object.prototype, {
+ oCoords: null,
+ 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);
+ var intersection = fabric.Intersection.intersectPolygonRectangle([ tl, tr, br, bl ], pointTL, pointBR);
+ return intersection.status === "Intersection";
+ },
+ intersectsWithObject: function(other) {
+ function 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);
+ var intersection = fabric.Intersection.intersectPolygonPolygon([ thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl ], [ otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl ]);
+ return intersection.status === "Intersection";
+ },
+ 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);
+ },
+ 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;
+ },
+ containsPoint: function(point) {
+ var lines = this._getImageLines(this.oCoords), xPoints = this._findCrossPoints(point, lines);
+ return xPoints !== 0 && xPoints % 2 === 1;
+ },
+ _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
+ }
+ };
+ },
+ _findCrossPoints: function(point, oCoords) {
+ var b1, b2, a1, a2, xi, yi, xcount = 0, iLine;
+ for (var lineKey in oCoords) {
+ iLine = oCoords[lineKey];
+ if (iLine.o.y < point.y && iLine.d.y < point.y) {
+ continue;
+ }
+ if (iLine.o.y >= point.y && iLine.d.y >= point.y) {
+ continue;
+ }
+ if (iLine.o.x === iLine.d.x && iLine.o.x >= point.x) {
+ xi = iLine.o.x;
+ yi = point.y;
+ } else {
+ 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;
+ }
+ if (xi >= point.x) {
+ xcount += 1;
+ }
+ if (xcount === 2) {
+ break;
+ }
+ }
+ return xcount;
+ },
+ getBoundingRectWidth: function() {
+ return this.getBoundingRect().width;
+ },
+ getBoundingRectHeight: function() {
+ return this.getBoundingRect().height;
+ },
+ getBoundingRect: function() {
+ this.oCoords || this.setCoords();
+ var xCoords = [ this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x ];
+ var minX = fabric.util.array.min(xCoords);
+ var maxX = fabric.util.array.max(xCoords);
+ var width = Math.abs(minX - maxX);
+ var yCoords = [ this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y ];
+ var minY = fabric.util.array.min(yCoords);
+ var maxY = fabric.util.array.max(yCoords);
+ var height = Math.abs(minY - maxY);
+ return {
+ left: minX,
+ top: minY,
+ width: width,
+ height: height
+ };
+ },
+ getWidth: function() {
+ return this.width * this.scaleX;
+ },
+ getHeight: function() {
+ return this.height * this.scaleY;
+ },
+ _constrainScale: function(value) {
+ if (Math.abs(value) < this.minScaleLimit) {
+ if (value < 0) return -this.minScaleLimit; else return this.minScaleLimit;
+ }
+ return value;
+ },
+ 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;
+ },
+ scaleToWidth: function(value) {
+ var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth();
+ return this.scale(value / this.width / boundingRectFactor);
+ },
+ scaleToHeight: function(value) {
+ var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight();
+ return this.scale(value / this.height / boundingRectFactor);
+ },
+ setCoords: function() {
+ var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, padding = this.padding, theta = degreesToRadians(this.angle);
+ this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2;
+ this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2;
+ if (this.currentWidth < 0) {
+ this.currentWidth = Math.abs(this.currentWidth);
+ }
+ var _hypotenuse = Math.sqrt(Math.pow(this.currentWidth / 2, 2) + Math.pow(this.currentHeight / 2, 2));
+ var _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0);
+ var offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta);
+ var coords = this.getCenterPoint();
+ var tl = {
+ x: coords.x - offsetX,
+ y: coords.y - offsetY
+ };
+ var tr = {
+ x: tl.x + this.currentWidth * cosTh,
+ y: tl.y + this.currentWidth * sinTh
+ };
+ var br = {
+ x: tr.x - this.currentHeight * sinTh,
+ y: tr.y + this.currentHeight * cosTh
+ };
+ var bl = {
+ x: tl.x - this.currentHeight * sinTh,
+ y: tl.y + this.currentHeight * cosTh
+ };
+ var ml = {
+ x: tl.x - this.currentHeight / 2 * sinTh,
+ y: tl.y + this.currentHeight / 2 * cosTh
+ };
+ var mt = {
+ x: tl.x + this.currentWidth / 2 * cosTh,
+ y: tl.y + this.currentWidth / 2 * sinTh
+ };
+ var mr = {
+ x: tr.x - this.currentHeight / 2 * sinTh,
+ y: tr.y + this.currentHeight / 2 * cosTh
+ };
+ var mb = {
+ x: bl.x + this.currentWidth / 2 * cosTh,
+ y: bl.y + this.currentWidth / 2 * sinTh
+ };
+ var mtr = {
+ x: mt.x,
+ y: mt.y
+ };
+ this.oCoords = {
+ tl: tl,
+ tr: tr,
+ br: br,
+ bl: bl,
+ ml: ml,
+ mt: mt,
+ mr: mr,
+ mb: mb,
+ mtr: mtr
+ };
+ this._setCornerCoords && this._setCornerCoords();
+ return this;
+ }
+ });
+ })();
+ fabric.util.object.extend(fabric.Object.prototype, {
+ hasStateChanged: function() {
+ return this.stateProperties.some(function(prop) {
+ return this[prop] !== this.originalState[prop];
+ }, this);
+ },
+ 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;
+ },
+ setupState: function() {
+ this.originalState = {};
+ this.saveState();
+ return this;
+ }
+ });
+ (function() {
+ var getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians;
+ fabric.util.object.extend(fabric.Object.prototype, {
+ _findTargetCorner: function(e, offset) {
+ if (!this.hasControls || !this.active) return false;
+ var pointer = getPointer(e, this.canvas.upperCanvasEl), ex = pointer.x - offset.left, ey = pointer.y - offset.top, xPoints, lines;
+ for (var i in this.oCoords) {
+ 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);
+ xPoints = this._findCrossPoints({
+ x: ex,
+ y: ey
+ }, lines);
+ if (xPoints !== 0 && xPoints % 2 === 1) {
+ this.__corner = i;
+ return i;
+ }
+ }
+ return false;
+ },
+ _setCornerCoords: function() {
+ var coords = this.oCoords, theta = degreesToRadians(this.angle), 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), sinTh = Math.sin(theta), cosTh = Math.cos(theta);
+ coords.tl.corner = {
+ tl: {
+ x: coords.tl.x - sinHalfOffset,
+ y: coords.tl.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.tl.x + cosHalfOffset,
+ y: coords.tl.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.tl.x - cosHalfOffset,
+ y: coords.tl.y + sinHalfOffset
+ },
+ br: {
+ x: coords.tl.x + sinHalfOffset,
+ y: coords.tl.y + cosHalfOffset
+ }
+ };
+ coords.tr.corner = {
+ tl: {
+ x: coords.tr.x - sinHalfOffset,
+ y: coords.tr.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.tr.x + cosHalfOffset,
+ y: coords.tr.y - sinHalfOffset
+ },
+ br: {
+ x: coords.tr.x + sinHalfOffset,
+ y: coords.tr.y + cosHalfOffset
+ },
+ bl: {
+ x: coords.tr.x - cosHalfOffset,
+ y: coords.tr.y + sinHalfOffset
+ }
+ };
+ coords.bl.corner = {
+ tl: {
+ x: coords.bl.x - sinHalfOffset,
+ y: coords.bl.y - cosHalfOffset
+ },
+ bl: {
+ x: coords.bl.x - cosHalfOffset,
+ y: coords.bl.y + sinHalfOffset
+ },
+ br: {
+ x: coords.bl.x + sinHalfOffset,
+ y: coords.bl.y + cosHalfOffset
+ },
+ tr: {
+ x: coords.bl.x + cosHalfOffset,
+ y: coords.bl.y - sinHalfOffset
+ }
+ };
+ coords.br.corner = {
+ tr: {
+ x: coords.br.x + cosHalfOffset,
+ y: coords.br.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.br.x - cosHalfOffset,
+ y: coords.br.y + sinHalfOffset
+ },
+ br: {
+ x: coords.br.x + sinHalfOffset,
+ y: coords.br.y + cosHalfOffset
+ },
+ tl: {
+ x: coords.br.x - sinHalfOffset,
+ y: coords.br.y - cosHalfOffset
+ }
+ };
+ coords.ml.corner = {
+ tl: {
+ x: coords.ml.x - sinHalfOffset,
+ y: coords.ml.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.ml.x + cosHalfOffset,
+ y: coords.ml.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.ml.x - cosHalfOffset,
+ y: coords.ml.y + sinHalfOffset
+ },
+ br: {
+ x: coords.ml.x + sinHalfOffset,
+ y: coords.ml.y + cosHalfOffset
+ }
+ };
+ coords.mt.corner = {
+ tl: {
+ x: coords.mt.x - sinHalfOffset,
+ y: coords.mt.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.mt.x + cosHalfOffset,
+ y: coords.mt.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.mt.x - cosHalfOffset,
+ y: coords.mt.y + sinHalfOffset
+ },
+ br: {
+ x: coords.mt.x + sinHalfOffset,
+ y: coords.mt.y + cosHalfOffset
+ }
+ };
+ coords.mr.corner = {
+ tl: {
+ x: coords.mr.x - sinHalfOffset,
+ y: coords.mr.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.mr.x + cosHalfOffset,
+ y: coords.mr.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.mr.x - cosHalfOffset,
+ y: coords.mr.y + sinHalfOffset
+ },
+ br: {
+ x: coords.mr.x + sinHalfOffset,
+ y: coords.mr.y + cosHalfOffset
+ }
+ };
+ coords.mb.corner = {
+ tl: {
+ x: coords.mb.x - sinHalfOffset,
+ y: coords.mb.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.mb.x + cosHalfOffset,
+ y: coords.mb.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.mb.x - cosHalfOffset,
+ y: coords.mb.y + sinHalfOffset
+ },
+ br: {
+ x: coords.mb.x + sinHalfOffset,
+ y: coords.mb.y + cosHalfOffset
+ }
+ };
+ coords.mtr.corner = {
+ tl: {
+ x: coords.mtr.x - sinHalfOffset + sinTh * this.rotatingPointOffset,
+ y: coords.mtr.y - cosHalfOffset - cosTh * this.rotatingPointOffset
+ },
+ tr: {
+ x: coords.mtr.x + cosHalfOffset + sinTh * this.rotatingPointOffset,
+ y: coords.mtr.y - sinHalfOffset - cosTh * this.rotatingPointOffset
+ },
+ bl: {
+ x: coords.mtr.x - cosHalfOffset + sinTh * this.rotatingPointOffset,
+ y: coords.mtr.y + sinHalfOffset - cosTh * this.rotatingPointOffset
+ },
+ br: {
+ x: coords.mtr.x + sinHalfOffset + sinTh * this.rotatingPointOffset,
+ y: coords.mtr.y + cosHalfOffset - cosTh * this.rotatingPointOffset
+ }
+ };
+ },
+ drawBorders: function(ctx) {
+ if (!this.hasBorders) return this;
+ var padding = this.padding, padding2 = padding * 2, strokeWidth = ~~(this.strokeWidth / 2) * 2;
+ ctx.save();
+ ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
+ ctx.strokeStyle = this.borderColor;
+ var scaleX = 1 / this._constrainScale(this.scaleX), scaleY = 1 / this._constrainScale(this.scaleY);
+ ctx.lineWidth = 1 / this.borderScaleFactor;
+ ctx.scale(scaleX, scaleY);
+ var w = this.getWidth(), h = this.getHeight();
+ ctx.strokeRect(~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) - .5, ~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) - .5, ~~(w + padding2 + strokeWidth * this.scaleX) + 1, ~~(h + padding2 + strokeWidth * this.scaleY) + 1);
+ if (this.hasRotatingPoint && !this.get("lockRotation") && this.hasControls) {
+ var rotateHeight = (this.flipY ? h + strokeWidth * this.scaleY + padding * 2 : -h - strokeWidth * this.scaleY - padding * 2) / 2;
+ ctx.beginPath();
+ ctx.moveTo(0, rotateHeight);
+ ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset));
+ ctx.closePath();
+ ctx.stroke();
+ }
+ ctx.restore();
+ return this;
+ },
+ drawControls: function(ctx) {
+ if (!this.hasControls) return this;
+ var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), left = -(this.width / 2), top = -(this.height / 2), _left, _top, sizeX = size / this.scaleX, sizeY = size / this.scaleY, paddingX = this.padding / this.scaleX, paddingY = this.padding / this.scaleY, scaleOffsetY = size2 / this.scaleY, scaleOffsetX = size2 / this.scaleX, scaleOffsetSizeX = (size2 - size) / this.scaleX, scaleOffsetSizeY = (size2 - size) / this.scaleY, height = this.height, width = this.width, methodName = this.transparentCorners ? "strokeRect" : "fillRect", transparent = this.transparentCorners, isVML = typeof G_vmlCanvasManager !== "undefined";
+ ctx.save();
+ ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY);
+ ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
+ ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
+ _left = left - scaleOffsetX - strokeWidth2 - paddingX;
+ _top = top - scaleOffsetY - strokeWidth2 - paddingY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ _left = left + width - scaleOffsetX + strokeWidth2 + paddingX;
+ _top = top - scaleOffsetY - strokeWidth2 - paddingY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ _left = left - scaleOffsetX - strokeWidth2 - paddingX;
+ _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
+ _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ if (!this.get("lockUniScaling")) {
+ _left = left + width / 2 - scaleOffsetX;
+ _top = top - scaleOffsetY - strokeWidth2 - paddingY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ _left = left + width / 2 - scaleOffsetX;
+ _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
+ _top = top + height / 2 - scaleOffsetY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ _left = left - scaleOffsetX - strokeWidth2 - paddingX;
+ _top = top + height / 2 - scaleOffsetY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ }
+ if (this.hasRotatingPoint) {
+ _left = left + width / 2 - scaleOffsetX;
+ _top = this.flipY ? top + height + this.rotatingPointOffset / this.scaleY - sizeY / 2 + strokeWidth2 + paddingY : top - this.rotatingPointOffset / this.scaleY - sizeY / 2 - strokeWidth2 - paddingY;
+ isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ }
+ ctx.restore();
+ return this;
+ }
+ });
+ })();
+ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
+ FX_DURATION: 500,
+ 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;
+ },
+ 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;
+ },
+ 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, {
+ 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;
+ },
+ _animate: function(property, to, options, skipCallbacks) {
+ var obj = 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(obj);
+ },
+ onChange: function(value) {
+ if (propPair) {
+ obj[propPair[0]][propPair[1]] = value;
+ } else {
+ obj.set(property, value);
+ }
+ if (skipCallbacks) return;
+ options.onChange && options.onChange();
+ },
+ onComplete: function() {
+ if (skipCallbacks) return;
+ obj.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;
+ }
+ fabric.Line = fabric.util.createClass(fabric.Object, {
+ type: "line",
+ 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);
+ },
+ _setWidthHeight: function(options) {
+ options || (options = {});
+ this.set("width", Math.abs(this.x2 - this.x1) || 1);
+ this.set("height", Math.abs(this.y2 - this.y1) || 1);
+ this.set("left", "left" in options ? options.left : Math.min(this.x1, this.x2) + this.width / 2);
+ this.set("top", "top" in options ? options.top : Math.min(this.y1, this.y2) + this.height / 2);
+ },
+ _set: function(key, value) {
+ this[key] = value;
+ if (key in coordProps) {
+ this._setWidthHeight();
+ }
+ return this;
+ },
+ _render: function(ctx) {
+ ctx.beginPath();
+ var isInPathGroup = this.group && this.group.type !== "group";
+ if (isInPathGroup && !this.transformMatrix) {
+ ctx.translate(-this.group.width / 2 + this.left, -this.group.height / 2 + this.top);
+ }
+ if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) {
+ var xMult = this.x1 <= this.x2 ? -1 : 1;
+ var yMult = this.y1 <= this.y2 ? -1 : 1;
+ ctx.moveTo(this.width === 1 ? 0 : xMult * this.width / 2, this.height === 1 ? 0 : yMult * this.height / 2);
+ ctx.lineTo(this.width === 1 ? 0 : xMult * -1 * this.width / 2, this.height === 1 ? 0 : yMult * -1 * this.height / 2);
+ }
+ ctx.lineWidth = this.strokeWidth;
+ var origStrokeStyle = ctx.strokeStyle;
+ ctx.strokeStyle = this.stroke || ctx.fillStyle;
+ this._renderStroke(ctx);
+ ctx.strokeStyle = origStrokeStyle;
+ },
+ _renderDashedStroke: function(ctx) {
+ var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1, x = this.width === 1 ? 0 : xMult * this.width / 2, y = this.height === 1 ? 0 : yMult * this.height / 2;
+ ctx.beginPath();
+ fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray);
+ ctx.closePath();
+ },
+ toObject: function(propertiesToInclude) {
+ return extend(this.callSuper("toObject", propertiesToInclude), {
+ x1: this.get("x1"),
+ y1: this.get("y1"),
+ x2: this.get("x2"),
+ y2: this.get("y2")
+ });
+ },
+ toSVG: function() {
+ var markup = this._createBaseSVGMarkup();
+ markup.push("');
+ return markup.join("");
+ },
+ complexity: function() {
+ return 1;
+ }
+ });
+ fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" "));
+ fabric.Line.fromElement = function(element, options) {
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES);
+ var points = [ parsedAttributes.x1 || 0, parsedAttributes.y1 || 0, parsedAttributes.x2 || 0, parsedAttributes.y2 || 0 ];
+ return new fabric.Line(points, extend(parsedAttributes, options));
+ };
+ fabric.Line.fromObject = function(object) {
+ var points = [ object.x1, object.y1, object.x2, object.y2 ];
+ return new fabric.Line(points, 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.Circle) {
+ fabric.warn("fabric.Circle is already defined.");
+ return;
+ }
+ fabric.Circle = fabric.util.createClass(fabric.Object, {
+ type: "circle",
+ initialize: function(options) {
+ options = options || {};
+ this.set("radius", options.radius || 0);
+ this.callSuper("initialize", options);
+ var diameter = this.get("radius") * 2;
+ this.set("width", diameter).set("height", diameter);
+ },
+ toObject: function(propertiesToInclude) {
+ return extend(this.callSuper("toObject", propertiesToInclude), {
+ radius: this.get("radius")
+ });
+ },
+ toSVG: function() {
+ var markup = this._createBaseSVGMarkup();
+ markup.push("');
+ return markup.join("");
+ },
+ _render: function(ctx, noTransform) {
+ ctx.beginPath();
+ ctx.globalAlpha = this.group ? ctx.globalAlpha * this.opacity : this.opacity;
+ ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.radius, 0, piBy2, false);
+ ctx.closePath();
+ this._renderFill(ctx);
+ this._renderStroke(ctx);
+ },
+ getRadiusX: function() {
+ return this.get("radius") * this.get("scaleX");
+ },
+ getRadiusY: function() {
+ return this.get("radius") * this.get("scaleY");
+ },
+ setRadius: function(value) {
+ this.radius = value;
+ this.set("width", value * 2).set("height", value * 2);
+ },
+ complexity: function() {
+ return 1;
+ }
+ });
+ fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("cx cy r".split(" "));
+ 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");
+ }
+ if ("left" in parsedAttributes) {
+ parsedAttributes.left -= options.width / 2 || 0;
+ }
+ if ("top" in parsedAttributes) {
+ parsedAttributes.top -= options.height / 2 || 0;
+ }
+ var obj = new fabric.Circle(extend(parsedAttributes, options));
+ obj.cx = parseFloat(element.getAttribute("cx")) || 0;
+ obj.cy = parseFloat(element.getAttribute("cy")) || 0;
+ return obj;
+ };
+ function isValidRadius(attributes) {
+ return "radius" in attributes && attributes.radius > 0;
+ }
+ 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;
+ }
+ fabric.Triangle = fabric.util.createClass(fabric.Object, {
+ type: "triangle",
+ initialize: function(options) {
+ options = options || {};
+ this.callSuper("initialize", options);
+ this.set("width", options.width || 100).set("height", options.height || 100);
+ },
+ _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);
+ },
+ _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();
+ },
+ toSVG: function() {
+ var markup = this._createBaseSVGMarkup(), widthBy2 = this.width / 2, heightBy2 = this.height / 2;
+ var points = [ -widthBy2 + " " + heightBy2, "0 " + -heightBy2, widthBy2 + " " + heightBy2 ].join(",");
+ markup.push("');
+ return markup.join("");
+ },
+ complexity: function() {
+ return 1;
+ }
+ });
+ 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;
+ }
+ fabric.Ellipse = fabric.util.createClass(fabric.Object, {
+ type: "ellipse",
+ rx: 0,
+ ry: 0,
+ initialize: function(options) {
+ options = options || {};
+ this.callSuper("initialize", options);
+ this.set("rx", options.rx || 0);
+ this.set("ry", options.ry || 0);
+ this.set("width", this.get("rx") * 2);
+ this.set("height", this.get("ry") * 2);
+ },
+ toObject: function(propertiesToInclude) {
+ return extend(this.callSuper("toObject", propertiesToInclude), {
+ rx: this.get("rx"),
+ ry: this.get("ry")
+ });
+ },
+ toSVG: function() {
+ var markup = this._createBaseSVGMarkup();
+ markup.push("');
+ return markup.join("");
+ },
+ render: function(ctx, noTransform) {
+ if (this.rx === 0 || this.ry === 0) return;
+ return this.callSuper("render", ctx, noTransform);
+ },
+ _render: function(ctx, noTransform) {
+ ctx.beginPath();
+ ctx.save();
+ ctx.globalAlpha = this.group ? ctx.globalAlpha * this.opacity : this.opacity;
+ if (this.transformMatrix && this.group) {
+ ctx.translate(this.cx, this.cy);
+ }
+ ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0);
+ ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.rx, 0, piBy2, false);
+ this._renderFill(ctx);
+ this._renderStroke(ctx);
+ ctx.restore();
+ },
+ complexity: function() {
+ return 1;
+ }
+ });
+ fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" "));
+ fabric.Ellipse.fromElement = function(element, options) {
+ options || (options = {});
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES);
+ var cx = parsedAttributes.left;
+ var cy = parsedAttributes.top;
+ if ("left" in parsedAttributes) {
+ parsedAttributes.left -= options.width / 2 || 0;
+ }
+ if ("top" in parsedAttributes) {
+ parsedAttributes.top -= options.height / 2 || 0;
+ }
+ var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));
+ ellipse.cx = cx || 0;
+ ellipse.cy = cy || 0;
+ return 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;
+ }
+ fabric.Rect = fabric.util.createClass(fabric.Object, {
+ type: "rect",
+ rx: 0,
+ ry: 0,
+ strokeDashArray: null,
+ initialize: function(options) {
+ options = options || {};
+ this._initStateProperties();
+ this.callSuper("initialize", options);
+ this._initRxRy();
+ this.x = options.x || 0;
+ this.y = options.y || 0;
+ },
+ _initStateProperties: function() {
+ this.stateProperties = this.stateProperties.concat([ "rx", "ry" ]);
+ },
+ _initRxRy: function() {
+ if (this.rx && !this.ry) {
+ this.ry = this.rx;
+ } else if (this.ry && !this.rx) {
+ this.rx = this.ry;
+ }
+ },
+ _render: function(ctx) {
+ var rx = this.rx || 0, ry = this.ry || 0, x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height, isInPathGroup = this.group && this.group.type !== "group";
+ ctx.beginPath();
+ ctx.globalAlpha = isInPathGroup ? ctx.globalAlpha * this.opacity : this.opacity;
+ if (this.transformMatrix && isInPathGroup) {
+ ctx.translate(this.width / 2 + this.x, this.height / 2 + this.y);
+ }
+ if (!this.transformMatrix && isInPathGroup) {
+ ctx.translate(-this.group.width / 2 + this.width / 2 + this.x, -this.group.height / 2 + this.height / 2 + this.y);
+ }
+ var isRounded = rx !== 0 || ry !== 0;
+ ctx.moveTo(x + rx, y);
+ ctx.lineTo(x + w - rx, y);
+ isRounded && ctx.quadraticCurveTo(x + w, y, x + w, y + ry, x + w, y + ry);
+ ctx.lineTo(x + w, y + h - ry);
+ isRounded && ctx.quadraticCurveTo(x + w, y + h, x + w - rx, y + h, x + w - rx, y + h);
+ ctx.lineTo(x + rx, y + h);
+ isRounded && ctx.quadraticCurveTo(x, y + h, x, y + h - ry, x, y + h - ry);
+ ctx.lineTo(x, y + ry);
+ isRounded && ctx.quadraticCurveTo(x, y, x + rx, y, x + rx, y);
+ ctx.closePath();
+ this._renderFill(ctx);
+ this._renderStroke(ctx);
+ },
+ _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();
+ },
+ _normalizeLeftTopProperties: function(parsedAttributes) {
+ if ("left" in parsedAttributes) {
+ this.set("left", parsedAttributes.left + this.getWidth() / 2);
+ }
+ this.set("x", parsedAttributes.left || 0);
+ if ("top" in parsedAttributes) {
+ this.set("top", parsedAttributes.top + this.getHeight() / 2);
+ }
+ this.set("y", parsedAttributes.top || 0);
+ return this;
+ },
+ toObject: function(propertiesToInclude) {
+ return extend(this.callSuper("toObject", propertiesToInclude), {
+ rx: this.get("rx") || 0,
+ ry: this.get("ry") || 0,
+ x: this.get("x"),
+ y: this.get("y")
+ });
+ },
+ toSVG: function() {
+ var markup = this._createBaseSVGMarkup();
+ markup.push("');
+ return markup.join("");
+ },
+ complexity: function() {
+ return 1;
+ }
+ });
+ fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" "));
+ function _setDefaultLeftTopValues(attributes) {
+ attributes.left = attributes.left || 0;
+ attributes.top = attributes.top || 0;
+ return attributes;
+ }
+ fabric.Rect.fromElement = function(element, options) {
+ if (!element) {
+ return null;
+ }
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES);
+ parsedAttributes = _setDefaultLeftTopValues(parsedAttributes);
+ var rect = new fabric.Rect(extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes));
+ rect._normalizeLeftTopProperties(parsedAttributes);
+ return 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 = {}), toFixed = fabric.util.toFixed, min = fabric.util.array.min;
+ if (fabric.Polyline) {
+ fabric.warn("fabric.Polyline is already defined");
+ return;
+ }
+ fabric.Polyline = fabric.util.createClass(fabric.Object, {
+ type: "polyline",
+ initialize: function(points, options, skipOffset) {
+ options = options || {};
+ this.set("points", points);
+ this.callSuper("initialize", options);
+ this._calcDimensions(skipOffset);
+ },
+ _calcDimensions: function(skipOffset) {
+ return fabric.Polygon.prototype._calcDimensions.call(this, skipOffset);
+ },
+ toObject: function(propertiesToInclude) {
+ return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude);
+ },
+ toSVG: function() {
+ 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("');
+ return markup.join("");
+ },
+ _render: function(ctx) {
+ var point;
+ ctx.beginPath();
+ ctx.moveTo(this.points[0].x, this.points[0].y);
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ point = this.points[i];
+ ctx.lineTo(point.x, point.y);
+ }
+ this._renderFill(ctx);
+ this._renderStroke(ctx);
+ },
+ _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);
+ }
+ },
+ complexity: function() {
+ return this.get("points").length;
+ }
+ });
+ fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
+ 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), minX = min(points, "x"), minY = min(points, "y");
+ minX = minX < 0 ? minX : 0;
+ minY = minX < 0 ? minY : 0;
+ for (var i = 0, len = points.length; i < len; i++) {
+ points[i].x -= options.width / 2 + minX || 0;
+ points[i].y -= options.height / 2 + minY || 0;
+ }
+ return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options), true);
+ };
+ 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;
+ }
+ fabric.Polygon = fabric.util.createClass(fabric.Object, {
+ type: "polygon",
+ initialize: function(points, options, skipOffset) {
+ options = options || {};
+ this.points = points;
+ this.callSuper("initialize", options);
+ this._calcDimensions(skipOffset);
+ },
+ _calcDimensions: function(skipOffset) {
+ var points = this.points, minX = min(points, "x"), minY = min(points, "y"), maxX = max(points, "x"), maxY = max(points, "y");
+ this.width = maxX - minX || 1;
+ this.height = maxY - minY || 1;
+ this.minX = minX;
+ this.minY = minY;
+ if (skipOffset) return;
+ var halfWidth = this.width / 2 + this.minX, halfHeight = this.height / 2 + this.minY;
+ this.points.forEach(function(p) {
+ p.x -= halfWidth;
+ p.y -= halfHeight;
+ }, this);
+ },
+ toObject: function(propertiesToInclude) {
+ return extend(this.callSuper("toObject", propertiesToInclude), {
+ points: this.points.concat()
+ });
+ },
+ toSVG: function() {
+ 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("');
+ return markup.join("");
+ },
+ _render: function(ctx) {
+ var point;
+ ctx.beginPath();
+ ctx.moveTo(this.points[0].x, this.points[0].y);
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ point = this.points[i];
+ ctx.lineTo(point.x, point.y);
+ }
+ this._renderFill(ctx);
+ if (this.stroke || this.strokeDashArray) {
+ ctx.closePath();
+ this._renderStroke(ctx);
+ }
+ },
+ _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] || this.points[0];
+ fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);
+ }
+ ctx.closePath();
+ },
+ complexity: function() {
+ return this.points.length;
+ }
+ });
+ fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
+ 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), minX = min(points, "x"), minY = min(points, "y");
+ minX = minX < 0 ? minX : 0;
+ minY = minX < 0 ? minY : 0;
+ for (var i = 0, len = points.length; i < len; i++) {
+ points[i].x -= options.width / 2 + minX || 0;
+ points[i].y -= options.height / 2 + minY || 0;
+ }
+ return new fabric.Polygon(points, extend(parsedAttributes, options), true);
+ };
+ fabric.Polygon.fromObject = function(object) {
+ return new fabric.Polygon(object.points, object, true);
+ };
+ })(typeof exports !== "undefined" ? exports : this);
+ (function(global) {
+ var commandLengths = {
+ m: 2,
+ l: 2,
+ h: 1,
+ v: 1,
+ c: 6,
+ s: 4,
+ q: 4,
+ t: 2,
+ a: 7
+ };
+ "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;
+ if (fabric.Path) {
+ fabric.warn("fabric.Path is already defined");
+ return;
+ }
+ function getX(item) {
+ if (item[0] === "H") {
+ return item[1];
+ }
+ return item[item.length - 2];
+ }
+ function getY(item) {
+ if (item[0] === "V") {
+ return item[1];
+ }
+ return item[item.length - 1];
+ }
+ fabric.Path = fabric.util.createClass(fabric.Object, {
+ type: "path",
+ 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 : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);
+ if (!this.path) return;
+ if (!fromArray) {
+ this.path = this._parsePath();
+ }
+ this._initializePath(options);
+ if (options.sourcePath) {
+ this.setSourcePath(options.sourcePath);
+ }
+ },
+ _initializePath: function(options) {
+ var isWidthSet = "width" in options && options.width != null, isHeightSet = "height" in options && options.width != null, isLeftSet = "left" in options, isTopSet = "top" in options, origLeft = isLeftSet ? this.left : 0, origTop = isTopSet ? this.top : 0;
+ if (!isWidthSet || !isHeightSet) {
+ extend(this, this._parseDimensions());
+ if (isWidthSet) {
+ this.width = options.width;
+ }
+ if (isHeightSet) {
+ this.height = options.height;
+ }
+ } else {
+ if (!isTopSet) {
+ this.top = this.height / 2;
+ }
+ if (!isLeftSet) {
+ this.left = this.width / 2;
+ }
+ }
+ this.pathOffset = this.pathOffset || this._calculatePathOffset(origLeft, origTop);
+ },
+ _calculatePathOffset: function(origLeft, origTop) {
+ return {
+ x: this.left - origLeft - this.width / 2,
+ y: this.top - origTop - this.height / 2
+ };
+ },
+ _render: function(ctx) {
+ var current, previous = null, x = 0, y = 0, controlX = 0, controlY = 0, tempX, tempY, tempControlX, tempControlY, l = -(this.width / 2 + this.pathOffset.x), t = -(this.height / 2 + this.pathOffset.y);
+ for (var i = 0, len = this.path.length; i < len; ++i) {
+ current = this.path[i];
+ switch (current[0]) {
+ case "l":
+ x += current[1];
+ y += current[2];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case "L":
+ x = current[1];
+ y = current[2];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case "h":
+ x += current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case "H":
+ x = current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case "v":
+ y += current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case "V":
+ y = current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case "m":
+ x += current[1];
+ y += current[2];
+ ctx[previous && (previous[0] === "m" || previous[0] === "M") ? "lineTo" : "moveTo"](x + l, y + t);
+ break;
+
+ case "M":
+ x = current[1];
+ y = current[2];
+ ctx[previous && (previous[0] === "m" || previous[0] === "M") ? "lineTo" : "moveTo"](x + l, y + t);
+ break;
+
+ case "c":
+ tempX = x + current[5];
+ tempY = y + current[6];
+ controlX = x + current[3];
+ controlY = y + current[4];
+ ctx.bezierCurveTo(x + current[1] + l, y + current[2] + t, controlX + l, controlY + t, tempX + l, tempY + t);
+ x = tempX;
+ y = tempY;
+ break;
+
+ case "C":
+ x = 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":
+ tempX = x + current[3];
+ tempY = y + current[4];
+ controlX = controlX ? 2 * x - controlX : x;
+ controlY = controlY ? 2 * y - controlY : y;
+ ctx.bezierCurveTo(controlX + l, controlY + t, x + current[1] + l, y + current[2] + t, tempX + l, tempY + t);
+ controlX = x + current[1];
+ controlY = y + current[2];
+ x = tempX;
+ y = tempY;
+ break;
+
+ case "S":
+ tempX = current[3];
+ tempY = current[4];
+ controlX = 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;
+ controlX = current[1];
+ controlY = current[2];
+ break;
+
+ case "q":
+ tempX = 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":
+ tempX = 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":
+ tempX = x + current[1];
+ tempY = y + current[2];
+ if (previous[0].match(/[QqTt]/) === null) {
+ controlX = x;
+ controlY = y;
+ } else if (previous[0] === "t") {
+ controlX = 2 * x - tempControlX;
+ controlY = 2 * y - tempControlY;
+ } else if (previous[0] === "q") {
+ controlX = 2 * x - controlX;
+ controlY = 2 * y - controlY;
+ }
+ tempControlX = controlX;
+ tempControlY = controlY;
+ ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t);
+ x = tempX;
+ y = tempY;
+ controlX = x + current[1];
+ controlY = y + current[2];
+ break;
+
+ case "T":
+ tempX = current[1];
+ tempY = current[2];
+ controlX = 2 * x - controlX;
+ controlY = 2 * y - controlY;
+ ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t);
+ x = tempX;
+ y = tempY;
+ break;
+
+ case "a":
+ drawArc(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":
+ drawArc(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":
+ ctx.closePath();
+ break;
+ }
+ previous = current;
+ }
+ },
+ render: function(ctx, noTransform) {
+ if (!this.visible) return;
+ ctx.save();
+ var m = this.transformMatrix;
+ if (m) {
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ if (!noTransform) {
+ this.transform(ctx);
+ }
+ if (this.overlayFill) {
+ ctx.fillStyle = this.overlayFill;
+ } else if (this.fill) {
+ ctx.fillStyle = this.fill.toLive ? this.fill.toLive(ctx) : this.fill;
+ }
+ 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.stroke;
+ }
+ this._setShadow(ctx);
+ this.clipTo && fabric.util.clipContext(this, ctx);
+ ctx.beginPath();
+ this._render(ctx);
+ this._renderFill(ctx);
+ this._renderStroke(ctx);
+ this.clipTo && ctx.restore();
+ this._removeShadow(ctx);
+ if (!noTransform && this.active) {
+ this.drawBorders(ctx);
+ this.drawControls(ctx);
+ }
+ ctx.restore();
+ },
+ toString: function() {
+ return "#";
+ },
+ toObject: function(propertiesToInclude) {
+ var o = extend(this.callSuper("toObject", propertiesToInclude), {
+ path: this.path,
+ pathOffset: this.pathOffset
+ });
+ if (this.sourcePath) {
+ o.sourcePath = this.sourcePath;
+ }
+ if (this.transformMatrix) {
+ o.transformMatrix = this.transformMatrix;
+ }
+ return o;
+ },
+ toDatalessObject: function(propertiesToInclude) {
+ var o = this.toObject(propertiesToInclude);
+ if (this.sourcePath) {
+ o.path = this.sourcePath;
+ }
+ delete o.sourcePath;
+ return o;
+ },
+ toSVG: function() {
+ var chunks = [], markup = this._createBaseSVGMarkup();
+ for (var i = 0, len = this.path.length; i < len; i++) {
+ chunks.push(this.path[i].join(" "));
+ }
+ var path = chunks.join(" ");
+ markup.push('', "", "");
+ return markup.join("");
+ },
+ complexity: function() {
+ return this.path.length;
+ },
+ _parsePath: function() {
+ var result = [], coords = [], currentPath, parsed, re = /(-?\.\d+)|(-?\d+(\.\d+)?)/g, 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].toLowerCase(), commandLength = commandLengths[command];
+ if (coordsParsed.length - 1 > commandLength) {
+ for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) {
+ result.push([ coordsParsed[0] ].concat(coordsParsed.slice(k, k + commandLength)));
+ }
+ } else {
+ result.push(coordsParsed);
+ }
+ }
+ return result;
+ },
+ _parseDimensions: function() {
+ var aX = [], aY = [], previousX, previousY, isLowerCase = false, x, y;
+ this.path.forEach(function(item, i) {
+ if (item[0] !== "H") {
+ previousX = i === 0 ? getX(item) : getX(this.path[i - 1]);
+ }
+ if (item[0] !== "V") {
+ previousY = i === 0 ? getY(item) : getY(this.path[i - 1]);
+ }
+ if (item[0] === item[0].toLowerCase()) {
+ isLowerCase = true;
+ }
+ x = isLowerCase ? previousX + getX(item) : item[0] === "V" ? previousX : getX(item);
+ y = isLowerCase ? previousY + getY(item) : item[0] === "H" ? previousY : getY(item);
+ var val = parseInt(x, 10);
+ if (!isNaN(val)) aX.push(val);
+ val = parseInt(y, 10);
+ if (!isNaN(val)) aY.push(val);
+ }, this);
+ var minX = min(aX), minY = min(aY), maxX = max(aX), maxY = max(aY), deltaX = maxX - minX, deltaY = maxY - minY;
+ var o = {
+ left: this.left + (minX + deltaX / 2),
+ top: this.top + (minY + deltaY / 2),
+ width: deltaX,
+ height: deltaY
+ };
+ return o;
+ }
+ });
+ fabric.Path.fromObject = function(object, callback) {
+ if (typeof object.path === "string") {
+ fabric.loadSVGFromURL(object.path, function(elements) {
+ var path = elements[0];
+ var 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));
+ }
+ };
+ fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat([ "d" ]);
+ 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)));
+ };
+ 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;
+ }
+ fabric.PathGroup = fabric.util.createClass(fabric.Path, {
+ type: "path-group",
+ fill: "",
+ initialize: function(paths, options) {
+ options = options || {};
+ this.paths = paths || [];
+ for (var i = this.paths.length; i--; ) {
+ this.paths[i].group = this;
+ }
+ this.setOptions(options);
+ this.setCoords();
+ if (options.sourcePath) {
+ this.setSourcePath(options.sourcePath);
+ }
+ },
+ render: function(ctx) {
+ if (!this.visible) return;
+ ctx.save();
+ var m = this.transformMatrix;
+ if (m) {
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ this.transform(ctx);
+ this._setShadow(ctx);
+ this.clipTo && fabric.util.clipContext(this, ctx);
+ for (var i = 0, l = this.paths.length; i < l; ++i) {
+ this.paths[i].render(ctx, true);
+ }
+ this.clipTo && ctx.restore();
+ this._removeShadow(ctx);
+ if (this.active) {
+ this.drawBorders(ctx);
+ this.drawControls(ctx);
+ }
+ ctx.restore();
+ },
+ _set: function(prop, value) {
+ if ((prop === "fill" || prop === "overlayFill") && value && this.isSameColor()) {
+ var i = this.paths.length;
+ while (i--) {
+ this.paths[i]._set(prop, value);
+ }
+ }
+ return this.callSuper("_set", prop, value);
+ },
+ toObject: function(propertiesToInclude) {
+ return extend(parentToObject.call(this, propertiesToInclude), {
+ paths: invoke(this.getObjects(), "toObject", propertiesToInclude),
+ sourcePath: this.sourcePath
+ });
+ },
+ toDatalessObject: function(propertiesToInclude) {
+ var o = this.toObject(propertiesToInclude);
+ if (this.sourcePath) {
+ o.paths = this.sourcePath;
+ }
+ return o;
+ },
+ toSVG: function() {
+ var objects = this.getObjects();
+ var markup = [ "" ];
+ for (var i = 0, len = objects.length; i < len; i++) {
+ markup.push(objects[i].toSVG());
+ }
+ markup.push("");
+ return markup.join("");
+ },
+ toString: function() {
+ return "#";
+ },
+ isSameColor: function() {
+ var firstPathFill = this.getObjects()[0].get("fill");
+ return this.getObjects().every(function(path) {
+ return path.get("fill") === firstPathFill;
+ });
+ },
+ complexity: function() {
+ return this.paths.reduce(function(total, path) {
+ return total + (path && path.complexity ? path.complexity() : 0);
+ }, 0);
+ },
+ toGrayscale: function() {
+ var i = this.paths.length;
+ while (i--) {
+ this.paths[i].toGrayscale();
+ }
+ return this;
+ },
+ getObjects: function() {
+ return this.paths;
+ }
+ });
+ 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));
+ });
+ }
+ };
+ 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;
+ }
+ var _lockProperties = {
+ lockMovementX: true,
+ lockMovementY: true,
+ lockRotation: true,
+ lockScalingX: true,
+ lockScalingY: true,
+ lockUniScaling: true
+ };
+ fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, {
+ type: "group",
+ 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");
+ this._calcBounds();
+ this._updateObjectsCoords();
+ if (options) {
+ extend(this, options);
+ }
+ this._setOpacityIfSame();
+ this.setCoords(true);
+ this.saveCoords();
+ },
+ _updateObjectsCoords: function() {
+ var groupDeltaX = this.left, groupDeltaY = this.top;
+ this.forEachObject(function(object) {
+ var objectLeft = object.get("left"), objectTop = object.get("top");
+ object.set("originalLeft", objectLeft);
+ object.set("originalTop", objectTop);
+ object.set("left", objectLeft - groupDeltaX);
+ object.set("top", objectTop - groupDeltaY);
+ object.setCoords();
+ object.__origHasControls = object.hasControls;
+ object.hasControls = false;
+ }, this);
+ },
+ toString: function() {
+ return "#";
+ },
+ getObjects: function() {
+ return this._objects;
+ },
+ addWithUpdate: function(object) {
+ this._restoreObjectsState();
+ this._objects.push(object);
+ object.group = this;
+ this.forEachObject(function(o) {
+ o.set("active", true);
+ o.group = this;
+ }, this);
+ this._calcBounds();
+ this._updateObjectsCoords();
+ return this;
+ },
+ removeWithUpdate: function(object) {
+ this._restoreObjectsState();
+ this.forEachObject(function(o) {
+ o.set("active", true);
+ o.group = this;
+ }, this);
+ this.remove(object);
+ this._calcBounds();
+ this._updateObjectsCoords();
+ return this;
+ },
+ _onObjectAdded: function(object) {
+ object.group = this;
+ },
+ _onObjectRemoved: function(object) {
+ delete object.group;
+ object.set("active", false);
+ },
+ delegatedProperties: {
+ fill: true,
+ opacity: true,
+ fontFamily: true,
+ fontWeight: true,
+ fontSize: true,
+ fontStyle: true,
+ lineHeight: true,
+ textDecoration: true,
+ textShadow: true,
+ textAlign: true,
+ backgroundColor: true
+ },
+ _set: function(key, value) {
+ if (key in this.delegatedProperties) {
+ var i = this._objects.length;
+ this[key] = value;
+ while (i--) {
+ this._objects[i].set(key, value);
+ }
+ } else {
+ this[key] = value;
+ }
+ },
+ toObject: function(propertiesToInclude) {
+ return extend(this.callSuper("toObject", propertiesToInclude), {
+ objects: invoke(this._objects, "toObject", propertiesToInclude)
+ });
+ },
+ render: function(ctx, noTransform) {
+ if (!this.visible) return;
+ ctx.save();
+ this.transform(ctx);
+ var groupScaleFactor = Math.max(this.scaleX, this.scaleY);
+ this.clipTo && fabric.util.clipContext(this, ctx);
+ for (var i = 0, len = this._objects.length; i < len; i++) {
+ var object = this._objects[i], originalScaleFactor = object.borderScaleFactor, originalHasRotatingPoint = object.hasRotatingPoint;
+ if (!object.visible) continue;
+ object.borderScaleFactor = groupScaleFactor;
+ object.hasRotatingPoint = false;
+ object.render(ctx);
+ object.borderScaleFactor = originalScaleFactor;
+ object.hasRotatingPoint = originalHasRotatingPoint;
+ }
+ this.clipTo && ctx.restore();
+ if (!noTransform && this.active) {
+ this.drawBorders(ctx);
+ this.drawControls(ctx);
+ }
+ ctx.restore();
+ this.setCoords();
+ },
+ _restoreObjectsState: function() {
+ this._objects.forEach(this._restoreObjectState, this);
+ return this;
+ },
+ _restoreObjectState: function(object) {
+ var groupLeft = this.get("left"), groupTop = this.get("top"), groupAngle = this.getAngle() * (Math.PI / 180), rotatedTop = Math.cos(groupAngle) * object.get("top") * this.get("scaleY") + Math.sin(groupAngle) * object.get("left") * this.get("scaleX"), rotatedLeft = -Math.sin(groupAngle) * object.get("top") * this.get("scaleY") + Math.cos(groupAngle) * object.get("left") * this.get("scaleX");
+ object.setAngle(object.getAngle() + this.getAngle());
+ object.set("left", groupLeft + rotatedLeft);
+ object.set("top", groupTop + rotatedTop);
+ object.set("scaleX", object.get("scaleX") * this.get("scaleX"));
+ object.set("scaleY", object.get("scaleY") * this.get("scaleY"));
+ object.setCoords();
+ object.hasControls = object.__origHasControls;
+ delete object.__origHasControls;
+ object.set("active", false);
+ object.setCoords();
+ delete object.group;
+ return this;
+ },
+ destroy: function() {
+ return this._restoreObjectsState();
+ },
+ saveCoords: function() {
+ this._originalLeft = this.get("left");
+ this._originalTop = this.get("top");
+ return this;
+ },
+ hasMoved: function() {
+ return this._originalLeft !== this.get("left") || this._originalTop !== this.get("top");
+ },
+ setObjectsCoords: function() {
+ this.forEachObject(function(object) {
+ object.setCoords();
+ });
+ return this;
+ },
+ _setOpacityIfSame: function() {
+ var objects = this.getObjects(), firstValue = objects[0] ? objects[0].get("opacity") : 1;
+ var isSameOpacity = objects.every(function(o) {
+ return o.get("opacity") === firstValue;
+ });
+ if (isSameOpacity) {
+ this.opacity = firstValue;
+ }
+ },
+ _calcBounds: function() {
+ var aX = [], aY = [], minX, minY, maxX, maxY, o, width, height, i = 0, len = this._objects.length;
+ for (;i < len; ++i) {
+ o = this._objects[i];
+ o.setCoords();
+ for (var prop in o.oCoords) {
+ aX.push(o.oCoords[prop].x);
+ aY.push(o.oCoords[prop].y);
+ }
+ }
+ minX = min(aX);
+ maxX = max(aX);
+ minY = min(aY);
+ maxY = max(aY);
+ width = maxX - minX || 0;
+ height = maxY - minY || 0;
+ this.width = width;
+ this.height = height;
+ this.left = minX + width / 2 || 0;
+ this.top = minY + height / 2 || 0;
+ },
+ toSVG: function() {
+ var objectsMarkup = [];
+ for (var i = this._objects.length; i--; ) {
+ objectsMarkup.push(this._objects[i].toSVG());
+ }
+ return '' + objectsMarkup.join("") + "";
+ },
+ 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];
+ }
+ }
+ });
+ fabric.Group.fromObject = function(object, callback) {
+ fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
+ delete object.objects;
+ callback && callback(new fabric.Group(enlivenedObjects, object));
+ });
+ };
+ 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;
+ }
+ fabric.Image = fabric.util.createClass(fabric.Object, {
+ type: "image",
+ initialize: function(element, options) {
+ options || (options = {});
+ this.filters = [];
+ this.callSuper("initialize", options);
+ this._initElement(element);
+ this._initConfig(options);
+ if (options.filters) {
+ this.filters = options.filters;
+ this.applyFilters();
+ }
+ },
+ getElement: function() {
+ return this._element;
+ },
+ setElement: function(element, callback) {
+ this._element = element;
+ this._originalElement = element;
+ this._initConfig();
+ if (this.filters.length !== 0) {
+ this.applyFilters(callback);
+ }
+ return this;
+ },
+ getOriginalSize: function() {
+ var element = this.getElement();
+ return {
+ width: element.width,
+ height: element.height
+ };
+ },
+ render: function(ctx, noTransform) {
+ if (!this.visible) return;
+ ctx.save();
+ var m = this.transformMatrix;
+ var isInPathGroup = this.group && this.group.type !== "group";
+ if (isInPathGroup) {
+ ctx.translate(-this.group.width / 2 + this.width / 2, -this.group.height / 2 + this.height / 2);
+ }
+ if (m) {
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ if (!noTransform) {
+ this.transform(ctx);
+ }
+ ctx.save();
+ this._setShadow(ctx);
+ this.clipTo && fabric.util.clipContext(this, ctx);
+ this._render(ctx);
+ if (this.shadow && !this.shadow.affectStroke) {
+ this._removeShadow(ctx);
+ }
+ this._renderStroke(ctx);
+ this.clipTo && ctx.restore();
+ ctx.restore();
+ if (this.active && !noTransform) {
+ this.drawBorders(ctx);
+ this.drawControls(ctx);
+ }
+ ctx.restore();
+ },
+ _stroke: function(ctx) {
+ ctx.save();
+ 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.stroke;
+ ctx.beginPath();
+ ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height);
+ ctx.closePath();
+ ctx.restore();
+ },
+ _renderDashedStroke: function(ctx) {
+ var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height;
+ ctx.save();
+ 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.stroke;
+ 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();
+ },
+ 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();
+ })
+ });
+ },
+ toSVG: function() {
+ var markup = [];
+ markup.push('', '');
+ if (this.stroke || this.strokeDashArray) {
+ var origFill = this.fill;
+ this.fill = null;
+ markup.push("');
+ this.fill = origFill;
+ }
+ markup.push("");
+ return markup.join("");
+ },
+ getSrc: function() {
+ return this.getElement().src || this.getElement()._src;
+ },
+ toString: function() {
+ return '#';
+ },
+ clone: function(callback, propertiesToInclude) {
+ this.constructor.fromObject(this.toObject(propertiesToInclude), callback);
+ },
+ applyFilters: function(callback) {
+ if (this.filters.length === 0) {
+ this._element = this._originalElement;
+ callback && callback();
+ return;
+ }
+ var imgEl = this._originalElement, 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);
+ this.filters.forEach(function(filter) {
+ filter && filter.applyTo(canvasEl);
+ });
+ replacement.width = imgEl.width;
+ replacement.height = imgEl.height;
+ if (fabric.isLikelyNode) {
+ replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression);
+ _this._element = replacement;
+ callback && callback();
+ } else {
+ replacement.onload = function() {
+ _this._element = replacement;
+ callback && callback();
+ replacement.onload = canvasEl = imgEl = null;
+ };
+ replacement.src = canvasEl.toDataURL("image/png");
+ }
+ return this;
+ },
+ _render: function(ctx) {
+ ctx.drawImage(this._element, -this.width / 2, -this.height / 2, this.width, this.height);
+ },
+ _resetWidthHeight: function() {
+ var element = this.getElement();
+ this.set("width", element.width);
+ this.set("height", element.height);
+ },
+ _initElement: function(element) {
+ this.setElement(fabric.util.getById(element));
+ fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
+ },
+ _initConfig: function(options) {
+ options || (options = {});
+ this.setOptions(options);
+ this._setWidthHeight(options);
+ },
+ _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();
+ }
+ },
+ _setWidthHeight: function(options) {
+ this.width = "width" in options ? options.width : this.getElement().width || 0;
+ this.height = "height" in options ? options.height : this.getElement().height || 0;
+ },
+ complexity: function() {
+ return 1;
+ }
+ });
+ fabric.Image.CSS_CANVAS = "canvas-img";
+ fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;
+ fabric.Image.fromObject = function(object, callback) {
+ var img = fabric.document.createElement("img"), src = object.src;
+ img.onload = function() {
+ fabric.Image.prototype._initFilters.call(object, object, function(filters) {
+ object.filters = filters || [];
+ var instance = new fabric.Image(img, object);
+ callback && callback(instance);
+ img = img.onload = img.onerror = null;
+ });
+ };
+ img.onerror = function() {
+ fabric.log("Error loading " + img.src);
+ callback && callback(null, true);
+ img = img.onload = img.onerror = null;
+ };
+ img.src = src;
+ };
+ fabric.Image.fromURL = function(url, callback, imgOptions) {
+ fabric.util.loadImage(url, function(img) {
+ callback(new fabric.Image(img, imgOptions));
+ });
+ };
+ fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" "));
+ fabric.Image.fromElement = function(element, callback, options) {
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES);
+ fabric.Image.fromURL(parsedAttributes["xlink:href"], callback, extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes));
+ };
+ fabric.Image.async = true;
+ fabric.Image.pngCompression = 1;
+ })(typeof exports !== "undefined" ? exports : this);
+ fabric.util.object.extend(fabric.Object.prototype, {
+ _getAngleValueForStraighten: function() {
+ var angle = this.getAngle() % 360;
+ if (angle > 0) {
+ return Math.round((angle - 1) / 90) * 90;
+ }
+ return Math.round(angle / 90) * 90;
+ },
+ straighten: function() {
+ this.setAngle(this._getAngleValueForStraighten());
+ return this;
+ },
+ 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, {
+ straightenObject: function(object) {
+ object.straighten();
+ this.renderAll();
+ return this;
+ },
+ fxStraightenObject: function(object) {
+ object.fxStraighten({
+ onChange: this.renderAll.bind(this)
+ });
+ return this;
+ }
+ });
+ fabric.Image.filters = fabric.Image.filters || {};
+ fabric.Image.filters.BaseFilter = fabric.util.createClass({
+ type: "BaseFilter",
+ toObject: function() {
+ return {
+ type: this.type
+ };
+ },
+ toJSON: function() {
+ return this.toObject();
+ }
+ });
+ (function(global) {
+ "use strict";
+ var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend;
+ fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Brightness",
+ initialize: function(options) {
+ options = options || {};
+ this.brightness = options.brightness || 100;
+ },
+ 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);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ brightness: this.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;
+ fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Convolute",
+ 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");
+ },
+ _createImageData: function(w, h) {
+ return this.tmpCtx.createImageData(w, h);
+ },
+ applyTo: function(canvasEl) {
+ var weights = this.matrix;
+ var context = canvasEl.getContext("2d");
+ var pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height);
+ var side = Math.round(Math.sqrt(weights.length));
+ var halfSide = Math.floor(side / 2);
+ var src = pixels.data;
+ var sw = pixels.width;
+ var sh = pixels.height;
+ var w = sw;
+ var h = sh;
+ var output = this._createImageData(w, h);
+ var dst = output.data;
+ var alphaFac = this.opaque ? 1 : 0;
+ for (var y = 0; y < h; y++) {
+ for (var x = 0; x < w; x++) {
+ var sy = y;
+ var sx = x;
+ var dstOff = (y * w + x) * 4;
+ var r = 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;
+ var scx = sx + cx - halfSide;
+ if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
+ var srcOff = (scy * sw + scx) * 4;
+ var 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);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ opaque: this.opaque,
+ matrix: this.matrix
+ });
+ }
+ });
+ 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;
+ fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "GradientTransparency",
+ initialize: function(options) {
+ options = options || {};
+ this.threshold = options.threshold || 100;
+ },
+ 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);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ threshold: this.threshold
+ });
+ }
+ });
+ 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 = {});
+ fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Grayscale",
+ 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);
+ }
+ });
+ 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 = {});
+ fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Invert",
+ 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);
+ }
+ });
+ 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;
+ fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Mask",
+ initialize: function(options) {
+ options = options || {};
+ this.mask = options.mask;
+ this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0;
+ },
+ 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);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ mask: this.mask.toObject(),
+ channel: this.channel
+ });
+ }
+ });
+ fabric.Image.filters.Mask.fromObject = function(object, callback) {
+ var img = fabric.document.createElement("img"), src = object.mask.src;
+ img.onload = function() {
+ object.mask = new fabric.Image(img, object.mask);
+ callback && callback(new fabric.Image.filters.Mask(object));
+ img = img.onload = img.onerror = null;
+ };
+ img.onerror = function() {
+ fabric.log("Error loading " + img.src);
+ callback && callback(null, true);
+ img = img.onload = img.onerror = null;
+ };
+ img.src = src;
+ };
+ 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;
+ fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Noise",
+ initialize: function(options) {
+ options = options || {};
+ this.noise = options.noise || 100;
+ },
+ 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 = (.5 - Math.random()) * noise;
+ data[i] += rand;
+ data[i + 1] += rand;
+ data[i + 2] += rand;
+ }
+ context.putImageData(imageData, 0, 0);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ noise: this.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;
+ fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Pixelate",
+ initialize: function(options) {
+ options = options || {};
+ this.blocksize = options.blocksize || 4;
+ },
+ 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];
+ 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);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ blocksize: this.blocksize
+ });
+ }
+ });
+ 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;
+ fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "RemoveWhite",
+ initialize: function(options) {
+ options = options || {};
+ this.threshold = options.threshold || 30;
+ this.distance = options.distance || 20;
+ },
+ 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);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ threshold: this.threshold,
+ distance: this.distance
+ });
+ }
+ });
+ 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 = {});
+ fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Sepia",
+ 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 = .3 * data[i] + .59 * data[i + 1] + .11 * data[i + 2];
+ data[i] = avg + 100;
+ data[i + 1] = avg + 50;
+ data[i + 2] = avg + 255;
+ }
+ context.putImageData(imageData, 0, 0);
+ }
+ });
+ 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 = {});
+ fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Sepia2",
+ 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 * .393 + g * .769 + b * .189) / 1.351;
+ data[i + 1] = (r * .349 + g * .686 + b * .168) / 1.203;
+ data[i + 2] = (r * .272 + g * .534 + b * .131) / 2.14;
+ }
+ context.putImageData(imageData, 0, 0);
+ }
+ });
+ 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;
+ fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
+ type: "Tint",
+ initialize: function(options) {
+ options = options || {};
+ this.color = options.color || 0;
+ },
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, a;
+ var rgb = parseInt(this.color, 10).toString(16);
+ var cr = parseInt("0x" + rgb.substr(0, 2), 16);
+ var cg = parseInt("0x" + rgb.substr(2, 2), 16);
+ var cb = parseInt("0x" + rgb.substr(4, 2), 16);
+ for (i = 0; i < iLen; i += 4) {
+ a = data[i + 3];
+ if (a > 0) {
+ data[i] = cr;
+ data[i + 1] = cg;
+ data[i + 2] = cb;
+ }
+ }
+ context.putImageData(imageData, 0, 0);
+ },
+ toObject: function() {
+ return extend(this.callSuper("toObject"), {
+ color: this.color
+ });
+ }
+ });
+ 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, 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", "path", "text", "textDecoration", "textShadow", "textAlign", "fontStyle", "lineHeight", "backgroundColor", "textBackgroundColor", "useNative");
+ fabric.Text = fabric.util.createClass(fabric.Object, {
+ _dimensionAffectingProps: {
+ fontSize: true,
+ fontWeight: true,
+ fontFamily: true,
+ textDecoration: true,
+ fontStyle: true,
+ lineHeight: true,
+ stroke: true,
+ strokeWidth: true,
+ text: true
+ },
+ type: "text",
+ fontSize: 40,
+ fontWeight: "normal",
+ fontFamily: "Times New Roman",
+ textDecoration: "",
+ textShadow: "",
+ textAlign: "left",
+ fontStyle: "",
+ lineHeight: 1.3,
+ backgroundColor: "",
+ textBackgroundColor: "",
+ path: null,
+ useNative: true,
+ stateProperties: stateProperties,
+ initialize: function(text, options) {
+ options = options || {};
+ this.text = text;
+ this.__skipDimension = true;
+ this.setOptions(options);
+ this.__skipDimension = false;
+ this._initDimensions();
+ this.setCoords();
+ },
+ _initDimensions: function() {
+ if (this.__skipDimension) return;
+ var canvasEl = fabric.util.createCanvasElement();
+ this._render(canvasEl.getContext("2d"));
+ },
+ toString: function() {
+ return "#';
+ },
+ _render: function(ctx) {
+ var isInPathGroup = this.group && this.group.type !== "group";
+ if (isInPathGroup && !this.transformMatrix) {
+ ctx.translate(-this.group.width / 2 + this.left, -this.group.height / 2 + this.top);
+ } else if (isInPathGroup && this.transformMatrix) {
+ ctx.translate(-this.group.width / 2, -this.group.height / 2);
+ }
+ if (typeof Cufon === "undefined" || this.useNative === true) {
+ this._renderViaNative(ctx);
+ } else {
+ this._renderViaCufon(ctx);
+ }
+ },
+ _renderViaNative: function(ctx) {
+ this.transform(ctx, fabric.isLikelyNode);
+ this._setTextStyles(ctx);
+ var textLines = this.text.split(/\r?\n/);
+ this.width = this._getTextWidth(ctx, textLines);
+ this.height = this._getTextHeight(ctx, textLines);
+ this.clipTo && fabric.util.clipContext(this, ctx);
+ this._renderTextBackground(ctx, textLines);
+ if (this.textAlign !== "left" && this.textAlign !== "justify") {
+ ctx.save();
+ ctx.translate(this.textAlign === "center" ? this.width / 2 : this.width, 0);
+ }
+ ctx.save();
+ this._setTextShadow(ctx);
+ this._renderTextFill(ctx, textLines);
+ this._renderTextStroke(ctx, textLines);
+ this.textShadow && ctx.restore();
+ ctx.restore();
+ if (this.textAlign !== "left" && this.textAlign !== "justify") {
+ ctx.restore();
+ }
+ this._renderTextDecoration(ctx, textLines);
+ this.clipTo && ctx.restore();
+ this._setBoundaries(ctx, textLines);
+ this._totalLineHeight = 0;
+ },
+ _setBoundaries: function(ctx, textLines) {
+ this._boundaries = [];
+ for (var i = 0, len = textLines.length; i < len; i++) {
+ var lineWidth = this._getLineWidth(ctx, textLines[i]);
+ var lineLeftOffset = this._getLineLeftOffset(lineWidth);
+ this._boundaries.push({
+ height: this.fontSize * this.lineHeight,
+ width: lineWidth,
+ left: lineLeftOffset
+ });
+ }
+ },
+ _setTextStyles: function(ctx) {
+ if (this.fill) {
+ ctx.fillStyle = this.fill.toLive ? this.fill.toLive(ctx) : this.fill;
+ }
+ 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.stroke;
+ }
+ ctx.textBaseline = "alphabetic";
+ ctx.textAlign = this.textAlign;
+ ctx.font = this._getFontDeclaration();
+ },
+ _getTextHeight: function(ctx, textLines) {
+ return this.fontSize * textLines.length * this.lineHeight;
+ },
+ _getTextWidth: function(ctx, textLines) {
+ var maxWidth = ctx.measureText(textLines[0]).width;
+ for (var i = 1, len = textLines.length; i < len; i++) {
+ var currentLineWidth = ctx.measureText(textLines[i]).width;
+ if (currentLineWidth > maxWidth) {
+ maxWidth = currentLineWidth;
+ }
+ }
+ return maxWidth;
+ },
+ _setTextShadow: function(ctx) {
+ if (!this.textShadow) return;
+ var reOffsetsAndBlur = /\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/;
+ var shadowDeclaration = this.textShadow;
+ var offsetsAndBlur = reOffsetsAndBlur.exec(this.textShadow);
+ var shadowColor = shadowDeclaration.replace(reOffsetsAndBlur, "");
+ ctx.save();
+ ctx.shadowColor = shadowColor;
+ ctx.shadowOffsetX = parseInt(offsetsAndBlur[1], 10);
+ ctx.shadowOffsetY = parseInt(offsetsAndBlur[2], 10);
+ ctx.shadowBlur = parseInt(offsetsAndBlur[3], 10);
+ this._shadows = [ {
+ blur: ctx.shadowBlur,
+ color: ctx.shadowColor,
+ offX: ctx.shadowOffsetX,
+ offY: ctx.shadowOffsetY
+ } ];
+ this._shadowOffsets = [ [ parseInt(ctx.shadowOffsetX, 10), parseInt(ctx.shadowOffsetY, 10) ] ];
+ },
+ _drawChars: function(method, ctx, chars, left, top) {
+ ctx[method](chars, left, top);
+ },
+ _drawTextLine: function(method, ctx, line, left, top, lineIndex) {
+ if (this.textAlign !== "justify") {
+ this._drawChars(method, ctx, line, left, top, lineIndex);
+ return;
+ }
+ var lineWidth = ctx.measureText(line).width;
+ var totalWidth = this.width;
+ if (totalWidth > lineWidth) {
+ var words = line.split(/\s+/);
+ var wordsWidth = ctx.measureText(line.replace(/\s+/g, "")).width;
+ var widthDiff = totalWidth - wordsWidth;
+ var numSpaces = words.length - 1;
+ var spaceWidth = widthDiff / numSpaces;
+ var leftOffset = 0;
+ for (var i = 0, len = words.length; i < len; i++) {
+ this._drawChars(method, ctx, words[i], left + leftOffset, top, lineIndex);
+ leftOffset += ctx.measureText(words[i]).width + spaceWidth;
+ }
+ } else {
+ this._drawChars(method, ctx, line, left, top, lineIndex);
+ }
+ },
+ _getLeftOffset: function() {
+ if (fabric.isLikelyNode && (this.originX === "left" || this.originX === "center")) {
+ return 0;
+ }
+ return -this.width / 2;
+ },
+ _getTopOffset: function() {
+ if (fabric.isLikelyNode) {
+ if (this.originY === "center") {
+ return -this.height / 2;
+ } else if (this.originY === "bottom") {
+ return -this.height;
+ }
+ return 0;
+ }
+ return -this.height / 2;
+ },
+ _renderTextFill: function(ctx, textLines) {
+ if (!this.fill && !this.skipFillStrokeCheck) return;
+ this._boundaries = [];
+ for (var i = 0, len = textLines.length; i < len; i++) {
+ this._drawTextLine("fillText", ctx, textLines[i], this._getLeftOffset(), this._getTopOffset() + i * this.fontSize * this.lineHeight + this.fontSize, i);
+ }
+ },
+ _renderTextStroke: function(ctx, textLines) {
+ if (!this.stroke && !this.skipFillStrokeCheck) return;
+ ctx.save();
+ if (this.strokeDashArray) {
+ if (1 & this.strokeDashArray.length) {
+ this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);
+ }
+ supportsLineDash && ctx.setLineDash(this.strokeDashArray);
+ }
+ ctx.beginPath();
+ for (var i = 0, len = textLines.length; i < len; i++) {
+ this._drawTextLine("strokeText", ctx, textLines[i], this._getLeftOffset(), this._getTopOffset() + i * this.fontSize * this.lineHeight + this.fontSize, i);
+ }
+ ctx.closePath();
+ ctx.restore();
+ },
+ _renderTextBackground: function(ctx, textLines) {
+ this._renderTextBoxBackground(ctx);
+ this._renderTextLinesBackground(ctx, textLines);
+ },
+ _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();
+ },
+ _renderTextLinesBackground: function(ctx, textLines) {
+ if (!this.textBackgroundColor) return;
+ ctx.save();
+ ctx.fillStyle = this.textBackgroundColor;
+ for (var i = 0, len = textLines.length; i < len; i++) {
+ if (textLines[i] !== "") {
+ var lineWidth = this._getLineWidth(ctx, textLines[i]);
+ var lineLeftOffset = this._getLineLeftOffset(lineWidth);
+ ctx.fillRect(this._getLeftOffset() + lineLeftOffset, this._getTopOffset() + i * this.fontSize * this.lineHeight, lineWidth, this.fontSize * this.lineHeight);
+ }
+ }
+ ctx.restore();
+ },
+ _getLineLeftOffset: function(lineWidth) {
+ if (this.textAlign === "center") {
+ return (this.width - lineWidth) / 2;
+ }
+ if (this.textAlign === "right") {
+ return this.width - lineWidth;
+ }
+ return 0;
+ },
+ _getLineWidth: function(ctx, line) {
+ return this.textAlign === "justify" ? this.width : ctx.measureText(line).width;
+ },
+ _renderTextDecoration: function(ctx, textLines) {
+ if (!this.textDecoration) return;
+ var halfOfVerticalBox = this.originY === "top" ? 0 : this._getTextHeight(ctx, textLines) / 2;
+ var _this = this;
+ function renderLinesAtOffset(offset) {
+ for (var i = 0, len = textLines.length; i < len; i++) {
+ var lineWidth = _this._getLineWidth(ctx, textLines[i]);
+ var lineLeftOffset = _this._getLineLeftOffset(lineWidth);
+ ctx.fillRect(_this._getLeftOffset() + lineLeftOffset, offset + i * _this.fontSize * _this.lineHeight - halfOfVerticalBox, lineWidth, 1);
+ }
+ }
+ if (this.textDecoration.indexOf("underline") > -1) {
+ renderLinesAtOffset(this.fontSize);
+ }
+ if (this.textDecoration.indexOf("line-through") > -1) {
+ renderLinesAtOffset(this.fontSize / 2);
+ }
+ if (this.textDecoration.indexOf("overline") > -1) {
+ renderLinesAtOffset(0);
+ }
+ },
+ _getFontDeclaration: function() {
+ return [ fabric.isLikelyNode ? this.fontWeight : this.fontStyle, fabric.isLikelyNode ? this.fontStyle : this.fontWeight, this.fontSize + "px", fabric.isLikelyNode ? '"' + this.fontFamily + '"' : this.fontFamily ].join(" ");
+ },
+ render: function(ctx, noTransform) {
+ if (!this.visible) return;
+ ctx.save();
+ this._render(ctx);
+ if (!noTransform && this.active) {
+ this.drawBorders(ctx);
+ this.drawControls(ctx);
+ }
+ ctx.restore();
+ },
+ toObject: function(propertiesToInclude) {
+ return 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,
+ textShadow: this.textShadow,
+ textAlign: this.textAlign,
+ path: this.path,
+ backgroundColor: this.backgroundColor,
+ textBackgroundColor: this.textBackgroundColor,
+ useNative: this.useNative
+ });
+ },
+ toSVG: function() {
+ var textLines = this.text.split(/\r?\n/), lineTopOffset = this.useNative ? this.fontSize * this.lineHeight : -this._fontAscent - this._fontAscent / 5 * this.lineHeight, textLeftOffset = -(this.width / 2), textTopOffset = this.useNative ? this.fontSize - 1 : this.height / 2 - textLines.length * this.fontSize - this._totalLineHeight, textAndBg = this._getSVGTextAndBg(lineTopOffset, textLeftOffset, textLines), shadowSpans = this._getSVGShadows(lineTopOffset, textLines);
+ textTopOffset += this._fontAscent ? this._fontAscent / 5 * this.lineHeight : 0;
+ return [ '', textAndBg.textBgRects.join(""), "', shadowSpans.join(""), textAndBg.textSpans.join(""), "", "" ].join("");
+ },
+ _getSVGShadows: function(lineTopOffset, textLines) {
+ var shadowSpans = [], j, i, jlen, ilen, lineTopOffsetMultiplier = 1;
+ if (!this._shadows || !this._boundaries) {
+ return shadowSpans;
+ }
+ for (j = 0, jlen = this._shadows.length; j < jlen; j++) {
+ for (i = 0, ilen = textLines.length; i < ilen; i++) {
+ if (textLines[i] !== "") {
+ var lineLeftOffset = this._boundaries && this._boundaries[i] ? this._boundaries[i].left : 0;
+ shadowSpans.push('", fabric.util.string.escapeXml(textLines[i]), "");
+ lineTopOffsetMultiplier = 1;
+ } else {
+ lineTopOffsetMultiplier++;
+ }
+ }
+ }
+ return shadowSpans;
+ },
+ _getSVGTextAndBg: function(lineTopOffset, textLeftOffset, textLines) {
+ var textSpans = [], textBgRects = [], i, lineLeftOffset, len, lineTopOffsetMultiplier = 1;
+ if (this.backgroundColor && this._boundaries) {
+ textBgRects.push("');
+ }
+ for (i = 0, len = textLines.length; i < len; i++) {
+ if (textLines[i] !== "") {
+ lineLeftOffset = this._boundaries && this._boundaries[i] ? toFixed(this._boundaries[i].left, 2) : 0;
+ textSpans.push('", fabric.util.string.escapeXml(textLines[i]), "");
+ lineTopOffsetMultiplier = 1;
+ } else {
+ lineTopOffsetMultiplier++;
+ }
+ if (!this.textBackgroundColor || !this._boundaries) continue;
+ textBgRects.push("');
+ }
+ return {
+ textSpans: textSpans,
+ textBgRects: textBgRects
+ };
+ },
+ _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() + '"';
+ },
+ setColor: function(value) {
+ this.set("fill", value);
+ return this;
+ },
+ getText: function() {
+ return this.text;
+ },
+ _set: function(name, value) {
+ if (name === "fontFamily" && this.path) {
+ this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, "$1" + value + "$3");
+ }
+ this.callSuper("_set", name, value);
+ if (name in this._dimensionAffectingProps) {
+ this._initDimensions();
+ this.setCoords();
+ }
+ },
+ complexity: function() {
+ return 1;
+ }
+ });
+ fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y font-family font-style font-weight font-size text-decoration".split(" "));
+ 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);
+ var text = new fabric.Text(element.textContent, options);
+ text.set({
+ left: text.getLeft() + text.getWidth() / 2,
+ top: text.getTop() - text.getHeight() / 2
+ });
+ return 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() {
+ if (typeof document !== "undefined" && typeof window !== "undefined") {
+ return;
+ }
+ var DOMParser = new require("xmldom").DOMParser, URL = require("url"), HTTP = require("http"), HTTPS = require("https"), Canvas = require("canvas"), Image = require("canvas").Image;
+ function request(url, encoding, callback) {
+ var oURL = URL.parse(url);
+ if (!oURL.port) {
+ oURL.port = oURL.protocol.indexOf("https:") === 0 ? 443 : 80;
+ }
+ var reqHandler = oURL.port === 443 ? HTTPS : HTTP;
+ var 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();
+ }
+ function request_fs(url, callback) {
+ var fs = require("fs"), stream = fs.createReadStream(url), body = "";
+ stream.on("data", function(chunk) {
+ body += chunk;
+ });
+ stream.on("end", function() {
+ callback(body);
+ });
+ }
+ fabric.util.loadImage = function(url, callback, context) {
+ var createImageAndCallBack = function(data) {
+ img.src = new Buffer(data, "binary");
+ img._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) {
+ request_fs(url, createImageAndCallBack);
+ } else if (url) {
+ request(url, "binary", createImageAndCallBack);
+ }
+ };
+ fabric.loadSVGFromURL = function(url, callback, reviver) {
+ url = url.replace(/^\n\s*/, "").replace(/\?.*$/, "").trim();
+ if (url.indexOf("http") !== 0) {
+ request_fs(url, function(body) {
+ fabric.loadSVGFromString(body, 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);
+ });
+ });
+ };
+ fabric.createCanvasForNode = function(width, height) {
+ var canvasEl = fabric.document.createElement("canvas"), nodeCanvas = new Canvas(width || 600, height || 600);
+ canvasEl.style = {};
+ canvasEl.width = nodeCanvas.width;
+ canvasEl.height = nodeCanvas.height;
+ var FabricCanvas = fabric.Canvas || fabric.StaticCanvas;
+ var fabricCanvas = new FabricCanvas(canvasEl);
+ fabricCanvas.contextContainer = nodeCanvas.getContext("2d");
+ fabricCanvas.nodeCanvas = nodeCanvas;
+ fabricCanvas.Font = Canvas.Font;
+ return fabricCanvas;
+ };
+ 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) {
+ origSetWidth.call(this, width);
+ 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) {
+ origSetHeight.call(this, height);
+ this.nodeCanvas.height = height;
+ return this;
+ };
+ if (fabric.Canvas) {
+ fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;
+ }
+ })();
+ window.fabric = fabric;
+ if (typeof define === "function" && define.amd) {
+ define("fabric", [], function() {
+ return fabric;
+ });
+ }
+})(window);
\ No newline at end of file
diff --git a/src/amd/requirejs.js b/src/amd/requirejs.js
new file mode 100644
index 00000000..c3ebd522
--- /dev/null
+++ b/src/amd/requirejs.js
@@ -0,0 +1,7 @@
+/* Footer for requirejs AMD support */
+
+window.fabric = fabric;
+
+if ( typeof define === "function" && define.amd) {
+ define( "fabric", [], function () { return fabric; } );
+}
\ No newline at end of file
diff --git a/test.js b/test.js
index e825c679..54a59172 100644
--- a/test.js
+++ b/test.js
@@ -5,6 +5,7 @@ testrunner.options.log.tests = false;
testrunner.options.log.assertions = false;
testrunner.run({
+ deps: "./test/fixtures/test_script.js",
code: "./dist/all.js",
tests: [
'./test/unit/rect.js',
diff --git a/test/fixtures/test_script.js b/test/fixtures/test_script.js
index 2a1b664c..450ae980 100644
--- a/test/fixtures/test_script.js
+++ b/test/fixtures/test_script.js
@@ -1,4 +1,7 @@
(function(){
// set global variable
this.foo = 'bar';
+ this.window = undefined;
+ this.document = undefined;
+
})();
\ No newline at end of file