/* build: `node build.js modules=ALL` */
/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
var fabric = fabric || { version: "0.9.13" };
if (typeof exports != 'undefined') {
exports.fabric = fabric;
}
if (typeof document != 'undefined' && typeof window != 'undefined') {
fabric.document = document;
fabric.window = window;
}
else {
// assume we're running under node.js when document/window are not present
fabric.document = require("jsdom").jsdom("
");
fabric.window = fabric.document.createWindow();
}
/**
* True when in environment that supports touch events
* @property isTouchSupported
* @type boolean
*/
fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
/**
* True when in environment that's probably Node.js
* @property isLikelyNode
* @type boolean
*/
fabric.isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined';
/*!
* Copyright (c) 2009 Simo Kinnunen.
* Licensed under the MIT license.
*/
var Cufon = (function() {
var api = function() {
return api.replace.apply(null, arguments);
};
var DOM = api.DOM = {
ready: (function() {
var complete = false, readyStatus = { loaded: 1, complete: 1 };
var queue = [], perform = function() {
if (complete) return;
complete = true;
for (var fn; fn = queue.shift(); fn());
};
// Gecko, Opera, WebKit r26101+
if (fabric.document.addEventListener) {
fabric.document.addEventListener('DOMContentLoaded', perform, false);
fabric.window.addEventListener('pageshow', perform, false); // For cached Gecko pages
}
// Old WebKit, Internet Explorer
if (!fabric.window.opera && fabric.document.readyState) (function() {
readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10);
})();
// Internet Explorer
if (fabric.document.readyState && fabric.document.createStyleSheet) (function() {
try {
fabric.document.body.doScroll('left');
perform();
}
catch (e) {
setTimeout(arguments.callee, 1);
}
})();
addEvent(fabric.window, 'load', perform); // Fallback
return function(listener) {
if (!arguments.length) perform();
else complete ? listener() : queue.push(listener);
};
})()
};
var CSS = api.CSS = {
Size: function(value, base) {
this.value = parseFloat(value);
this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';
this.convert = function(value) {
return value / base * this.value;
};
this.convertFrom = function(value) {
return value / this.value * base;
};
this.toString = function() {
return this.value + this.unit;
};
},
getStyle: function(el) {
return new Style(el.style);
/*
var view = document.defaultView;
if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));
if (el.currentStyle) return new Style(el.currentStyle);
return new Style(el.style);
*/
},
quotedList: cached(function(value) {
// doesn't work properly with empty quoted strings (""), but
// it's not worth the extra code.
var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match;
while (match = re.exec(value)) list.push(match[3] || match[1]);
return list;
}),
ready: (function() {
var complete = false;
var queue = [], perform = function() {
complete = true;
for (var fn; fn = queue.shift(); fn());
};
// Safari 2 does not include ');
function getFontSizeInPixels(el, value) {
return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value);
}
// Original by Dead Edwards.
// Combined with getFontSizeInPixels it also works with relative units.
function getSizeInPixels(el, value) {
if (/px$/i.test(value)) return parseFloat(value);
var style = el.style.left, runtimeStyle = el.runtimeStyle.left;
el.runtimeStyle.left = el.currentStyle.left;
el.style.left = value;
var result = el.style.pixelLeft;
el.style.left = style;
el.runtimeStyle.left = runtimeStyle;
return result;
}
return function(font, text, style, options, node, el, hasNext) {
var redraw = (text === null);
if (redraw) text = node.alt;
// @todo word-spacing, text-decoration
var viewBox = font.viewBox;
var size = style.computedFontSize ||
(style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));
var letterSpacing = style.computedLSpacing;
if (letterSpacing == undefined) {
letterSpacing = style.get('letterSpacing');
style.computedLSpacing = letterSpacing =
(letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing));
}
var wrapper, canvas;
if (redraw) {
wrapper = node;
canvas = node.firstChild;
}
else {
wrapper = fabric.document.createElement('span');
wrapper.className = 'cufon cufon-vml';
wrapper.alt = text;
canvas = fabric.document.createElement('span');
canvas.className = 'cufon-vml-canvas';
wrapper.appendChild(canvas);
if (options.printable) {
var print = fabric.document.createElement('span');
print.className = 'cufon-alt';
print.appendChild(fabric.document.createTextNode(text));
wrapper.appendChild(print);
}
// ie6, for some reason, has trouble rendering the last VML element in the document.
// we can work around this by injecting a dummy element where needed.
// @todo find a better solution
if (!hasNext) wrapper.appendChild(fabric.document.createElement('cvml:shape'));
}
var wStyle = wrapper.style;
var cStyle = canvas.style;
var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height);
var roundingFactor = roundedHeight / height;
var minX = viewBox.minX, minY = viewBox.minY;
cStyle.height = roundedHeight;
cStyle.top = Math.round(size.convert(minY - font.ascent));
cStyle.left = Math.round(size.convert(minX));
wStyle.height = size.convert(font.height) + 'px';
var textDecoration = Cufon.getTextDecoration(options);
var color = style.get('color');
var chars = Cufon.CSS.textTransform(text, style).split('');
var width = 0, offsetX = 0, advance = null;
var glyph, shape, shadows = options.textShadow;
// pre-calculate width
for (var i = 0, k = 0, l = chars.length; i < l; ++i) {
glyph = font.glyphs[chars[i]] || font.missingGlyph;
if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing;
}
if (advance === null) return null;
var fullWidth = -minX + width + (viewBox.width - advance);
var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth);
var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
var stretch = 'r' + coordSize + 'nsnf';
for (i = 0; i < l; ++i) {
glyph = font.glyphs[chars[i]] || font.missingGlyph;
if (!glyph) continue;
if (redraw) {
// some glyphs may be missing so we can't use i
shape = canvas.childNodes[k];
if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow
}
else {
shape = fabric.document.createElement('cvml:shape');
canvas.appendChild(shape);
}
shape.stroked = 'f';
shape.coordsize = coordSize;
shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY;
shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch;
shape.fillcolor = color;
// it's important to not set top/left or IE8 will grind to a halt
var sStyle = shape.style;
sStyle.width = roundedShapeWidth;
sStyle.height = roundedHeight;
if (shadows) {
// due to the limitations of the VML shadow element there
// can only be two visible shadows. opacity is shared
// for all shadows.
var shadow1 = shadows[0], shadow2 = shadows[1];
var color1 = Cufon.CSS.color(shadow1.color), color2;
var shadow = fabric.document.createElement('cvml:shadow');
shadow.on = 't';
shadow.color = color1.color;
shadow.offset = shadow1.offX + ',' + shadow1.offY;
if (shadow2) {
color2 = Cufon.CSS.color(shadow2.color);
shadow.type = 'double';
shadow.color2 = color2.color;
shadow.offset2 = shadow2.offX + ',' + shadow2.offY;
}
shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1;
shape.appendChild(shadow);
}
offsetX += ~~(glyph.w || font.w) + letterSpacing;
++k;
}
wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0);
return wrapper;
};
})());
Cufon.getTextDecoration = function(options) {
return {
underline: options.textDecoration === 'underline',
overline: options.textDecoration === 'overline',
'line-through': options.textDecoration === 'line-through'
};
};
if (typeof exports != 'undefined') {
exports.Cufon = Cufon;
}
/*
json2.js
2011-10-19
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or ' '),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, regexp: true */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
var JSON;
if (!JSON) {
JSON = {};
}
(function () {
'use strict';
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf())
? this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z'
: null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string'
? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0
? '[]'
: gap
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
: '[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0
? '{}'
: gap
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
: '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function'
? walk({'': j}, '')
: j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());
/**
* Wrapper around `console.log` (when available)
* @method log
* @param {Any} Values to log
*/
fabric.log = function() { };
/**
* Wrapper around `console.warn` (when available)
* @method warn
* @param {Any} Values to log as a warning
*/
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);
};
}
}
/**
* @namespace
*/
fabric.Observable = {
/**
* Observes specified event
* @method observe
* @depracated Since 0.8.34. Use `on` instead.
* @param {String} eventName
* @param {Function} handler
*/
observe: function(eventName, handler) {
if (!this.__eventListeners) {
this.__eventListeners = { };
}
// one object with key/value pairs was passed
if (arguments.length === 1) {
for (var prop in eventName) {
this.on(prop, eventName[prop]);
}
}
else {
if (!this.__eventListeners[eventName]) {
this.__eventListeners[eventName] = [ ];
}
this.__eventListeners[eventName].push(handler);
}
},
/**
* Stops event observing for a particular event handler
* @method stopObserving
* @depracated Since 0.8.34. Use `off` instead.
* @param {String} eventName
* @param {Function} handler
*/
stopObserving: function(eventName, handler) {
if (!this.__eventListeners) {
this.__eventListeners = { };
}
if (this.__eventListeners[eventName]) {
fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
}
},
/**
* Fires event with an optional options object
* @method fire
* @param {String} eventName
* @param {Object} [options]
*/
fire: function(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++) {
// avoiding try/catch for perf. reasons
listenersForEvent[i](options || { });
}
}
};
/**
* Alias for observe
* @method observe
* @memberOf fabric.Observable
*/
fabric.Observable.on = fabric.Observable.observe;
/**
* Alias for stopObserving
* @method off
*/
fabric.Observable.off = fabric.Observable.stopObserving;
(function() {
/**
* @namespace
*/
fabric.util = { };
/**
* Removes value from an array.
* Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
* @static
* @memberOf fabric.util
* @method removeFromArray
* @param {Array} array
* @param {Any} value
* @return {Array} original array
*/
function removeFromArray(array, value) {
var idx = array.indexOf(value);
if (idx !== -1) {
array.splice(idx, 1);
}
return array;
};
/**
* Returns random number between 2 specified ones.
* @static
* @method getRandomInt
* @memberOf fabric.util
* @param {Number} min lower limit
* @param {Number} max upper limit
* @return {Number} random value (between min and max)
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var PiBy180 = Math.PI / 180;
/**
* Transforms degrees to radians.
* @static
* @method degreesToRadians
* @memberOf fabric.util
* @param {Number} degrees value in degrees
* @return {Number} value in radians
*/
function degreesToRadians(degrees) {
return degrees * PiBy180;
}
/**
* A wrapper around Number#toFixed, which contrary to native method returns number, not string.
* @static
* @method toFixed
* @memberOf fabric.util
* @param {Number | String} number number to operate on
* @param {Number} fractionDigits number of fraction digits to "leave"
* @return {Number}
*/
function toFixed(number, fractionDigits) {
return parseFloat(Number(number).toFixed(fractionDigits));
}
/**
* Function which always returns `false`.
* @static
* @method falseFunction
* @memberOf fabric.util
* @return {Boolean}
*/
function falseFunction() {
return false;
}
/**
* Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
* @method animate
* @memberOf fabric.util
* @param {Object} [options] Animation options
* @param {Function} [options.onChange] Callback; invoked on every value change
* @param {Function} [options.onComplete] Callback; invoked when value change is completed
* @param {Number} [options.startValue=0] Starting value
* @param {Number} [options.endValue=100] Ending value
* @param {Number} [options.byValue=100] Value to modify the property by
* @param {Function} [options.easing] Easing function
* @param {Number} [options.duration=500] Duration of change
*/
function animate(options) {
options || (options = { });
var start = +new Date(),
duration = options.duration || 500,
finish = start + duration, time, pos,
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();
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, element) {
fabric.window.setTimeout(callback, 1000 / 60);
};
/**
* requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
* @method requestAnimFrame
* @memberOf fabric.util
* @param {Function} callback Callback to invoke
* @param {DOMElement} element optional Element to associate with animation
*/
var requestAnimFrame = function() {
return _requestAnimFrame.apply(fabric.window, arguments);
};
/**
* Loads image element from given url and passes it to a callback
* @method loadImage
* @memberOf fabric.util
* @param {String} url URL representing an image
* @param {Function} callback Callback; invoked with loaded image
* @param {Any} context optional Context to invoke callback in
*/
function loadImage(url, callback, context) {
if (url) {
var img = new Image();
/** @ignore */
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) {
function getKlass(type) {
return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(type))];
}
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 = getKlass(o.type);
if (klass.async) {
klass.fromObject(o, function (o) {
enlivenedObjects[index] = o;
onLoaded();
});
}
else {
enlivenedObjects[index] = klass.fromObject(o);
onLoaded();
}
});
}
function groupSVGElements(elements, options, path) {
var object = elements.length > 1
? new fabric.PathGroup(elements, options)
: elements[0];
if (typeof path !== 'undefined') {
object.setSourcePath(path);
}
return object;
}
fabric.util.removeFromArray = removeFromArray;
fabric.util.degreesToRadians = degreesToRadians;
fabric.util.toFixed = toFixed;
fabric.util.getRandomInt = getRandomInt;
fabric.util.falseFunction = falseFunction;
fabric.util.animate = animate;
fabric.util.requestAnimFrame = requestAnimFrame;
fabric.util.loadImage = loadImage;
fabric.util.enlivenObjects = enlivenObjects;
fabric.util.groupSVGElements = groupSVGElements;
})();
(function() {
var slice = Array.prototype.slice;
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this), len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n !== n) { // shortcut for verifying if it's NaN
n = 0;
}
else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
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]; // in case fn mutates this
if (fn.call(context, val, i, this)) {
result.push(val);
}
}
}
return result;
};
}
if (!Array.prototype.reduce) {
Array.prototype.reduce = function(fn /*, initial*/) {
var len = this.length >>> 0,
i = 0,
rv;
if (arguments.length > 1) {
rv = arguments[1];
}
else {
do {
if (i in this) {
rv = this[i++];
break;
}
// if array contains no values, no initial value to return
if (++i >= len) {
throw new TypeError();
}
}
while (true);
}
for (; i < len; i++) {
if (i in this) {
rv = fn.call(null, rv, this[i], i, this);
}
}
return rv;
};
}
/**
* Invokes method on all items in a given array
* @method invoke
* @memberOf fabric.util.array
* @param {Array} array Array to iterate over
* @param {String} method Name of a method to invoke
*/
function invoke(array, method) {
var args = slice.call(arguments, 2), result = [ ];
for (var i = 0, len = array.length; i < len; i++) {
result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
}
return result;
}
/**
* Finds maximum value in array (not necessarily "first" one)
* @method max
* @memberOf fabric.util.array
* @param {Array} array Array to iterate over
* @param {String} byProperty
*/
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;
}
/**
* Finds minimum value in array (not necessarily "first" one)
* @method min
* @memberOf fabric.util.array
* @param {Array} array Array to iterate over
* @param {String} byProperty
*/
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;
}
/** @namespace */
fabric.util.array = {
invoke: invoke,
min: min,
max: max
};
})();
(function(){
/**
* Copies all enumerable properties of one object to another
* @memberOf fabric.util.object
* @method extend
* @param {Object} destination Where to copy to
* @param {Object} source Where to copy from
*/
function extend(destination, source) {
// JScript DontEnum bug is not taken care of
for (var property in source) {
destination[property] = source[property];
}
return destination;
}
/**
* Creates an empty object and copies all enumerable properties of another object to it
* @method clone
* @memberOf fabric.util.object
* @param {Object} object Object to clone
*/
function clone(object) {
return extend({ }, object);
}
/** @namespace fabric.util.object */
fabric.util.object = {
extend: extend,
clone: clone
};
})();
(function() {
if (!String.prototype.trim) {
/**
* Trims a string (removing whitespace from the beginning and the end)
* @method trim
* @see String#trim on MDN
*/
String.prototype.trim = function () {
// this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
};
}
/**
* Camelizes a string
* @memberOf fabric.util.string
* @method camelize
* @param {String} string String to camelize
* @return {String} Camelized version of a string
*/
function camelize(string) {
return string.replace(/-+(.)?/g, function(match, character) {
return character ? character.toUpperCase() : '';
});
}
/**
* Capitalizes a string
* @memberOf fabric.util.string
* @method capitalize
* @param {String} string String to capitalize
* @return {String} Capitalized version of a string
*/
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, '>');
}
/** @namespace */
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) {
/**
* Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
* @see Function#bind on MDN
* @param {Object} thisArg Object to bind function to
* @param {Any[]} [...] Values to pass to a bound function
* @return {Function}
*/
Function.prototype.bind = function(thisArg) {
var fn = this, args = slice.call(arguments, 1), bound;
if (args.length) {
bound = function() {
return apply.call(fn, this instanceof dummy ? this : thisArg, args.concat(slice.call(arguments)));
};
}
else {
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;
})();
/** @ignore */
var addMethods = function(klass, source, parent) {
for (var property in source) {
if (property in klass.prototype && typeof klass.prototype[property] == 'function') {
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() { };
/**
* Helper for creation of "classes"
* @method createClass
* @memberOf fabric.util
*/
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;
return klass;
}
fabric.util.createClass = createClass;
})();
(function (global) {
/* EVENT HANDLING */
function areHostMethods(object) {
var methodNames = Array.prototype.slice.call(arguments, 1),
t, i, len = methodNames.length;
for (i = 0; i < len; i++) {
t = typeof object[methodNames[i]];
if (!(/^(?:function|object|unknown)$/).test(t)) return false;
}
return true;
}
var getUniqueId = (function () {
if (typeof fabric.document.documentElement.uniqueID !== 'undefined') {
return function (element) {
return element.uniqueID;
};
}
var uid = 0;
return function (element) {
return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
};
})();
/** @ignore */
var getElement, setElement;
(function () {
var elements = { };
/** @ignore */
getElement = function (uid) {
return elements[uid];
};
/** @ignore */
setElement = function (uid, element) {
elements[uid] = element;
};
})();
function createListener(uid, handler) {
return {
handler: handler,
wrappedHandler: createWrappedHandler(uid, handler)
};
}
function createWrappedHandler(uid, handler) {
return function (e) {
handler.call(getElement(uid), e || fabric.window.event);
};
}
function createDispatcher(uid, eventName) {
return function (e) {
if (handlers[uid] && handlers[uid][eventName]) {
var handlersForEvent = handlers[uid][eventName];
for (var i = 0, len = handlersForEvent.length; i < len; i++) {
handlersForEvent[i].call(this, e || fabric.window.event);
}
}
};
}
var shouldUseAddListenerRemoveListener = (
areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
shouldUseAttachEventDetachEvent = (
areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
// IE branch
listeners = { },
// DOM L0 branch
handlers = { },
addListener, removeListener;
if (shouldUseAddListenerRemoveListener) {
/** @ignore */
addListener = function (element, eventName, handler) {
element.addEventListener(eventName, handler, false);
};
/** @ignore */
removeListener = function (element, eventName, handler) {
element.removeEventListener(eventName, handler, false);
};
}
else if (shouldUseAttachEventDetachEvent) {
/** @ignore */
addListener = function (element, eventName, handler) {
var uid = getUniqueId(element);
setElement(uid, element);
if (!listeners[uid]) {
listeners[uid] = { };
}
if (!listeners[uid][eventName]) {
listeners[uid][eventName] = [ ];
}
var listener = createListener(uid, handler);
listeners[uid][eventName].push(listener);
element.attachEvent('on' + eventName, listener.wrappedHandler);
};
/** @ignore */
removeListener = function (element, eventName, handler) {
var uid = getUniqueId(element), listener;
if (listeners[uid] && listeners[uid][eventName]) {
for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
listener = listeners[uid][eventName][i];
if (listener && listener.handler === handler) {
element.detachEvent('on' + eventName, listener.wrappedHandler);
listeners[uid][eventName][i] = null;
}
}
}
};
}
else {
/** @ignore */
addListener = function (element, eventName, handler) {
var uid = getUniqueId(element);
if (!handlers[uid]) {
handlers[uid] = { };
}
if (!handlers[uid][eventName]) {
handlers[uid][eventName] = [ ];
var existingHandler = element['on' + eventName];
if (existingHandler) {
handlers[uid][eventName].push(existingHandler);
}
element['on' + eventName] = createDispatcher(uid, eventName);
}
handlers[uid][eventName].push(handler);
};
/** @ignore */
removeListener = function (element, eventName, handler) {
var uid = getUniqueId(element);
if (handlers[uid] && handlers[uid][eventName]) {
var handlersForEvent = handlers[uid][eventName];
for (var i = 0, len = handlersForEvent.length; i < len; i++) {
if (handlersForEvent[i] === handler) {
handlersForEvent.splice(i, 1);
}
}
}
};
}
/**
* Adds an event listener to an element
* @mthod addListener
* @memberOf fabric.util
* @function
* @param {HTMLElement} element
* @param {String} eventName
* @param {Function} handler
*/
fabric.util.addListener = addListener;
/**
* Removes an event listener from an element
* @mthod removeListener
* @memberOf fabric.util
* @function
* @param {HTMLElement} element
* @param {String} eventName
* @param {Function} handler
*/
fabric.util.removeListener = removeListener;
/**
* Cross-browser wrapper for getting event's coordinates
* @method getPointer
* @memberOf fabric.util
* @param {Event} event
*/
function getPointer(event) {
// TODO (kangax): this method needs fixing
return { x: pointerX(event), y: pointerY(event) };
}
function pointerX(event) {
var docElement = fabric.document.documentElement,
body = fabric.document.body || { scrollLeft: 0 };
// looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
// is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
// need to investigate later
return event.pageX || ((typeof event.clientX != 'unknown' ? event.clientX : 0) +
(docElement.scrollLeft || body.scrollLeft) -
(docElement.clientLeft || 0));
}
function pointerY(event) {
var docElement = fabric.document.documentElement,
body = fabric.document.body || { scrollTop: 0 };
return event.pageY || ((typeof event.clientY != 'unknown' ? event.clientY : 0) +
(docElement.scrollTop || body.scrollTop) -
(docElement.clientTop || 0));
}
if (fabric.isTouchSupported) {
pointerX = function(event) {
return event.touches && event.touches[0] && event.touches[0].pageX || event.clientX;
};
pointerY = function(event) {
return event.touches && event.touches[0] && event.touches[0].pageY || event.clientY;
};
}
fabric.util.getPointer = getPointer;
fabric.util.object.extend(fabric.util, fabric.Observable);
})(this);
(function () {
/**
* Cross-browser wrapper for setting element's style
* @method setStyle
* @memberOf fabric.util
* @param {HTMLElement} element
* @param {Object} styles
* @return {HTMLElement} Element that was passed as a first argument
*/
function setStyle(element, styles) {
var elementStyle = element.style, match;
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',
view = fabric.document.defaultView,
supportsGCS = view && typeof view.getComputedStyle !== 'undefined',
reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
/** @ignore */
setOpacity = function (element) { return element; };
if (supportsOpacity) {
/** @ignore */
setOpacity = function(element, value) {
element.style.opacity = value;
return element;
};
}
else if (supportsFilters) {
/** @ignore */
setOpacity = function(element, value) {
var es = element.style;
if (element.currentStyle && !element.currentStyle.hasLayout) {
es.zoom = 1;
}
if (reOpacity.test(es.filter)) {
value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');
es.filter = es.filter.replace(reOpacity, value);
}
else {
es.filter += ' alpha(opacity=' + (value * 100) + ')';
}
return element;
};
}
fabric.util.setStyle = setStyle;
})();
(function() {
var _slice = Array.prototype.slice;
/**
* Takes id and returns an element with that id (if one exists in a document)
* @method getById
* @memberOf fabric.util
* @param {String|HTMLElement} id
* @return {HTMLElement|null}
*/
function getById(id) {
return typeof id === 'string' ? fabric.document.getElementById(id) : id;
}
/**
* Converts an array-like object (e.g. arguments or NodeList) to an array
* @method toArray
* @memberOf fabric.util
* @param {Object} arrayLike
* @return {Array}
*/
function toArray(arrayLike) {
return _slice.call(arrayLike, 0);
}
try {
var sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
}
catch(err) { }
if (!sliceCanConvertNodelists) {
toArray = function(arrayLike) {
var arr = new Array(arrayLike.length), i = arrayLike.length;
while (i--) {
arr[i] = arrayLike[i];
}
return arr;
};
}
/**
* Creates specified element with specified attributes
* @method makeElement
* @memberOf fabric.util
* @param {String} tagName Type of an element to create
* @param {Object} [attributes] Attributes to set on an element
* @return {HTMLElement} Newly created element
*/
function makeElement(tagName, attributes) {
var el = fabric.document.createElement(tagName);
for (var prop in attributes) {
if (prop === 'class') {
el.className = attributes[prop];
}
else if (prop === 'for') {
el.htmlFor = attributes[prop];
}
else {
el.setAttribute(prop, attributes[prop]);
}
}
return el;
}
/**
* Adds class to an element
* @method addClass
* @memberOf fabric.util
* @param {HTMLElement} element Element to add class to
* @param {String} className Class to add to an element
*/
function addClass(element, className) {
if ((' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
element.className += (element.className ? ' ' : '') + className;
}
}
/**
* Wraps element with another element
* @method wrapElement
* @memberOf fabric.util
* @param {HTMLElement} element Element to wrap
* @param {HTMLElement|String} wrapper Element to wrap with
* @param {Object} [attributes] Attributes to set on a wrapper
* @return {HTMLElement} wrapper
*/
function wrapElement(element, wrapper, attributes) {
if (typeof wrapper === 'string') {
wrapper = makeElement(wrapper, attributes);
}
if (element.parentNode) {
element.parentNode.replaceChild(wrapper, element);
}
wrapper.appendChild(element);
return wrapper;
}
/**
* Returns offset for a given element
* @method getElementOffset
* @function
* @memberOf fabric.util
* @param {HTMLElement} element Element to get offset for
* @return {Object} Object with "left" and "top" properties
*/
function getElementOffset(element) {
// TODO (kangax): need to fix this method
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
}
while (element);
return ({ left: valueL, top: valueT });
}
(function () {
var style = fabric.document.documentElement.style;
var selectProp = 'userSelect' in style
? 'userSelect'
: 'MozUserSelect' in style
? 'MozUserSelect'
: 'WebkitUserSelect' in style
? 'WebkitUserSelect'
: 'KhtmlUserSelect' in style
? 'KhtmlUserSelect'
: '';
/**
* Makes element unselectable
* @method makeElementUnselectable
* @memberOf fabric.util
* @param {HTMLElement} element Element to make unselectable
* @return {HTMLElement} Element that was passed in
*/
function makeElementUnselectable(element) {
if (typeof element.onselectstart !== 'undefined') {
element.onselectstart = fabric.util.falseFunction;
}
if (selectProp) {
element.style[selectProp] = 'none';
}
else if (typeof element.unselectable == 'string') {
element.unselectable = 'on';
}
return element;
}
/**
* Makes element selectable
* @method makeElementSelectable
* @memberOf fabric.util
* @param {HTMLElement} element Element to make selectable
* @return {HTMLElement} Element that was passed in
*/
function makeElementSelectable(element) {
if (typeof element.onselectstart !== 'undefined') {
element.onselectstart = null;
}
if (selectProp) {
element.style[selectProp] = '';
}
else if (typeof element.unselectable == 'string') {
element.unselectable = '';
}
return element;
}
fabric.util.makeElementUnselectable = makeElementUnselectable;
fabric.util.makeElementSelectable = makeElementSelectable;
})();
(function() {
/**
* Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
* @method getScript
* @memberOf fabric.util
* @param {String} url URL of a script to load
* @param {Function} callback Callback to execute when script is finished loading
*/
function getScript(url, callback) {
var headEl = fabric.document.getElementsByTagName("head")[0],
scriptEl = fabric.document.createElement('script'),
loading = true;
scriptEl.type = 'text/javascript';
scriptEl.setAttribute('runat', 'server');
/** @ignore */
scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
if (loading) {
if (typeof this.readyState == 'string' &&
this.readyState !== 'loaded' &&
this.readyState !== 'complete') return;
loading = false;
callback(e || fabric.window.event);
scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
}
};
scriptEl.src = url;
headEl.appendChild(scriptEl);
// causes issue in Opera
// headEl.removeChild(scriptEl);
}
fabric.util.getScript = getScript;
})();
fabric.util.getById = getById;
fabric.util.toArray = toArray;
fabric.util.makeElement = makeElement;
fabric.util.addClass = addClass;
fabric.util.wrapElement = wrapElement;
fabric.util.getElementOffset = getElementOffset;
})();
(function(){
function addParamToUrl(url, param) {
return url + (/\?/.test(url) ? '&' : '?') + param;
}
var makeXHR = (function() {
var factories = [
function() { return new ActiveXObject("Microsoft.XMLHTTP"); },
function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); },
function() { return new XMLHttpRequest(); }
];
for (var i = factories.length; i--; ) {
try {
var req = factories[i]();
if (req) {
return factories[i];
}
}
catch (err) { }
}
})();
function emptyFn() { };
/**
* Cross-browser abstraction for sending XMLHttpRequest
* @method request
* @memberOf fabric.util
* @param {String} url URL to send XMLHttpRequest to
* @param {Object} [options] Options object
* @param {String} [options.method="GET"]
* @param {Function} options.onComplete Callback to invoke when request is completed
* @return {XMLHttpRequest} request
*/
function request(url, options) {
options || (options = { });
var method = options.method ? options.method.toUpperCase() : 'GET',
onComplete = options.onComplete || function() { },
request = makeXHR(),
body;
/** @ignore */
request.onreadystatechange = function() {
if (request.readyState === 4) {
onComplete(request);
request.onreadystatechange = emptyFn;
}
};
if (method === 'GET') {
body = null;
if (typeof options.parameters == 'string') {
url = addParamToUrl(url, options.parameters);
}
}
request.open(method, url, true);
if (method === 'POST' || method === 'PUT') {
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
request.send(body);
return request;
};
fabric.util.request = request;
})();
(function() {
/**
* @method easeInQuad
* @memberOf fabric.util.ease
*/
function easeInQuad(t, b, c, d) {
return c*(t/=d)*t + b;
}
/**
* @method easeOutQuad
* @memberOf fabric.util.ease
*/
function easeOutQuad(t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
}
/**
* @method easeInOutQuad
* @memberOf fabric.util.ease
*/
function easeInOutQuad(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
}
/**
* @method easeInCubic
* @memberOf fabric.util.ease
*/
function easeInCubic(t, b, c, d) {
return c*(t/=d)*t*t + b;
}
/**
* @method easeOutCubic
* @memberOf fabric.util.ease
*/
function easeOutCubic(t, b, c, d) {
return c*((t=t/d-1)*t*t + 1) + b;
}
/**
* @method easeInOutCubic
* @memberOf fabric.util.ease
*/
function easeInOutCubic(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
}
/**
* @method easeInQuart
* @memberOf fabric.util.ease
*/
function easeInQuart(t, b, c, d) {
return c*(t/=d)*t*t*t + b;
}
/**
* @method easeOutQuart
* @memberOf fabric.util.ease
*/
function easeOutQuart(t, b, c, d) {
return -c * ((t=t/d-1)*t*t*t - 1) + b;
}
/**
* @method easeInOutQuart
* @memberOf fabric.util.ease
*/
function easeInOutQuart(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
}
/**
* @method easeInQuint
* @memberOf fabric.util.ease
*/
function easeInQuint(t, b, c, d) {
return c*(t/=d)*t*t*t*t + b;
}
/**
* @method easeOutQuint
* @memberOf fabric.util.ease
*/
function easeOutQuint(t, b, c, d) {
return c*((t=t/d-1)*t*t*t*t + 1) + b;
}
/**
* @method easeInOutQuint
* @memberOf fabric.util.ease
*/
function easeInOutQuint(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
}
/**
* @method easeInSine
* @memberOf fabric.util.ease
*/
function easeInSine(t, b, c, d) {
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
}
/**
* @method easeOutSine
* @memberOf fabric.util.ease
*/
function easeOutSine(t, b, c, d) {
return c * Math.sin(t/d * (Math.PI/2)) + b;
}
/**
* @method easeInOutSine
* @memberOf fabric.util.ease
*/
function easeInOutSine(t, b, c, d) {
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
}
/**
* @method easeInExpo
* @memberOf fabric.util.ease
*/
function easeInExpo(t, b, c, d) {
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
}
/**
* @method easeOutExpo
* @memberOf fabric.util.ease
*/
function easeOutExpo(t, b, c, d) {
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
}
/**
* @method easeInOutExpo
* @memberOf fabric.util.ease
*/
function easeInOutExpo(t, b, c, d) {
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
/**
* @method easeInCirc
* @memberOf fabric.util.ease
*/
function easeInCirc(t, b, c, d) {
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
}
/**
* @method easeOutCirc
* @memberOf fabric.util.ease
*/
function easeOutCirc(t, b, c, d) {
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
}
/**
* @method easeInOutCirc
* @memberOf fabric.util.ease
*/
function easeInOutCirc(t, b, c, d) {
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
}
/**
* @method easeInElastic
* @memberOf fabric.util.ease
*/
function easeInElastic(t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var 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;
}
/**
* @method easeOutElastic
* @memberOf fabric.util.ease
*/
function easeOutElastic(t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var 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;
}
/**
* @method easeInOutElastic
* @memberOf fabric.util.ease
*/
function easeInOutElastic(t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var 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;
}
/**
* @method easeInBack
* @memberOf fabric.util.ease
*/
function easeInBack(t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
}
/**
* @method easeOutBack
* @memberOf fabric.util.ease
*/
function easeOutBack(t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
}
/**
* @method easeInOutBack
* @memberOf fabric.util.ease
*/
function easeInOutBack(t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 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;
}
/**
* @method easeInBounce
* @memberOf fabric.util.ease
*/
function easeInBounce(t, b, c, d) {
return c - easeOutBounce (d-t, 0, c, d) + b;
}
/**
* @method easeOutBounce
* @memberOf fabric.util.ease
*/
function easeOutBounce(t, b, c, d) {
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .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;
}
}
/**
* @method easeInOutBounce
* @memberOf fabric.util.ease
*/
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;
}
/** @namespace fabric.util.ease */
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";
/**
* @name fabric
* @namespace
*/
var fabric = global.fabric || (global.fabric = { }),
extend = fabric.util.object.extend,
capitalize = fabric.util.string.capitalize,
clone = fabric.util.object.clone;
var attributesMap = {
'cx': 'left',
'x': 'left',
'cy': 'top',
'y': 'top',
'r': 'radius',
'fill-opacity': 'opacity',
'fill-rule': 'fillRule',
'stroke-width': 'strokeWidth',
'transform': 'transformMatrix',
'text-decoration': 'textDecoration',
'font-size': 'fontSize',
'font-weight': 'fontWeight',
'font-style': 'fontStyle',
'font-family': 'fontFamily'
};
function normalizeAttr(attr) {
// transform attribute names
if (attr in attributesMap) {
return attributesMap[attr];
}
return attr;
}
/**
* Returns an object of attributes' name/value, given element and an array of attribute names;
* Parses parent "g" nodes recursively upwards.
* @static
* @memberOf fabric
* @method parseAttributes
* @param {DOMElement} element Element to parse
* @param {Array} attributes Array of attributes to parse
* @return {Object} object containing parsed attributes' names/values
*/
function parseAttributes(element, attributes) {
if (!element) {
return;
}
var value,
parsed,
parentAttributes = { };
// if there's a parent container (`g` node), parse its attributes recursively upwards
if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) {
parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
}
var ownAttributes = attributes.reduce(function(memo, attr) {
value = element.getAttribute(attr);
parsed = parseFloat(value);
if (value) {
// "normalize" attribute values
if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
value = '';
}
if (attr === 'fill-rule') {
value = (value === 'evenodd') ? 'destination-over' : value;
}
if (attr === 'transform') {
value = fabric.parseTransformAttribute(value);
}
attr = normalizeAttr(attr);
memo[attr] = isNaN(parsed) ? value : parsed;
}
return memo;
}, { });
// add values parsed from style, which take precedence over attributes
// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element)));
return extend(parentAttributes, ownAttributes);
};
/**
* Parses "transform" attribute, returning an array of values
* @static
* @function
* @memberOf fabric
* @method parseTransformAttribute
* @param attributeValue {String} string containing attribute value
* @return {Array} array of 6 elements representing transformation matrix
*/
fabric.parseTransformAttribute = (function() {
function rotateMatrix(matrix, args) {
var angle = args[0];
matrix[0] = Math.cos(angle);
matrix[1] = Math.sin(angle);
matrix[2] = -Math.sin(angle);
matrix[3] = Math.cos(angle);
}
function scaleMatrix(matrix, args) {
var multiplierX = args[0],
multiplierY = (args.length === 2) ? args[1] : args[0];
matrix[0] = multiplierX;
matrix[3] = multiplierY;
}
function skewXMatrix(matrix, args) {
matrix[2] = args[0];
}
function skewYMatrix(matrix, args) {
matrix[1] = args[0];
}
function translateMatrix(matrix, args) {
matrix[4] = args[0];
if (args.length === 2) {
matrix[5] = args[1];
}
}
// identity matrix
var iMatrix = [
1, // a
0, // b
0, // c
1, // d
0, // e
0 // f
],
// == begin transform regexp
number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)',
comma_wsp = '(?:\\s+,?\\s*|,\\s*)',
skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + ')' + comma_wsp + '(' + number + '))?\\s*\\))',
scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
matrix = '(?:(matrix)\\s*\\(\\s*' +
'(' + number + ')' + comma_wsp +
'(' + number + ')' + comma_wsp +
'(' + number + ')' + comma_wsp +
'(' + number + ')' + comma_wsp +
'(' + number + ')' + comma_wsp +
'(' + number + ')' +
'\\s*\\))',
transform = '(?:' +
matrix + '|' +
translate + '|' +
scale + '|' +
rotate + '|' +
skewX + '|' +
skewY +
')',
transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')',
transform_list = '^\\s*(?:' + transforms + '?)\\s*$',
// http://www.w3.org/TR/SVG/coords.html#TransformAttribute
reTransformList = new RegExp(transform_list),
// == end transform regexp
reTransform = new RegExp(transform);
return function(attributeValue) {
// start with identity matrix
var matrix = iMatrix.concat();
// return if no argument was given or
// an argument does not match transform attribute regexp
if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
return matrix;
}
attributeValue.replace(reTransform, function(match) {
var m = new RegExp(transform).exec(match).filter(function (match) {
return (match !== '' && match != null);
}),
operation = m[1],
args = m.slice(2).map(parseFloat);
switch(operation) {
case 'translate':
translateMatrix(matrix, args);
break;
case 'rotate':
rotateMatrix(matrix, args);
break;
case 'scale':
scaleMatrix(matrix, args);
break;
case 'skewX':
skewXMatrix(matrix, args);
break;
case 'skewY':
skewYMatrix(matrix, args);
break;
case 'matrix':
matrix = args;
break;
}
})
return matrix;
}
})();
/**
* Parses "points" attribute, returning an array of values
* @static
* @memberOf fabric
* @method parsePointsAttribute
* @param points {String} points attribute string
* @return {Array} array of points
*/
function parsePointsAttribute(points) {
// points attribute is required and must not be empty
if (!points) return null;
points = points.trim();
var asPairs = points.indexOf(',') > -1;
points = points.split(/\s+/);
var parsedPoints = [ ];
// points could look like "10,20 30,40" or "10 20 30 40"
if (asPairs) {
for (var i = 0, len = points.length; i < len; i++) {
var pair = points[i].split(',');
parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) });
}
}
else {
for (var i = 0, len = points.length; i < len; i+=2) {
parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i+1]) });
}
}
// odd number of points is an error
if (parsedPoints.length % 2 !== 0) {
// return null;
}
return parsedPoints;
};
/**
* Parses "style" attribute, retuning an object with values
* @static
* @memberOf fabric
* @method parseStyleAttribute
* @param {SVGElement} element Element to parse
* @return {Object} Objects with values parsed from style attribute of an element
*/
function parseStyleAttribute(element) {
var oStyle = { },
style = element.getAttribute('style');
if (style) {
if (typeof style == 'string') {
style = style.replace(/;$/, '').split(';').forEach(function (current) {
var attr = current.split(':');
oStyle[normalizeAttr(attr[0].trim().toLowerCase())] = attr[1].trim();
});
} else {
for (var prop in style) {
if (typeof style[prop] !== 'undefined') {
oStyle[normalizeAttr(prop.toLowerCase())] = style[prop];
}
}
}
}
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]));
}
}
}
}
/**
* Transforms an array of svg elements to corresponding fabric.* instances
* @static
* @memberOf fabric
* @method parseElements
* @param {Array} elements Array of elements to parse
* @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
* @param {Object} options Options object
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
*/
function parseElements(elements, callback, options, reviver) {
var instances = 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), options);
}
else {
var obj = klass.fromElement(el, options);
reviver && reviver(el, obj);
instances.splice(index, 0, obj);
checkIfDone();
}
}
catch(e) {
fabric.log(e.message || e);
}
}
else {
checkIfDone();
}
}
};
/**
* Returns CSS rules for a given SVG document
* @static
* @function
* @memberOf fabric
* @method getCSSRules
* @param {SVGDocument} doc SVG document to parse
* @return {Object} CSS rules of this document
*/
function getCSSRules(doc) {
var styles = doc.getElementsByTagName('style'),
allRules = { },
rules;
// very crude parsing of style contents
for (var i = 0, len = styles.length; i < len; i++) {
var styleContents = styles[0].textContent;
// remove comments
styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
rules = rules.map(function(rule) { return rule.trim() });
rules.forEach(function(rule) {
var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
rule = match[1],
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;
}
/**
* Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
* @static
* @function
* @memberOf fabric
* @method parseSVGDocument
* @param {SVGDocument} doc SVG document to parse
* @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document).
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
*/
fabric.parseSVGDocument = (function() {
var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/;
// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
// \d doesn't quite cut it (as we need to match an actual float number)
// matches, e.g.: +14.56e-12, etc.
var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)';
var reViewBoxAttrValue = new RegExp(
'^' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*' +
'$'
);
function hasAncestorWithNodeName(element, nodeName) {
while (element && (element = element.parentNode)) {
if (nodeName.test(element.nodeName)) {
return true;
}
}
return false;
}
return function(doc, callback, reviver) {
if (!doc) return;
var startTime = new Date(),
descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
if (descendants.length === 0) {
// we're likely in node, where "o3-xml" library fails to gEBTN("*")
// https://github.com/ajaxorg/node-o3-xml/issues/21
descendants = doc.selectNodes("//*[name(.)!='svg']");
var arr = [ ];
for (var i = 0, len = descendants.length; i < len; i++) {
arr[i] = descendants[i];
}
descendants = arr;
}
var elements = descendants.filter(function(el) {
return reAllowedSVGTagNames.test(el.tagName) &&
!hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
});
if (!elements || (elements && !elements.length)) return;
var viewBoxAttr = doc.getAttribute('viewBox'),
widthAttr = doc.getAttribute('width'),
heightAttr = doc.getAttribute('height'),
width = null,
height = null,
minX,
minY;
if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
minX = parseInt(viewBoxAttr[1], 10);
minY = parseInt(viewBoxAttr[2], 10);
width = parseInt(viewBoxAttr[3], 10);
height = parseInt(viewBoxAttr[4], 10);
}
// values of width/height attributes overwrite those extracted from viewbox attribute
width = widthAttr ? parseFloat(widthAttr) : width;
height = heightAttr ? parseFloat(heightAttr) : height;
var options = {
width: width,
height: height
};
fabric.gradientDefs = fabric.getGradientDefs(doc);
fabric.cssRules = getCSSRules(doc);
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances) {
fabric.documentParsingTime = new Date() - startTime;
if (callback) {
callback(instances, options);
}
}, clone(options), reviver);
};
})();
/**
* Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
* @property
* @namespace
*/
var svgCache = {
/**
* @method has
* @param {String} name
* @param {Function} callback
*/
has: function (name, callback) {
callback(false);
},
/**
* @method get
* @param {String} url
* @param {Function} callback
*/
get: function (url, callback) {
/* NOOP */
},
/**
* @method set
* @param {String} url
* @param {Object} object
*/
set: function (url, object) {
/* NOOP */
}
};
/**
* Takes url corresponding to an SVG document, and parses it into a set of fabric objects
* @method loadSVGFromURL
* @param {String} url
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
*/
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';
//IE chokes on DOCTYPE
xml.loadXML(r.responseText.replace(//i,''));
}
if (!xml.documentElement) return;
fabric.parseSVGDocument(xml.documentElement, function (results, options) {
svgCache.set(url, {
objects: fabric.util.array.invoke(results, 'toObject'),
options: options
});
callback(results, options);
}, reviver);
}
}
/**
* @method _enlivenCachedObject
*/
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 });
}
/**
* Takes string corresponding to an SVG document, and parses it into a set of fabric objects
* @method loadSVGFromString
* @param {String} string
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
*/
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) {
var doc = new ActiveXObject('Microsoft.XMLDOM');
doc.async = 'false';
//IE chokes on DOCTYPE
doc.loadXML(string.replace(//i,''));
}
fabric.parseSVGDocument(doc.documentElement, function (results, options) {
callback(results, options);
}, reviver);
}
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;
}
extend(fabric, {
parseAttributes: parseAttributes,
parseElements: parseElements,
parseStyleAttribute: parseStyleAttribute,
parsePointsAttribute: parsePointsAttribute,
getCSSRules: getCSSRules,
loadSVGFromURL: loadSVGFromURL,
loadSVGFromString: loadSVGFromString,
createSVGFontFacesMarkup: createSVGFontFacesMarkup
});
})(typeof exports != 'undefined' ? exports : this);
(function() {
function getColorStopFromStyle(el) {
var style = el.getAttribute('style');
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') {
return value;
}
}
}
}
/**
* @class Object
* @memberOf fabric
*/
fabric.Gradient = fabric.util.createClass(/** @scope fabric.Gradient.prototype */ {
initialize: function(options) {
options || (options = { });
this.x1 = options.x1 || 0;
this.y1 = options.y1 || 0;
this.x2 = options.x2 || 0;
this.y2 = options.y2 || 0;
this.colorStops = options.colorStops;
},
toObject: function() {
return {
x1: this.x1,
x2: this.x2,
y1: this.y1,
y2: this.y2,
colorStops: this.colorStops
};
},
toLiveGradient: function(ctx) {
var gradient = ctx.createLinearGradient(
this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2);
for (var position in this.colorStops) {
var colorValue = this.colorStops[position];
gradient.addColorStop(parseFloat(position), colorValue);
}
return gradient;
}
});
fabric.util.object.extend(fabric.Gradient, {
/**
* @method fromElement
* @static
* @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
*/
fromElement: function(el, instance) {
/**
* @example:
*
*
*
*
*
*
* OR
*
*
*
*
*
*
*/
var colorStopEls = el.getElementsByTagName('stop'),
el,
offset,
colorStops = { },
colorStopFromStyle,
coords = {
x1: el.getAttribute('x1') || 0,
y1: el.getAttribute('y1') || 0,
x2: el.getAttribute('x2') || '100%',
y2: el.getAttribute('y2') || 0
};
for (var i = colorStopEls.length; i--; ) {
el = colorStopEls[i];
offset = el.getAttribute('offset');
// convert percents to absolute values
offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
colorStops[offset] = getColorStopFromStyle(el) || el.getAttribute('stop-color');
}
_convertPercentUnitsToValues(instance, coords);
return new fabric.Gradient({
x1: coords.x1,
y1: coords.y1,
x2: coords.x2,
y2: coords.y2,
colorStops: colorStops
});
},
/**
* @method forObject
* @static
*/
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') {
options[prop] = object.width * percents / 100;
}
else if (prop === 'y1' || prop === 'y2') {
options[prop] = object.height * percents / 100;
}
}
// normalize rendering point (should be from top/left corner rather than center of the shape)
if (prop === 'x1' || prop === 'x2') {
options[prop] -= object.width / 2;
}
else if (prop === 'y1' || prop === 'y2') {
options[prop] -= object.height / 2;
}
}
}
/**
* Parses an SVG document, returning all of the gradient declarations found in it
* @static
* @function
* @memberOf fabric
* @method getGradientDefs
* @param {SVGDocument} doc SVG document to parse
* @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
*/
function getGradientDefs(doc) {
var linearGradientEls = doc.getElementsByTagName('linearGradient'),
radialGradientEls = doc.getElementsByTagName('radialGradient'),
el,
gradientDefs = { };
for (var i = linearGradientEls.length; i--; ) {
el = linearGradientEls[i];
gradientDefs[el.getAttribute('id')] = el;
}
for (var i = radialGradientEls.length; i--; ) {
el = radialGradientEls[i];
gradientDefs[el.getAttribute('id')] = el;
}
return gradientDefs;
}
fabric.getGradientDefs = getGradientDefs;
})();
(function(global) {
"use strict";
/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
var fabric = global.fabric || (global.fabric = { });
if (fabric.Point) {
fabric.warn('fabric.Point is already defined');
return;
}
fabric.Point = Point;
/**
* @name Point
* @memberOf fabric
* @constructor
* @param {Number} x
* @param {Number} y
* @return {fabric.Point} thisArg
*/
function Point(x, y) {
if (arguments.length > 0) {
this.init(x, y);
}
}
Point.prototype = /** @scope fabric.Point.prototype */ {
constructor: Point,
/**
* @method init
* @param {Number} x
* @param {Number} y
*/
init: function (x, y) {
this.x = x;
this.y = y;
},
/**
* @method add
* @param {fabric.Point} that
* @return {fabric.Point} new Point instance with added values
*/
add: function (that) {
return new Point(this.x + that.x, this.y + that.y);
},
/**
* @method addEquals
* @param {fabric.Point} that
* @return {fabric.Point} thisArg
*/
addEquals: function (that) {
this.x += that.x;
this.y += that.y;
return this;
},
/**
* @method scalarAdd
* @param {Number} scalar
* @return {fabric.Point} new Point with added value
*/
scalarAdd: function (scalar) {
return new Point(this.x + scalar, this.y + scalar);
},
/**
* @method scalarAddEquals
* @param {Number} scalar
* @param {fabric.Point} thisArg
*/
scalarAddEquals: function (scalar) {
this.x += scalar;
this.y += scalar;
return this;
},
/**
* @method subtract
* @param {fabric.Point} that
* @return {fabric.Point} new Point object with subtracted values
*/
subtract: function (that) {
return new Point(this.x - that.x, this.y - that.y);
},
/**
* @method subtractEquals
* @param {fabric.Point} that
* @return {fabric.Point} thisArg
*/
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);
},
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";
/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
var fabric = global.fabric || (global.fabric = { });
if (fabric.Intersection) {
fabric.warn('fabric.Intersection is already defined');
return;
}
/**
* @class Intersection
* @memberOf fabric
*/
function Intersection(status) {
if (arguments.length > 0) {
this.init(status);
}
}
fabric.Intersection = Intersection;
fabric.Intersection.prototype = /** @scope fabric.Intersection.prototype */ {
/**
* @method init
* @param {String} status
*/
init: function (status) {
this.status = status;
this.points = [];
},
/**
* @method appendPoint
* @param {String} status
*/
appendPoint: function (point) {
this.points.push(point);
},
/**
* @method appendPoints
* @param {String} status
*/
appendPoints: function (points) {
this.points = this.points.concat(points);
}
};
/**
* @static
* @method intersectLineLine
*/
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("No Intersection");
}
}
else {
if (ua_t == 0 || ub_t == 0) {
result = new Intersection("Coincident");
}
else {
result = new Intersection("Parallel");
}
}
return result;
};
/**
* @method intersectLinePolygon
*/
fabric.Intersection.intersectLinePolygon = function(a1,a2,points){
var result = new Intersection("No 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;
};
/**
* @method intersectPolygonPolygon
*/
fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
var result = new Intersection("No 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;
};
/**
* @method intersectPolygonRectangle
*/
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("No 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;
}
/**
* The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
* {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
*
* @class Color
* @memberOf fabric
* @param {String} color (optional) in hex or rgb(a) format
*/
function Color(color) {
if (!color) {
this.setSource([0, 0, 0, 1]);
}
else {
this._tryParsingColor(color);
}
}
fabric.Color = Color;
fabric.Color.prototype = /** @scope fabric.Color.prototype */ {
/**
* @private
* @method _tryParsingColor
*/
_tryParsingColor: function(color) {
var source = Color.sourceFromHex(color);
if (!source) {
source = Color.sourceFromRgb(color);
}
if (source) {
this.setSource(source);
}
},
/**
* Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
* @method getSource
* @return {Array}
*/
getSource: function() {
return this._source;
},
/**
* Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
* @method setSource
* @param {Array} source
*/
setSource: function(source) {
this._source = source;
},
/**
* Returns color represenation in RGB format
* @method toRgb
* @return {String} ex: rgb(0-255,0-255,0-255)
*/
toRgb: function() {
var source = this.getSource();
return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';
},
/**
* Returns color represenation in RGBA format
* @method toRgba
* @return {String} ex: rgba(0-255,0-255,0-255,0-1)
*/
toRgba: function() {
var source = this.getSource();
return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';
},
/**
* Returns color represenation in HEX format
* @method toHex
* @return {String} ex: FF5555
*/
toHex: function() {
var source = this.getSource();
var r = source[0].toString(16);
r = (r.length == 1) ? ('0' + r) : r;
var g = source[1].toString(16);
g = (g.length == 1) ? ('0' + g) : g;
var b = source[2].toString(16);
b = (b.length == 1) ? ('0' + b) : b;
return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
},
/**
* Gets value of alpha channel for this color
* @method getAlpha
* @return {Number} 0-1
*/
getAlpha: function() {
return this.getSource()[3];
},
/**
* Sets value of alpha channel for this color
* @method setAlpha
* @param {Number} 0-1
* @return {fabric.Color} thisArg
*/
setAlpha: function(alpha) {
var source = this.getSource();
source[3] = alpha;
this.setSource(source);
return this;
},
/**
* Transforms color to its grayscale representation
* @method toGrayscale
* @return {fabric.Color} thisArg
*/
toGrayscale: function() {
var source = this.getSource(),
average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),
currentAlpha = source[3];
this.setSource([average, average, average, currentAlpha]);
return this;
},
/**
* Transforms color to its black and white representation
* @method toGrayscale
* @return {fabric.Color} thisArg
*/
toBlackWhite: function(threshold) {
var source = this.getSource(),
average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
currentAlpha = source[3],
threshold = threshold || 127;
average = (Number(average) < Number(threshold)) ? 0 : 255;
this.setSource([average, average, average, currentAlpha]);
return this;
},
/**
* Overlays color with another color
* @method overlayWith
* @param {String|fabric.Color} otherColor
* @return {fabric.Color} thisArg
*/
overlayWith: function(otherColor) {
if (!(otherColor instanceof Color)) {
otherColor = new Color(otherColor);
}
var result = [],
alpha = this.getAlpha(),
otherAlpha = 0.5,
source = this.getSource(),
otherSource = otherColor.getSource();
for (var i = 0; i < 3; i++) {
result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));
}
result[3] = alpha;
this.setSource(result);
return this;
}
};
/**
* Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgb(255, 100, 10, 0.5), rgb(1,1,1))
* @static
* @field
*/
fabric.Color.reRGBa = /^rgba?\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d+(?:\.\d+)?))?\)$/;
/**
* Regex matching color in HEX format (ex: #FF5555, 010155, aff)
* @static
* @field
*/
fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
/**
* Returns new color object, when given a color in RGB format
* @method fromRgb
* @param {String} color ex: rgb(0-255,0-255,0-255)
* @return {fabric.Color}
*/
fabric.Color.fromRgb = function(color) {
return Color.fromSource(Color.sourceFromRgb(color));
};
/**
* Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
* @method sourceFromRgb
* @param {String} color ex: rgb(0-255,0-255,0-255)
* @return {Array} source
*/
fabric.Color.sourceFromRgb = function(color) {
var match = color.match(Color.reRGBa);
if (match) {
return [
parseInt(match[1], 10),
parseInt(match[2], 10),
parseInt(match[3], 10),
match[4] ? parseFloat(match[4]) : 1
];
}
};
/**
* Returns new color object, when given a color in RGBA format
* @static
* @function
* @method fromRgba
* @param {String} color
* @return {fabric.Color}
*/
fabric.Color.fromRgba = Color.fromRgb;
/**
* Returns new color object, when given a color in HEX format
* @static
* @method fromHex
* @return {fabric.Color}
*/
fabric.Color.fromHex = function(color) {
return Color.fromSource(Color.sourceFromHex(color));
};
/**
* Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format
* @static
* @method sourceFromHex
* @param {String} color ex: FF5555
* @return {Array} source
*/
fabric.Color.sourceFromHex = function(color) {
if (color.match(Color.reHex)) {
var value = color.slice(color.indexOf('#') + 1),
isShortNotation = (value.length === 3),
r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2),
g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4),
b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6);
return [
parseInt(r, 16),
parseInt(g, 16),
parseInt(b, 16),
1
];
}
};
/**
* Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
* @static
* @method fromSource
* @return {fabric.Color}
*/
fabric.Color.fromSource = function(source) {
var oColor = new Color();
oColor.setSource(source);
return oColor;
};
})(typeof exports != 'undefined' ? exports : this);
(function (global) {
"use strict";
if (fabric.StaticCanvas) {
fabric.warn('fabric.StaticCanvas is already defined.');
return;
}
// aliases for faster resolution
var extend = fabric.util.object.extend,
getElementOffset = fabric.util.getElementOffset,
removeFromArray = fabric.util.removeFromArray,
removeListener = fabric.util.removeListener,
CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
/**
* @class fabric.StaticCanvas
* @constructor
* @param {HTMLElement | String} el <canvas> element to initialize instance on
* @param {Object} [options] Options object
*/
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, /** @scope fabric.StaticCanvas.prototype */ {
/**
* Background color of canvas instance
* @property
* @type String
*/
backgroundColor: 'rgba(0, 0, 0, 0)',
/**
* Background image of canvas instance
* Should be set via `setBackgroundImage`
* @property
* @type String
*/
backgroundImage: '',
/**
* Opacity of the background image of the canvas instance
* @property
* @type Float
*/
backgroundImageOpacity: 1.0,
/**
* Indicatus whether the background image should be stretched to fit the
* dimensions of the canvas instance.
* @property
* @type Boolean
*/
backgroundImageStretch: true,
/**
* Indicates whether toObject/toDatalessObject should include default values
* @property
* @type Boolean
*/
includeDefaultValues: true,
/**
* Indicates whether objects' state should be saved
* @property
* @type Boolean
*/
stateful: true,
/**
* Indicates whether fabric.Canvas#add should also re-render canvas.
* Disabling this option could give a great performance boost when adding a lot of objects to canvas at once
* (followed by a manual rendering after addition)
*/
renderOnAddition: true,
/**
* Function that determines clipping of entire canvas area
* Being passed context as first argument. See clipping canvas area in https://github.com/kangax/fabric.js/wiki/FAQ
* @property
* @type Function
*/
clipTo: null,
/**
* Indicates whether object controls (borders/corners) are rendered above overlay image
* @property
* @type Boolean
*/
controlsAboveOverlay: false,
/**
* Callback; invoked right before object is about to be scaled/rotated
* @method onBeforeScaleRotate
* @param {fabric.Object} target Object that's about to be scaled/rotated
*/
onBeforeScaleRotate: function (target) {
/* NOOP */
},
/**
* Callback; invoked on every redraw of canvas and is being passed a number indicating current fps
* @method onFpsUpdate
* @param {Number} fps
*/
onFpsUpdate: null,
_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));
}
this.calcOffset();
},
/**
* Calculates canvas element offset relative to the document
* This method is also attached as "resize" event handler of window
* @method calcOffset
* @return {fabric.Canvas} instance
* @chainable
*/
calcOffset: function () {
this._offset = getElementOffset(this.lowerCanvasEl);
return this;
},
/**
* Sets overlay image for this canvas
* @method setOverlayImage
* @param {String} url url of an image to set overlay to
* @param {Function} callback callback to invoke when image is loaded and set as an overlay
* @return {fabric.Canvas} thisArg
* @chainable
*/
setOverlayImage: function (url, callback) { // TODO (kangax): test callback
fabric.util.loadImage(url, function(img) {
this.overlayImage = img;
callback && callback();
}, this);
return this;
},
/**
* Sets background image for this canvas
* @method setBackgroundImage
* @param {String} url url of an image to set background to
* @param {Function} callback callback to invoke when image is loaded and set as background
* @param {Object} options optional options to set for the background image
* @return {fabric.Canvas} thisArg
* @chainable
*/
setBackgroundImage: function (url, callback, options) {
return 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);
},
/**
* @private
* @method _createCanvasElement
* @param {Element} element
*/
_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) {
if (typeof element.getContext === 'undefined' &&
typeof G_vmlCanvasManager !== 'undefined' &&
G_vmlCanvasManager.initElement) {
G_vmlCanvasManager.initElement(element);
}
if (typeof element.getContext === 'undefined') {
throw CANVAS_INIT_ERROR;
}
},
/**
* @method _initOptions
* @param {Object} options
*/
_initOptions: function (options) {
for (var prop in options) {
this[prop] = options[prop];
}
this.width = parseInt(this.lowerCanvasEl.width, 10) || 0;
this.height = parseInt(this.lowerCanvasEl.height, 10) || 0;
if (!this.lowerCanvasEl.style) return;
this.lowerCanvasEl.style.width = this.width + 'px';
this.lowerCanvasEl.style.height = this.height + 'px';
},
/**
* Creates a secondary canvas
* @method _createLowerCanvas
*/
_createLowerCanvas: function (canvasEl) {
this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
this._initCanvasElement(this.lowerCanvasEl);
fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
if (this.interactive) {
this._applyCanvasStyle(this.lowerCanvasEl);
}
this.contextContainer = this.lowerCanvasEl.getContext('2d');
},
/**
* Returns canvas width
* @method getWidth
* @return {Number}
*/
getWidth: function () {
return this.width;
},
/**
* Returns canvas height
* @method getHeight
* @return {Number}
*/
getHeight: function () {
return this.height;
},
/**
* Sets width of this canvas instance
* @method setWidth
* @param {Number} width value to set width to
* @return {fabric.Canvas} instance
* @chainable true
*/
setWidth: function (value) {
return this._setDimension('width', value);
},
/**
* Sets height of this canvas instance
* @method setHeight
* @param {Number} height value to set height to
* @return {fabric.Canvas} instance
* @chainable true
*/
setHeight: function (value) {
return this._setDimension('height', value);
},
/**
* Sets dimensions (width, height) of this canvas instance
* @method setDimensions
* @param {Object} dimensions
* @return {fabric.Canvas} thisArg
* @chainable
*/
setDimensions: function(dimensions) {
for (var prop in dimensions) {
this._setDimension(prop, dimensions[prop]);
}
return this;
},
/**
* Helper for setting width/height
* @private
* @method _setDimensions
* @param {String} prop property (width|height)
* @param {Number} value value to set property to
* @return {fabric.Canvas} instance
* @chainable true
*/
_setDimension: function (prop, value) {
this.lowerCanvasEl[prop] = value;
this.lowerCanvasEl.style[prop] = value + 'px';
if (this.upperCanvasEl) {
this.upperCanvasEl[prop] = value;
this.upperCanvasEl.style[prop] = value + 'px';
}
if (this.wrapperEl) {
this.wrapperEl.style[prop] = value + 'px';
}
this[prop] = value;
this.calcOffset();
this.renderAll();
return this;
},
/**
* Returns <canvas> element corresponding to this instance
* @method getElement
* @return {HTMLCanvasElement}
*/
getElement: function () {
return this.lowerCanvasEl;
},
// placeholder
getActiveObject: function() {
return null;
},
// placeholder
getActiveGroup: function() {
return null;
},
/**
* Given a context, renders an object on that context
* @param ctx {Object} context to render object on
* @param object {Object} object to render
* @private
*/
_draw: function (ctx, object) {
if (!object) return;
if (this.controlsAboveOverlay) {
var hasBorders = object.hasBorders, hasCorners = object.hasCorners;
object.hasBorders = object.hasCorners = false;
object.render(ctx);
object.hasBorders = hasBorders;
object.hasCorners = hasCorners;
}
else {
object.render(ctx);
}
},
/**
* Adds objects to canvas, then renders canvas;
* Objects should be instances of (or inherit from) fabric.Object
* @method add
* @return {fabric.Canvas} thisArg
* @chainable
*/
add: function () {
this._objects.push.apply(this._objects, arguments);
for (var i = arguments.length; i--; ) {
this._initObject(arguments[i]);
}
this.renderOnAddition && this.renderAll();
return this;
},
/**
* @private
* @method _initObject
*/
_initObject: function(obj) {
this.stateful && obj.setupState();
obj.setCoords();
obj.canvas = this;
this.fire('object:added', { target: obj });
obj.fire('added');
},
/**
* Inserts an object to canvas at specified index and renders canvas.
* An object should be an instance of (or inherit from) fabric.Object
* @method insertAt
* @param object {Object} Object to insert
* @param index {Number} index to insert object at
* @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs
* @return {fabric.Canvas} instance
*/
insertAt: function (object, index, nonSplicing) {
if (nonSplicing) {
this._objects[index] = object;
}
else {
this._objects.splice(index, 0, object);
}
this._initObject(object);
this.renderOnAddition && this.renderAll();
return this;
},
/**
* Returns an array of objects this instance has
* @method getObjects
* @return {Array}
*/
getObjects: function () {
return this._objects;
},
/**
* Clears specified context of canvas element
* @method clearContext
* @param context {Object} ctx context to clear
* @return {fabric.Canvas} thisArg
* @chainable
*/
clearContext: function(ctx) {
ctx.clearRect(0, 0, this.width, this.height);
return this;
},
/**
* Returns context of canvas where objects are drawn
* @method getContext
* @return {CanvasRenderingContext2D}
*/
getContext: function () {
return this.contextContainer;
},
/**
* Clears all contexts (background, main, top) of an instance
* @method clear
* @return {fabric.Canvas} thisArg
* @chainable
*/
clear: function () {
this._objects.length = 0;
this.clearContext(this.contextContainer);
if (this.contextTop) {
this.clearContext(this.contextTop);
}
this.renderAll();
return this;
},
/**
* Renders both the top canvas and the secondary container canvas.
* @method renderAll
* @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas
* @return {fabric.Canvas} instance
* @chainable
*/
renderAll: function (allOnTop) {
var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'];
if (this.contextTop) {
this.clearContext(this.contextTop);
}
if (allOnTop === false || (typeof allOnTop === 'undefined')) {
this.clearContext(canvasToDrawOn);
}
var length = this._objects.length,
activeGroup = this.getActiveGroup(),
startTime = new Date();
if (this.clipTo) {
canvasToDrawOn.save();
canvasToDrawOn.beginPath();
this.clipTo(canvasToDrawOn);
canvasToDrawOn.clip();
}
canvasToDrawOn.fillStyle = this.backgroundColor;
canvasToDrawOn.fillRect(0, 0, this.width, this.height);
if (typeof this.backgroundImage == 'object') {
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();
}
if (length) {
for (var i = 0; i < length; ++i) {
if (!activeGroup ||
(activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
this._draw(canvasToDrawOn, this._objects[i]);
}
}
}
if (this.clipTo) {
canvasToDrawOn.restore();
}
// delegate rendering to group selection (if one exists)
if (activeGroup) {
this._draw(this.contextTop, activeGroup);
}
if (this.overlayImage) {
this.contextContainer.drawImage(this.overlayImage, 0, 0);
}
if (this.controlsAboveOverlay) {
this.drawControls(this.contextContainer);
}
if (this.onFpsUpdate) {
var elapsedTime = new Date() - startTime;
this.onFpsUpdate(~~(1000 / elapsedTime));
}
this.fire('after:render');
return this;
},
/**
* Method to render only the top canvas.
* Also used to render the group selection box.
* @method renderTop
* @return {fabric.Canvas} thisArg
* @chainable
*/
renderTop: function () {
this.clearContext(this.contextTop || this.contextContainer);
if (this.overlayImage) {
this.contextContainer.drawImage(this.overlayImage, 0, 0);
}
// we render the top context - last object
if (this.selection && this._groupSelector) {
this._drawSelection();
}
// delegate rendering to group selection if one exists
// used for drawing selection borders/corners
var activeGroup = this.getActiveGroup();
if (activeGroup) {
activeGroup.render(this.contextTop);
}
this.fire('after:render');
return this;
},
/**
* Draws objects' controls (borders/corners)
* @method drawControls
* @param {Object} ctx context to render controls on
*/
drawControls: function(ctx) {
var activeGroup = this.getActiveGroup();
if (activeGroup) {
ctx.save();
fabric.Group.prototype.transform.call(activeGroup, ctx);
activeGroup.drawBorders(ctx).drawCorners(ctx);
ctx.restore();
}
else {
for (var i = 0, len = this._objects.length; i < len; ++i) {
if (!this._objects[i].active) continue;
ctx.save();
fabric.Object.prototype.transform.call(this._objects[i], ctx);
this._objects[i].drawBorders(ctx).drawCorners(ctx);
ctx.restore();
}
}
},
/**
* Exports canvas element to a dataurl image.
* @method toDataURL
* @param {String} format the format of the output image. Either "jpeg" or "png".
* @param {Number} quality quality level (0..1)
* @return {String}
*/
toDataURL: function (format, quality) {
var canvasEl = this.upperCanvasEl || this.lowerCanvasEl;
this.renderAll(true);
var data = (fabric.StaticCanvas.supports('toDataURLWithQuality'))
? canvasEl.toDataURL('image/' + format, quality)
: canvasEl.toDataURL('image/' + format);
this.renderAll();
return data;
},
/**
* Exports canvas element to a dataurl image (allowing to change image size via multiplier).
* @method toDataURLWithMultiplier
* @param {String} format (png|jpeg)
* @param {Number} multiplier
* @param {Number} quality (0..1)
* @return {String}
*/
toDataURLWithMultiplier: function (format, multiplier, quality) {
var origWidth = this.getWidth(),
origHeight = this.getHeight(),
scaledWidth = origWidth * multiplier,
scaledHeight = origHeight * multiplier,
activeObject = this.getActiveObject(),
activeGroup = this.getActiveGroup();
this.setWidth(scaledWidth).setHeight(scaledHeight);
this.contextTop.scale(multiplier, multiplier);
if (activeGroup) {
// not removing group due to complications with restoring it with correct state afterwords
this._tempRemoveBordersCornersFromGroup(activeGroup);
}
else if (activeObject) {
this.deactivateAll();
}
// restoring width, height for `renderAll` to draw
// background properly (while context is scaled)
this.width = origWidth;
this.height = origHeight;
this.renderAll(true);
var dataURL = this.toDataURL(format, quality);
this.contextTop.scale(1 / multiplier, 1 / multiplier);
this.setWidth(origWidth).setHeight(origHeight);
if (activeGroup) {
this._restoreBordersCornersOnGroup(activeGroup);
}
else if (activeObject) {
this.setActiveObject(activeObject);
}
this.renderAll();
return dataURL;
},
_tempRemoveBordersCornersFromGroup: function(group) {
group.origHideCorners = group.hideCorners;
group.origBorderColor = group.borderColor;
group.hideCorners = true;
group.borderColor = 'rgba(0,0,0,0)';
group.forEachObject(function(o) {
o.origBorderColor = o.borderColor;
o.borderColor = 'rgba(0,0,0,0)';
});
},
_restoreBordersCornersOnGroup: function(group) {
group.hideCorners = group.origHideCorners;
group.borderColor = group.origBorderColor;
group.forEachObject(function(o) {
o.borderColor = o.origBorderColor;
delete o.origBorderColor;
});
},
/**
* Returns coordinates of a center of canvas.
* Returned value is an object with top and left properties
* @method getCenter
* @return {Object} object with "top" and "left" number values
*/
getCenter: function () {
return {
top: this.getHeight() / 2,
left: this.getWidth() / 2
};
},
/**
* Centers object horizontally.
* @method centerObjectH
* @param {fabric.Object} object Object to center
* @return {fabric.Canvas} thisArg
*/
centerObjectH: function (object) {
object.set('left', this.getCenter().left);
this.renderAll();
return this;
},
/**
* Centers object vertically.
* @method centerObjectH
* @param {fabric.Object} object Object to center
* @return {fabric.Canvas} thisArg
* @chainable
*/
centerObjectV: function (object) {
object.set('top', this.getCenter().top);
this.renderAll();
return this;
},
/**
* Centers object vertically and horizontally.
* @method centerObject
* @param {fabric.Object} object Object to center
* @return {fabric.Canvas} thisArg
* @chainable
*/
centerObject: function (object) {
return this.centerObjectH(object).centerObjectV(object);
},
/**
* Returs dataless JSON representation of canvas
* @method toDatalessJSON
* @return {String} json string
*/
toDatalessJSON: function () {
return this.toDatalessObject();
},
/**
* Returns object representation of canvas
* @method toObject
* @return {Object}
*/
toObject: function () {
return this._toObjectMethod('toObject');
},
/**
* Returns dataless object representation of canvas
* @method toDatalessObject
* @return {Object}
*/
toDatalessObject: function () {
return this._toObjectMethod('toDatalessObject');
},
/**
* @private
* @method _toObjectMethod
*/
_toObjectMethod: function (methodName) {
var data = {
objects: this._objects.map(function (instance) {
// TODO (kangax): figure out how to clean this up
if (!this.includeDefaultValues) {
var originalValue = instance.includeDefaultValues;
instance.includeDefaultValues = false;
}
var object = instance[methodName]();
if (!this.includeDefaultValues) {
instance.includeDefaultValues = originalValue;
}
return object;
}, this),
background: this.backgroundColor
};
if (this.backgroundImage) {
data.backgroundImage = this.backgroundImage.src;
data.backgroundImageOpacity = this.backgroundImageOpacity;
data.backgroundImageStretch = this.backgroundImageStretch;
}
return data;
},
/**
* Returns SVG representation of canvas
* @function
* @method toSVG
* @return {String}
*/
toSVG: function() {
var markup = [
'',
'',
'');
return markup.join('');
},
/**
* Returns true if canvas contains no objects
* @method isEmpty
* @return {Boolean} true if canvas is empty
*/
isEmpty: function () {
return this._objects.length === 0;
},
/**
* Removes an object from canvas and returns it
* @method remove
* @param object {Object} Object to remove
* @return {Object} removed object
*/
remove: function (object) {
removeFromArray(this._objects, object);
if (this.getActiveObject() === object) {
// removing active object should fire "selection:cleared" events
this.fire('before:selection:cleared', { target: object });
this.discardActiveObject();
this.fire('selection:cleared');
}
this.renderAll();
return object;
},
/**
* Moves an object to the bottom of the stack of drawn objects
* @method sendToBack
* @param object {fabric.Object} Object to send to back
* @return {fabric.Canvas} thisArg
* @chainable
*/
sendToBack: function (object) {
removeFromArray(this._objects, object);
this._objects.unshift(object);
return this.renderAll();
},
/**
* Moves an object to the top of the stack of drawn objects
* @method bringToFront
* @param object {fabric.Object} Object to send
* @return {fabric.Canvas} thisArg
* @chainable
*/
bringToFront: function (object) {
removeFromArray(this._objects, object);
this._objects.push(object);
return this.renderAll();
},
/**
* Moves an object one level down in stack of drawn objects
* @method sendBackwards
* @param object {fabric.Object} Object to send
* @return {fabric.Canvas} thisArg
* @chainable
*/
sendBackwards: function (object) {
var idx = this._objects.indexOf(object),
nextIntersectingIdx = idx;
// if object is not on the bottom of stack
if (idx !== 0) {
// traverse down the stack looking for the nearest intersecting object
for (var i=idx-1; i>=0; --i) {
if (object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i])) {
nextIntersectingIdx = i;
break;
}
}
removeFromArray(this._objects, object);
this._objects.splice(nextIntersectingIdx, 0, object);
}
return this.renderAll();
},
/**
* Moves an object one level up in stack of drawn objects
* @method bringForward
* @param object {fabric.Object} Object to send
* @return {fabric.Canvas} thisArg
* @chainable
*/
bringForward: function (object) {
var objects = this.getObjects(),
idx = objects.indexOf(object),
nextIntersectingIdx = idx;
// if object is not on top of stack (last item in an array)
if (idx !== objects.length-1) {
// traverse up the stack looking for the nearest intersecting object
for (var i = idx + 1, l = this._objects.length; i < l; ++i) {
if (object.intersectsWithObject(objects[i]) || object.isContainedWithinObject(this._objects[i])) {
nextIntersectingIdx = i;
break;
}
}
removeFromArray(objects, object);
objects.splice(nextIntersectingIdx, 0, object);
}
this.renderAll();
},
/**
* Returns object at specified index
* @method item
* @param {Number} index
* @return {fabric.Object}
*/
item: function (index) {
return this.getObjects()[index];
},
/**
* Returns number representation of an instance complexity
* @method complexity
* @return {Number} complexity
*/
complexity: function () {
return this.getObjects().reduce(function (memo, current) {
memo += current.complexity ? current.complexity() : 0;
return memo;
}, 0);
},
/**
* Iterates over all objects, invoking callback for each one of them
* @method forEachObject
* @return {fabric.Canvas} thisArg
*/
forEachObject: function(callback, context) {
var objects = this.getObjects(),
i = objects.length;
while (i--) {
callback.call(context, objects[i], i, objects);
}
return this;
},
/**
* Clears a canvas element and removes all event handlers.
* @method dispose
* @return {fabric.Canvas} thisArg
* @chainable
*/
dispose: function () {
this.clear();
if (this.interactive) {
removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
removeListener(fabric.window, 'resize', this._onResize);
}
return this;
},
/**
* @private
* @method _resizeImageToFit
* @param {HTMLImageElement} imgEl
*/
_resizeImageToFit: function (imgEl) {
var imageWidth = imgEl.width || imgEl.offsetWidth,
widthScaleFactor = this.getWidth() / imageWidth;
// scale image down so that it has original dimensions when printed in large resolution
if (imageWidth) {
imgEl.width = imageWidth * widthScaleFactor;
}
}
});
/**
* Returns a string representation of an instance
* @method toString
* @return {String} string representation of an instance
*/
fabric.StaticCanvas.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet
return '#';
};
extend(fabric.StaticCanvas, /** @scope fabric.StaticCanvas */ {
/**
* @static
* @property EMPTY_JSON
* @type String
*/
EMPTY_JSON: '{"objects": [], "background": "white"}',
/**
* Takes