From d4cb7e88681fa4e5e73609b9b597ff76241f7fbe Mon Sep 17 00:00:00 2001 From: Dan Alstadt Date: Thu, 20 Nov 2014 14:27:34 -0600 Subject: [PATCH] add html2canvas-container classname to iframe --- dist/html2canvas.js | 530 +- dist/html2canvas.min.js | 4 +- dist/html2canvas.svg.js | 17691 ---------------------------------- dist/html2canvas.svg.min.js | 7 +- src/core.js | 1 + 5 files changed, 5 insertions(+), 18228 deletions(-) diff --git a/dist/html2canvas.js b/dist/html2canvas.js index abd33f8..4040f59 100644 --- a/dist/html2canvas.js +++ b/dist/html2canvas.js @@ -37,535 +37,6 @@ if (typeof(Object.create) !== "function" || typeof(document.createElement("canva return; } -/*! https://mths.be/punycode v1.3.1 by @mathias */ -;(function(root) { - - /** Detect free variables */ - var freeExports = typeof exports == 'object' && exports && - !exports.nodeType && exports; - var freeModule = typeof module == 'object' && module && - !module.nodeType && module; - var freeGlobal = typeof global == 'object' && global; - if ( - freeGlobal.global === freeGlobal || - freeGlobal.window === freeGlobal || - freeGlobal.self === freeGlobal - ) { - root = freeGlobal; - } - - /** - * The `punycode` object. - * @name punycode - * @type Object - */ - var punycode, - - /** Highest positive signed 32-bit float value */ - maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 - - /** Bootstring parameters */ - base = 36, - tMin = 1, - tMax = 26, - skew = 38, - damp = 700, - initialBias = 72, - initialN = 128, // 0x80 - delimiter = '-', // '\x2D' - - /** Regular expressions */ - regexPunycode = /^xn--/, - regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars - regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators - - /** Error messages */ - errors = { - 'overflow': 'Overflow: input needs wider integers to process', - 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', - 'invalid-input': 'Invalid input' - }, - - /** Convenience shortcuts */ - baseMinusTMin = base - tMin, - floor = Math.floor, - stringFromCharCode = String.fromCharCode, - - /** Temporary variable */ - key; - - /*--------------------------------------------------------------------------*/ - - /** - * A generic error utility function. - * @private - * @param {String} type The error type. - * @returns {Error} Throws a `RangeError` with the applicable error message. - */ - function error(type) { - throw RangeError(errors[type]); - } - - /** - * A generic `Array#map` utility function. - * @private - * @param {Array} array The array to iterate over. - * @param {Function} callback The function that gets called for every array - * item. - * @returns {Array} A new array of values returned by the callback function. - */ - function map(array, fn) { - var length = array.length; - var result = []; - while (length--) { - result[length] = fn(array[length]); - } - return result; - } - - /** - * A simple `Array#map`-like wrapper to work with domain name strings or email - * addresses. - * @private - * @param {String} domain The domain name or email address. - * @param {Function} callback The function that gets called for every - * character. - * @returns {Array} A new string of characters returned by the callback - * function. - */ - function mapDomain(string, fn) { - var parts = string.split('@'); - var result = ''; - if (parts.length > 1) { - // In email addresses, only the domain name should be punycoded. Leave - // the local part (i.e. everything up to `@`) intact. - result = parts[0] + '@'; - string = parts[1]; - } - var labels = string.split(regexSeparators); - var encoded = map(labels, fn).join('.'); - return result + encoded; - } - - /** - * Creates an array containing the numeric code points of each Unicode - * character in the string. While JavaScript uses UCS-2 internally, - * this function will convert a pair of surrogate halves (each of which - * UCS-2 exposes as separate characters) into a single code point, - * matching UTF-16. - * @see `punycode.ucs2.encode` - * @see - * @memberOf punycode.ucs2 - * @name decode - * @param {String} string The Unicode input string (UCS-2). - * @returns {Array} The new array of code points. - */ - function ucs2decode(string) { - var output = [], - counter = 0, - length = string.length, - value, - extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } - - /** - * Creates a string based on an array of numeric code points. - * @see `punycode.ucs2.decode` - * @memberOf punycode.ucs2 - * @name encode - * @param {Array} codePoints The array of numeric code points. - * @returns {String} The new Unicode string (UCS-2). - */ - function ucs2encode(array) { - return map(array, function(value) { - var output = ''; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - return output; - }).join(''); - } - - /** - * Converts a basic code point into a digit/integer. - * @see `digitToBasic()` - * @private - * @param {Number} codePoint The basic numeric code point value. - * @returns {Number} The numeric value of a basic code point (for use in - * representing integers) in the range `0` to `base - 1`, or `base` if - * the code point does not represent a value. - */ - function basicToDigit(codePoint) { - if (codePoint - 48 < 10) { - return codePoint - 22; - } - if (codePoint - 65 < 26) { - return codePoint - 65; - } - if (codePoint - 97 < 26) { - return codePoint - 97; - } - return base; - } - - /** - * Converts a digit/integer into a basic code point. - * @see `basicToDigit()` - * @private - * @param {Number} digit The numeric value of a basic code point. - * @returns {Number} The basic code point whose value (when used for - * representing integers) is `digit`, which needs to be in the range - * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is - * used; else, the lowercase form is used. The behavior is undefined - * if `flag` is non-zero and `digit` has no uppercase form. - */ - function digitToBasic(digit, flag) { - // 0..25 map to ASCII a..z or A..Z - // 26..35 map to ASCII 0..9 - return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); - } - - /** - * Bias adaptation function as per section 3.4 of RFC 3492. - * http://tools.ietf.org/html/rfc3492#section-3.4 - * @private - */ - function adapt(delta, numPoints, firstTime) { - var k = 0; - delta = firstTime ? floor(delta / damp) : delta >> 1; - delta += floor(delta / numPoints); - for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { - delta = floor(delta / baseMinusTMin); - } - return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); - } - - /** - * Converts a Punycode string of ASCII-only symbols to a string of Unicode - * symbols. - * @memberOf punycode - * @param {String} input The Punycode string of ASCII-only symbols. - * @returns {String} The resulting string of Unicode symbols. - */ - function decode(input) { - // Don't use UCS-2 - var output = [], - inputLength = input.length, - out, - i = 0, - n = initialN, - bias = initialBias, - basic, - j, - index, - oldi, - w, - k, - digit, - t, - /** Cached calculation results */ - baseMinusT; - - // Handle the basic code points: let `basic` be the number of input code - // points before the last delimiter, or `0` if there is none, then copy - // the first basic code points to the output. - - basic = input.lastIndexOf(delimiter); - if (basic < 0) { - basic = 0; - } - - for (j = 0; j < basic; ++j) { - // if it's not a basic code point - if (input.charCodeAt(j) >= 0x80) { - error('not-basic'); - } - output.push(input.charCodeAt(j)); - } - - // Main decoding loop: start just after the last delimiter if any basic code - // points were copied; start at the beginning otherwise. - - for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { - - // `index` is the index of the next character to be consumed. - // Decode a generalized variable-length integer into `delta`, - // which gets added to `i`. The overflow checking is easier - // if we increase `i` as we go, then subtract off its starting - // value at the end to obtain `delta`. - for (oldi = i, w = 1, k = base; /* no condition */; k += base) { - - if (index >= inputLength) { - error('invalid-input'); - } - - digit = basicToDigit(input.charCodeAt(index++)); - - if (digit >= base || digit > floor((maxInt - i) / w)) { - error('overflow'); - } - - i += digit * w; - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - - if (digit < t) { - break; - } - - baseMinusT = base - t; - if (w > floor(maxInt / baseMinusT)) { - error('overflow'); - } - - w *= baseMinusT; - - } - - out = output.length + 1; - bias = adapt(i - oldi, out, oldi == 0); - - // `i` was supposed to wrap around from `out` to `0`, - // incrementing `n` each time, so we'll fix that now: - if (floor(i / out) > maxInt - n) { - error('overflow'); - } - - n += floor(i / out); - i %= out; - - // Insert `n` at position `i` of the output - output.splice(i++, 0, n); - - } - - return ucs2encode(output); - } - - /** - * Converts a string of Unicode symbols (e.g. a domain name label) to a - * Punycode string of ASCII-only symbols. - * @memberOf punycode - * @param {String} input The string of Unicode symbols. - * @returns {String} The resulting Punycode string of ASCII-only symbols. - */ - function encode(input) { - var n, - delta, - handledCPCount, - basicLength, - bias, - j, - m, - q, - k, - t, - currentValue, - output = [], - /** `inputLength` will hold the number of code points in `input`. */ - inputLength, - /** Cached calculation results */ - handledCPCountPlusOne, - baseMinusT, - qMinusT; - - // Convert the input in UCS-2 to Unicode - input = ucs2decode(input); - - // Cache the length - inputLength = input.length; - - // Initialize the state - n = initialN; - delta = 0; - bias = initialBias; - - // Handle the basic code points - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue < 0x80) { - output.push(stringFromCharCode(currentValue)); - } - } - - handledCPCount = basicLength = output.length; - - // `handledCPCount` is the number of code points that have been handled; - // `basicLength` is the number of basic code points. - - // Finish the basic string - if it is not empty - with a delimiter - if (basicLength) { - output.push(delimiter); - } - - // Main encoding loop: - while (handledCPCount < inputLength) { - - // All non-basic code points < n have been handled already. Find the next - // larger one: - for (m = maxInt, j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue >= n && currentValue < m) { - m = currentValue; - } - } - - // Increase `delta` enough to advance the decoder's state to , - // but guard against overflow - handledCPCountPlusOne = handledCPCount + 1; - if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { - error('overflow'); - } - - delta += (m - n) * handledCPCountPlusOne; - n = m; - - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - - if (currentValue < n && ++delta > maxInt) { - error('overflow'); - } - - if (currentValue == n) { - // Represent delta as a generalized variable-length integer - for (q = delta, k = base; /* no condition */; k += base) { - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - if (q < t) { - break; - } - qMinusT = q - t; - baseMinusT = base - t; - output.push( - stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) - ); - q = floor(qMinusT / baseMinusT); - } - - output.push(stringFromCharCode(digitToBasic(q, 0))); - bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); - delta = 0; - ++handledCPCount; - } - } - - ++delta; - ++n; - - } - return output.join(''); - } - - /** - * Converts a Punycode string representing a domain name or an email address - * to Unicode. Only the Punycoded parts of the input will be converted, i.e. - * it doesn't matter if you call it on a string that has already been - * converted to Unicode. - * @memberOf punycode - * @param {String} input The Punycoded domain name or email address to - * convert to Unicode. - * @returns {String} The Unicode representation of the given Punycode - * string. - */ - function toUnicode(input) { - return mapDomain(input, function(string) { - return regexPunycode.test(string) - ? decode(string.slice(4).toLowerCase()) - : string; - }); - } - - /** - * Converts a Unicode string representing a domain name or an email address to - * Punycode. Only the non-ASCII parts of the domain name will be converted, - * i.e. it doesn't matter if you call it with a domain that's already in - * ASCII. - * @memberOf punycode - * @param {String} input The domain name or email address to convert, as a - * Unicode string. - * @returns {String} The Punycode representation of the given domain name or - * email address. - */ - function toASCII(input) { - return mapDomain(input, function(string) { - return regexNonASCII.test(string) - ? 'xn--' + encode(string) - : string; - }); - } - - /*--------------------------------------------------------------------------*/ - - /** Define the public API */ - punycode = { - /** - * A string representing the current Punycode.js version number. - * @memberOf punycode - * @type String - */ - 'version': '1.3.1', - /** - * An object of methods to convert from JavaScript's internal character - * representation (UCS-2) to Unicode code points, and back. - * @see - * @memberOf punycode - * @type Object - */ - 'ucs2': { - 'decode': ucs2decode, - 'encode': ucs2encode - }, - 'decode': decode, - 'encode': encode, - 'toASCII': toASCII, - 'toUnicode': toUnicode - }; - - /** Expose `punycode` */ - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define('punycode', function() { - return punycode; - }); - } else if (freeExports && freeModule) { - if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+ - freeModule.exports = punycode; - } else { // in Narwhal or RingoJS v0.7.0- - for (key in punycode) { - punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); - } - } - } else { // in Rhino or a web browser - root.punycode = punycode; - } - -}(this)); - var html2canvasNodeAttribute = "data-html2canvas-node"; var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone"; var html2canvasCanvasCloneIndex = 0; @@ -692,6 +163,7 @@ function createWindowClone(ownerDocument, containerDocument, width, height, opti var documentElement = ownerDocument.documentElement.cloneNode(true), container = containerDocument.createElement("iframe"); + container.className = "html2canvas-container"; container.style.visibility = "hidden"; container.style.position = "absolute"; container.style.left = container.style.top = "-10000px"; diff --git a/dist/html2canvas.min.js b/dist/html2canvas.min.js index cac6e26..e1598de 100644 --- a/dist/html2canvas.min.js +++ b/dist/html2canvas.min.js @@ -4,5 +4,5 @@ Released under MIT License */ -(function(a,b,c,d,e,f,g){function h(a,b,c,d){return o(a,a,c,d,b).then(function(e){E("Document cloned");var f="["+Qb+"='true']";a.querySelector(f).removeAttribute(Qb);var g=e.contentWindow,h=g.document.querySelector(f),j=Promise.resolve("function"==typeof b.onclone?b.onclone(g.document):!0);return j.then(function(){return i(h,e,b,c,d)})})}function i(a,c,d,e,f){var g=c.contentWindow,h=new Gb(g.document),i=new C(d,h),n=M(a),o="view"===d.type?e:l(g.document),p="view"===d.type?f:m(g.document),q=new Ob(o,p,i,d,b),r=new O(a,q,h,i,d);return r.ready.then(function(){E("Finished rendering");var b;return b="view"===d.type?k(q.canvas,{width:q.canvas.width,height:q.canvas.height,top:0,left:0,x:0,y:0}):a===g.document.body||a===g.document.documentElement||null!=d.canvas?q.canvas:k(q.canvas,{width:null!=d.width?d.width:n.width,height:null!=d.height?d.height:n.height,top:n.top,left:n.left,x:g.pageXOffset,y:g.pageYOffset}),j(c,d),b})}function j(a,b){b.removeContainer&&(a.parentNode.removeChild(a),E("Cleaned up container"))}function k(a,c){var d=b.createElement("canvas"),e=Math.min(a.width-1,Math.max(0,c.left)),f=Math.min(a.width,Math.max(1,c.left+c.width)),g=Math.min(a.height-1,Math.max(0,c.top)),h=Math.min(a.height,Math.max(1,c.top+c.height));return d.width=c.width,d.height=c.height,E("Cropping canvas at:","left:",c.left,"top:",c.top,"width:",f-e,"height:",h-g),E("Resulting crop with width",c.width,"and height",c.height," with x",e,"and y",g),d.getContext("2d").drawImage(a,e,g,f-e,h-g,c.x,c.y,f-e,h-g),d}function l(a){return Math.max(Math.max(a.body.scrollWidth,a.documentElement.scrollWidth),Math.max(a.body.offsetWidth,a.documentElement.offsetWidth),Math.max(a.body.clientWidth,a.documentElement.clientWidth))}function m(a){return Math.max(Math.max(a.body.scrollHeight,a.documentElement.scrollHeight),Math.max(a.body.offsetHeight,a.documentElement.offsetHeight),Math.max(a.body.clientHeight,a.documentElement.clientHeight))}function n(){return"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"}function o(a,b,c,d,e){r(a);var f=a.documentElement.cloneNode(!0),g=b.createElement("iframe");return g.style.visibility="hidden",g.style.position="absolute",g.style.left=g.style.top="-10000px",g.width=c,g.height=d,g.scrolling="no",b.body.appendChild(g),new Promise(function(b){var c=g.contentWindow.document;g.contentWindow.onload=g.onload=function(){var f=setInterval(function(){c.body.childNodes.length>0&&(s(a,c),clearInterval(f),"view"===e.type&&g.contentWindow.scrollTo(d,h),b(g))},50)};var d=a.defaultView.pageXOffset,h=a.defaultView.pageYOffset;c.open(),c.write(""),(d!==a.defaultView.pageXOffset||h!==a.defaultView.pageYOffset)&&a.defaultView.scrollTo(d,h),c.replaceChild(e.javascriptEnabled===!0?c.adoptNode(f):t(c.adoptNode(f)),c.documentElement),c.close()})}function p(b,c,d,e,f,g){return new xb(b,c,a.document).then(q(b)).then(function(a){return o(a,d,e,f,g)})}function q(a){return function(c){var d,e=new DOMParser;try{d=e.parseFromString(c,"text/html")}catch(f){E("DOMParser not supported, falling back to createHTMLDocument"),d=b.implementation.createHTMLDocument("");try{d.open(),d.write(c),d.close()}catch(g){E("createHTMLDocument write not supported, falling back to document.body.innerHTML"),d.body.innerHTML=c}}var h=d.querySelector("base");if(!h||!h.href.host){var i=d.createElement("base");i.href=a,d.head.insertBefore(i,d.head.firstChild)}return d}}function r(a){[].slice.call(a.querySelectorAll("canvas"),0).forEach(function(a){a.setAttribute(Rb,"canvas-"+Sb++)})}function s(a,b){[].slice.call(a.querySelectorAll("["+Rb+"]"),0).forEach(function(a){try{var c=b.querySelector("["+Rb+'="'+a.getAttribute(Rb)+'"]');c&&(c.width=a.width,c.height=a.height,c.getContext("2d").putImageData(a.getContext("2d").getImageData(0,0,a.width,a.height),0,0))}catch(d){E("Unable to copy canvas content from",a,d)}a.removeAttribute(Rb)})}function t(a){return[].slice.call(a.childNodes,0).filter(u).forEach(function(b){"SCRIPT"===b.tagName?a.removeChild(b):t(b)}),a}function u(a){return a.nodeType===Node.ELEMENT_NODE}function v(a){var c=b.createElement("a");return c.href=a,c.href=c.href,c}function w(a){if(this.src=a,E("DummyImageContainer for",a),!this.promise||!this.image){E("Initiating DummyImageContainer"),w.prototype.image=new Image;var b=this.image;w.prototype.promise=new Promise(function(a,c){b.onload=a,b.onerror=c,b.src=n(),b.complete===!0&&a(b)})}}function x(a,c){var d,e,f=b.createElement("div"),g=b.createElement("img"),h=b.createElement("span"),i="Hidden Text";f.style.visibility="hidden",f.style.fontFamily=a,f.style.fontSize=c,f.style.margin=0,f.style.padding=0,b.body.appendChild(f),g.src=n(),g.width=1,g.height=1,g.style.margin=0,g.style.padding=0,g.style.verticalAlign="baseline",h.style.fontFamily=a,h.style.fontSize=c,h.style.margin=0,h.style.padding=0,h.appendChild(b.createTextNode(i)),f.appendChild(h),f.appendChild(g),d=g.offsetTop-h.offsetTop+1,f.removeChild(h),f.appendChild(b.createTextNode(i)),f.style.lineHeight="normal",g.style.verticalAlign="super",e=g.offsetTop-f.offsetTop+1,b.body.removeChild(f),this.baseline=d,this.lineWidth=1,this.middle=e}function y(){this.data={}}function z(a,b,c){this.image=null,this.src=a;var d=this,e=M(a);this.promise=(b?new Promise(function(b){"about:blank"===a.contentWindow.document.URL||null==a.contentWindow.document.documentElement?a.contentWindow.onload=a.onload=function(){b(a)}:b(a)}):this.proxyLoad(c.proxy,e,c)).then(function(a){return html2canvas(a.contentWindow.document.documentElement,{type:"view",width:a.width,height:a.height,proxy:c.proxy,javascriptEnabled:c.javascriptEnabled,removeContainer:c.removeContainer,allowTaint:c.allowTaint,imageTimeout:c.imageTimeout/2})}).then(function(a){return d.image=a})}function A(a){this.src=a.value,this.colorStops=[],this.type=null,this.x0=.5,this.y0=.5,this.x1=.5,this.y1=.5,this.promise=Promise.resolve(!0)}function B(a,b){this.src=a,this.image=new Image;var c=this;this.tainted=null,this.promise=new Promise(function(d,e){c.image.onload=d,c.image.onerror=e,b&&(c.image.crossOrigin="anonymous"),c.image.src=a,c.image.complete===!0&&d(c.image)})}function C(b,c){this.link=null,this.options=b,this.support=c,this.origin=this.getOrigin(a.location.href)}function D(a){A.apply(this,arguments),this.type=this.TYPES.LINEAR;var b=null===a.args[0].match(this.stepRegExp);b?a.args[0].split(" ").reverse().forEach(function(a){switch(a){case"left":this.x0=0,this.x1=1;break;case"top":this.y0=0,this.y1=1;break;case"right":this.x0=1,this.x1=0;break;case"bottom":this.y0=1,this.y1=0;break;case"to":var b=this.y0,c=this.x0;this.y0=this.y1,this.x0=this.x1,this.x1=c,this.y1=b}},this):(this.y0=0,this.y1=1),this.colorStops=a.args.slice(b?1:0).map(function(a){var b=a.match(this.stepRegExp);return{color:b[1],stop:"%"===b[3]?b[2]/100:null}},this),null===this.colorStops[0].stop&&(this.colorStops[0].stop=0),null===this.colorStops[this.colorStops.length-1].stop&&(this.colorStops[this.colorStops.length-1].stop=1),this.colorStops.forEach(function(a,b){null===a.stop&&this.colorStops.slice(b).some(function(c,d){return null!==c.stop?(a.stop=(c.stop-this.colorStops[b-1].stop)/(d+1)+this.colorStops[b-1].stop,!0):!1},this)},this)}function E(){a.html2canvas.logging&&a.console&&a.console.log&&Function.prototype.bind.call(a.console.log,a.console).apply(a.console,[Date.now()-a.html2canvas.start+"ms","html2canvas:"].concat([].slice.call(arguments,0)))}function F(a,b){this.node=a,this.parent=b,this.stack=null,this.bounds=null,this.borders=null,this.clip=[],this.backgroundClip=[],this.offsetBounds=null,this.visible=null,this.computedStyles=null,this.styles={},this.backgroundImages=null,this.transformData=null,this.transformMatrix=null,this.isPseudoElement=!1,this.opacity=null}function G(a){var b=a.options[a.selectedIndex||0];return b?b.text||"":""}function H(a){return a&&"matrix"===a[1]?a[2].split(",").map(function(a){return parseFloat(a.trim())}):void 0}function I(a){return-1!==a.toString().indexOf("%")}function J(a){var b,c,d,e,f,g,h,i=" \r\n ",j=[],k=0,l=0,m=function(){b&&('"'===c.substr(0,1)&&(c=c.substr(1,c.length-2)),c&&h.push(c),"-"===b.substr(0,1)&&(e=b.indexOf("-",1)+1)>0&&(d=b.substr(0,e),b=b.substr(e)),j.push({prefix:d,method:b.toLowerCase(),value:f,args:h,image:null})),h=[],b=d=c=f=""};return h=[],b=d=c=f="",a.split("").forEach(function(a){if(!(0===k&&i.indexOf(a)>-1)){switch(a){case'"':g?g===a&&(g=null):g=a;break;case"(":if(g)break;if(0===k)return k=1,void(f+=a);l++;break;case")":if(g)break;if(1===k){if(0===l)return k=0,f+=a,void m();l--}break;case",":if(g)break;if(0===k)return void m();if(1===k&&0===l&&!b.match(/^url$/i))return h.push(c),c="",void(f+=a)}f+=a,0===k?b+=a:c+=a}}),m(),j}function K(a){return a.replace("px","")}function L(a){return parseFloat(a)}function M(a){if(a.getBoundingClientRect){var b=a.getBoundingClientRect(),c=null==a.offsetWidth?b.width:a.offsetWidth;return{top:b.top,bottom:b.bottom||b.top+b.height,right:b.left+c,left:b.left,width:c,height:null==a.offsetHeight?b.height:a.offsetHeight}}return{}}function N(a){var b=a.offsetParent?N(a.offsetParent):{top:0,left:0};return{top:a.offsetTop+b.top,bottom:a.offsetTop+a.offsetHeight+b.top,right:a.offsetLeft+b.left+a.offsetWidth,left:a.offsetLeft+b.left,width:a.offsetWidth,height:a.offsetHeight}}function O(a,b,c,d,e){E("Starting NodeParser"),this.renderer=b,this.options=e,this.range=null,this.support=c,this.renderQueue=[],this.stack=new Fb(!0,1,a.ownerDocument,null);var f=new F(a,null);if(a===a.ownerDocument.documentElement){var g=new F(this.renderer.isTransparent(f.css("backgroundColor"))?a.ownerDocument.body:a.ownerDocument.documentElement,null);b.rectangle(0,0,b.width,b.height,g.css("backgroundColor"))}f.visibile=f.isElementVisible(),this.createPseudoHideStyles(a.ownerDocument),this.disableAnimations(a.ownerDocument),this.nodes=sb([f].concat(this.getChildren(f)).filter(function(a){return a.visible=a.isElementVisible()}).map(this.getPseudoElements,this)),this.fontMetrics=new y,E("Fetched nodes, total:",this.nodes.length),E("Calculate overflow clips"),this.calculateOverflowClips(),E("Start fetching images"),this.images=d.fetch(this.nodes.filter(jb)),this.ready=this.images.ready.then(ob(function(){return E("Images loaded, starting parsing"),E("Creating stacking contexts"),this.createStackingContexts(),E("Sorting stacking contexts"),this.sortStackingContexts(this.stack),this.parse(this.stack),E("Render queue created with "+this.renderQueue.length+" items"),new Promise(ob(function(a){e.async?"function"==typeof e.async?e.async.call(this,this.renderQueue,a):this.renderQueue.length>0?(this.renderIndex=0,this.asyncRenderer(this.renderQueue,a)):a():(this.renderQueue.forEach(this.paint,this),a())},this))},this))}function P(a){return a.parent&&a.parent.clip.length}function Q(a){return a.replace(/(\-[a-z])/g,function(a){return a.toUpperCase().replace("-","")})}function R(){}function S(a,b,c,d){var e=4*((Math.sqrt(2)-1)/3),f=c*e,g=d*e,h=a+c,i=b+d;return{topLeft:U({x:a,y:i},{x:a,y:i-g},{x:h-f,y:b},{x:h,y:b}),topRight:U({x:a,y:b},{x:a+f,y:b},{x:h,y:i-g},{x:h,y:i}),bottomRight:U({x:h,y:b},{x:h,y:b+g},{x:a+f,y:i},{x:a,y:i}),bottomLeft:U({x:h,y:i},{x:h-f,y:i},{x:a,y:b+g},{x:a,y:b})}}function T(a,b,c){var d=a.left,e=a.top,f=a.width,g=a.height,h=b[0][0],i=b[0][1],j=b[1][0],k=b[1][1],l=b[2][0],m=b[2][1],n=b[3][0],o=b[3][1],p=f-j,q=g-m,r=f-l,s=g-o;return{topLeftOuter:S(d,e,h,i).topLeft.subdivide(.5),topLeftInner:S(d+c[3].width,e+c[0].width,Math.max(0,h-c[3].width),Math.max(0,i-c[0].width)).topLeft.subdivide(.5),topRightOuter:S(d+p,e,j,k).topRight.subdivide(.5),topRightInner:S(d+Math.min(p,f+c[3].width),e+c[0].width,p>f+c[3].width?0:j-c[3].width,k-c[0].width).topRight.subdivide(.5),bottomRightOuter:S(d+r,e+q,l,m).bottomRight.subdivide(.5),bottomRightInner:S(d+Math.min(r,f-c[3].width),e+Math.min(q,g+c[0].width),Math.max(0,l-c[1].width),m-c[2].width).bottomRight.subdivide(.5),bottomLeftOuter:S(d,e+s,n,o).bottomLeft.subdivide(.5),bottomLeftInner:S(d+c[3].width,e+s,Math.max(0,n-c[3].width),o-c[2].width).bottomLeft.subdivide(.5)}}function U(a,b,c,d){var e=function(a,b,c){return{x:a.x+(b.x-a.x)*c,y:a.y+(b.y-a.y)*c}};return{start:a,startControl:b,endControl:c,end:d,subdivide:function(f){var g=e(a,b,f),h=e(b,c,f),i=e(c,d,f),j=e(g,h,f),k=e(h,i,f),l=e(j,k,f);return[U(a,g,j,l),U(l,k,i,d)]},curveTo:function(a){a.push(["bezierCurve",b.x,b.y,c.x,c.y,d.x,d.y])},curveToReversed:function(d){d.push(["bezierCurve",c.x,c.y,b.x,b.y,a.x,a.y])}}}function V(a,b,c,d,e,f,g){var h=[];return b[0]>0||b[1]>0?(h.push(["line",d[1].start.x,d[1].start.y]),d[1].curveTo(h)):h.push(["line",a.c1[0],a.c1[1]]),c[0]>0||c[1]>0?(h.push(["line",f[0].start.x,f[0].start.y]),f[0].curveTo(h),h.push(["line",g[0].end.x,g[0].end.y]),g[0].curveToReversed(h)):(h.push(["line",a.c2[0],a.c2[1]]),h.push(["line",a.c3[0],a.c3[1]])),b[0]>0||b[1]>0?(h.push(["line",e[1].end.x,e[1].end.y]),e[1].curveToReversed(h)):h.push(["line",a.c4[0],a.c4[1]]),h}function W(a,b,c,d,e,f,g){b[0]>0||b[1]>0?(a.push(["line",d[0].start.x,d[0].start.y]),d[0].curveTo(a),d[1].curveTo(a)):a.push(["line",f,g]),(c[0]>0||c[1]>0)&&a.push(["line",e[0].start.x,e[0].start.y])}function X(a){return a.cssInt("zIndex")<0}function Y(a){return a.cssInt("zIndex")>0}function Z(a){return 0===a.cssInt("zIndex")}function $(a){return-1!==["inline","inline-block","inline-table"].indexOf(a.css("display"))}function _(a){return a instanceof Fb}function ab(a){return a.node.data.trim().length>0}function bb(a){return/^(normal|none|0px)$/.test(a.parent.css("letterSpacing"))}function cb(a){return["TopLeft","TopRight","BottomRight","BottomLeft"].map(function(b){var c=a.css("border"+b+"Radius"),d=c.split(" ");return d.length<=1&&(d[1]=d[0]),d.map(pb)})}function db(a){return a.nodeType===Node.TEXT_NODE||a.nodeType===Node.ELEMENT_NODE}function eb(a){var b=a.css("position"),c=-1!==["absolute","relative","fixed"].indexOf(b)?a.css("zIndex"):"auto";return"auto"!==c}function fb(a){return"static"!==a.css("position")}function gb(a){return"none"!==a.css("float")}function hb(a){return-1!==["inline-block","inline-table"].indexOf(a.css("display"))}function ib(a){var b=this;return function(){return!a.apply(b,arguments)}}function jb(a){return a.node.nodeType===Node.ELEMENT_NODE}function kb(a){return a.isPseudoElement===!0}function lb(a){return a.node.nodeType===Node.TEXT_NODE}function mb(a){return function(b,c){return b.cssInt("zIndex")+a.indexOf(b)/a.length-(c.cssInt("zIndex")+a.indexOf(c)/a.length)}}function nb(a){return a.getOpacity()<1}function ob(a,b){return function(){return a.apply(b,arguments)}}function pb(a){return parseInt(a,10)}function qb(a){return a.width}function rb(a){return a.node.nodeType!==Node.ELEMENT_NODE||-1===["SCRIPT","HEAD","TITLE","OBJECT","BR","OPTION"].indexOf(a.node.nodeName)}function sb(a){return[].concat.apply([],a)}function tb(a){var b=a.substr(0,1);return b===a.substr(a.length-1)&&b.match(/'|"/)?a.substr(1,a.length-2):a}function ub(b){for(var c,d=[],e=0,f=!1;b.length;)vb(b[e])===f?(c=b.splice(0,e),c.length&&d.push(a.html2canvas.punycode.ucs2.encode(c)),f=!f,e=0):e++,e>=b.length&&(c=b.splice(0,e),c.length&&d.push(a.html2canvas.punycode.ucs2.encode(c)));return d}function vb(a){return-1!==[32,13,10,9,45].indexOf(a)}function wb(a){return/[^\u0000-\u00ff]/.test(a)}function xb(a,b,c){var d=Ab(Ub),e=Bb(b,a,d);return Ub?Nb(e):zb(c,e,d).then(function(a){return Ib(a.content)})}function yb(a,b,c){var d=Ab(Vb),e=Bb(b,a,d);return Vb?Promise.resolve(e):zb(c,e,d).then(function(a){return"data:"+a.type+";base64,"+a.content})}function zb(b,c,d){return new Promise(function(e,f){var g=b.createElement("script"),h=function(){delete a.html2canvas.proxy[d],b.body.removeChild(g)};a.html2canvas.proxy[d]=function(a){h(),e(a)},g.src=c,g.onerror=function(a){h(),f(a)},b.body.appendChild(g)})}function Ab(a){return a?"":"html2canvas_"+Date.now()+"_"+ ++Tb+"_"+Math.round(1e5*Math.random())}function Bb(a,b,c){return a+"?url="+encodeURIComponent(b)+(c.length?"&callback=html2canvas.proxy."+c:"")}function Cb(a,c){var d=(b.createElement("script"),b.createElement("a"));d.href=a,a=d.href,this.src=a,this.image=new Image;var e=this;this.promise=new Promise(function(d,f){e.image.crossOrigin="Anonymous",e.image.onload=d,e.image.onerror=f,new yb(a,c,b).then(function(a){e.image.src=a})["catch"](f)})}function Db(a,b,c){F.call(this,a,b),this.isPseudoElement=!0,this.before=":before"===c}function Eb(a,b,c,d,e){this.width=a,this.height=b,this.images=c,this.options=d,this.document=e}function Fb(a,b,c,d){F.call(this,c,d),this.ownStacking=a,this.contexts=[],this.children=[],this.opacity=(this.parent?this.parent.stack.opacity:1)*b}function Gb(a){this.rangeBounds=this.testRangeBounds(a),this.cors=this.testCORS(),this.svg=this.testSVG()}function Hb(a){this.src=a,this.image=null;var b=this;this.promise=this.hasFabric().then(function(){return b.isInline(a)?Promise.resolve(b.inlineFormatting(a)):Nb(a)}).then(function(a){return new Promise(function(c){html2canvas.fabric.loadSVGFromString(a,b.createCanvas.call(b,c))})})}function Ib(a){var b,c,d,e,f,g,h,i,j="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",k=a.length,l="";for(b=0;k>b;b+=4)c=j.indexOf(a[b]),d=j.indexOf(a[b+1]),e=j.indexOf(a[b+2]),f=j.indexOf(a[b+3]),g=c<<2|d>>4,h=(15&d)<<4|e>>2,i=(3&e)<<6|f,l+=64===e?String.fromCharCode(g):64===f||-1===f?String.fromCharCode(g,h):String.fromCharCode(g,h,i);return l}function Jb(a,b){this.src=a,this.image=null;var c=this;this.promise=b?new Promise(function(b,d){c.image=new Image,c.image.onload=b,c.image.onerror=d,c.image.src="data:image/svg+xml,"+(new XMLSerializer).serializeToString(a),c.image.complete===!0&&b(c.image)}):this.hasFabric().then(function(){return new Promise(function(b){html2canvas.fabric.parseSVGDocument(a,c.createCanvas.call(c,b))})})}function Kb(a,b){F.call(this,a,b)}function Lb(a,b,c){return a.length>0?b+c.toUpperCase():void 0}function Mb(a){A.apply(this,arguments),this.type="linear"===a.args[0]?this.TYPES.LINEAR:this.TYPES.RADIAL}function Nb(a){return new Promise(function(b,c){var d=new XMLHttpRequest;d.open("GET",a),d.onload=function(){200===d.status?b(d.responseText):c(new Error(d.statusText))},d.onerror=function(){c(new Error("Network Error"))},d.send()})}function Ob(a,b){Eb.apply(this,arguments),this.canvas=this.options.canvas||this.document.createElement("canvas"),this.options.canvas||(this.canvas.width=a,this.canvas.height=b),this.ctx=this.canvas.getContext("2d"),this.options.background&&this.rectangle(0,0,a,b,this.options.background),this.taintCtx=this.document.createElement("canvas").getContext("2d"),this.ctx.textBaseline="bottom",this.variables={},E("Initialized CanvasRenderer with size",a,"x",b)}function Pb(a){return a.length>0}if(!function(){var c,d,f,g;!function(){var a={},b={};c=function(b,c,d){a[b]={deps:c,callback:d}},g=f=d=function(c){function e(a){if("."!==a.charAt(0))return a;for(var b=a.split("/"),d=c.split("/").slice(0,-1),e=0,f=b.length;f>e;e++){var g=b[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(g._eak_seen=a,b[c])return b[c];if(b[c]={},!a[c])throw new Error("Could not find module "+c);for(var f,h=a[c],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)k.push("exports"===i[l]?f={}:d(e(i[l])));var n=j.apply(this,k);return b[c]=f||n}}(),c("promise/all",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to all.");return new b(function(b,c){function d(a){return function(b){f(a,b)}}function f(a,c){h[a]=c,0===--i&&b(h)}var g,h=[],i=a.length;0===i&&b([]);for(var j=0;j1&&(d=c[0]+"@",a=c[1]);var e=a.split(H),f=g(e,b).join(".");return d+f}function i(a){for(var b,c,d=[],e=0,f=a.length;f>e;)b=a.charCodeAt(e++),b>=55296&&56319>=b&&f>e?(c=a.charCodeAt(e++),56320==(64512&c)?d.push(((1023&b)<<10)+(1023&c)+65536):(d.push(b),e--)):d.push(b);return d}function j(a){return g(a,function(a){var b="";return a>65535&&(a-=65536,b+=L(a>>>10&1023|55296),a=56320|1023&a),b+=L(a)}).join("")}function k(a){return 10>a-48?a-22:26>a-65?a-65:26>a-97?a-97:x}function l(a,b){return a+22+75*(26>a)-((0!=b)<<5)}function m(a,b,c){var d=0;for(a=c?K(a/B):a>>1,a+=K(a/b);a>J*z>>1;d+=x)a=K(a/J);return K(d+(J+1)*a/(a+A))}function n(a){var c,d,e,f,g,h,i,l,n,o,p=[],q=a.length,r=0,s=D,t=C;for(d=a.lastIndexOf(E),0>d&&(d=0),e=0;d>e;++e)a.charCodeAt(e)>=128&&b("not-basic"),p.push(a.charCodeAt(e));for(f=d>0?d+1:0;q>f;){for(g=r,h=1,i=x;f>=q&&b("invalid-input"),l=k(a.charCodeAt(f++)),(l>=x||l>K((w-r)/h))&&b("overflow"),r+=l*h,n=t>=i?y:i>=t+z?z:i-t,!(n>l);i+=x)o=x-n,h>K(w/o)&&b("overflow"),h*=o;c=p.length+1,t=m(r-g,c,0==g),K(r/c)>w-s&&b("overflow"),s+=K(r/c),r%=c,p.splice(r++,0,s)}return j(p)}function o(a){var c,d,e,f,g,h,j,k,n,o,p,q,r,s,t,u=[];for(a=i(a),q=a.length,c=D,d=0,g=C,h=0;q>h;++h)p=a[h],128>p&&u.push(L(p));for(e=f=u.length,f&&u.push(E);q>e;){for(j=w,h=0;q>h;++h)p=a[h],p>=c&&j>p&&(j=p);for(r=e+1,j-c>K((w-d)/r)&&b("overflow"),d+=(j-c)*r,c=j,h=0;q>h;++h)if(p=a[h],c>p&&++d>w&&b("overflow"),p==c){for(k=d,n=x;o=g>=n?y:n>=g+z?z:n-g,!(o>k);n+=x)t=k-o,s=x-o,u.push(L(l(o+t%s,0))),k=K(t/s);u.push(L(l(k,0))),g=m(d,r,e==f),d=0,++e}++d,++c}return u.join("")}function p(a){return h(a,function(a){return F.test(a)?n(a.slice(4).toLowerCase()):a})}function q(a){return h(a,function(a){return G.test(a)?"xn--"+o(a):a})}var r="object"==typeof d&&d&&!d.nodeType&&d,s="object"==typeof c&&c&&!c.nodeType&&c,t="object"==typeof e&&e;(t.global===t||t.window===t||t.self===t)&&(a=t);var u,v,w=2147483647,x=36,y=1,z=26,A=38,B=700,C=72,D=128,E="-",F=/^xn--/,G=/[^\x20-\x7E]/,H=/[\x2E\u3002\uFF0E\uFF61]/g,I={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},J=x-y,K=Math.floor,L=String.fromCharCode;if(u={version:"1.3.1",ucs2:{decode:i,encode:j},decode:n,encode:o,toASCII:q,toUnicode:p},"function"==typeof f&&"object"==typeof f.amd&&f.amd)f("punycode",function(){return u});else if(r&&s)if(c.exports==r)s.exports=u;else for(v in u)u.hasOwnProperty(v)&&(r[v]=u[v]);else a.punycode=u}(this);var Qb="data-html2canvas-node",Rb="data-html2canvas-canvas-clone",Sb=0;a.html2canvas=function(c,d){if(d=d||{},d.logging&&(a.html2canvas.logging=!0,a.html2canvas.start=Date.now()),d.async="undefined"==typeof d.async?!0:d.async,d.allowTaint="undefined"==typeof d.allowTaint?!1:d.allowTaint,d.removeContainer="undefined"==typeof d.removeContainer?!0:d.removeContainer,d.javascriptEnabled="undefined"==typeof d.javascriptEnabled?!1:d.javascriptEnabled,d.imageTimeout="undefined"==typeof d.imageTimeout?1e4:d.imageTimeout,"string"==typeof c)return"string"!=typeof d.proxy?Promise.reject("Proxy must be used when rendering url"):p(v(c),d.proxy,b,a.innerWidth,a.innerHeight,d).then(function(b){return i(b.contentWindow.document.documentElement,b,d,a.innerWidth,a.innerHeight)});var e=(c===g?[b.documentElement]:c.length?c:[c])[0];return e.setAttribute(Qb,"true"),h(e.ownerDocument,d,e.ownerDocument.defaultView.innerWidth,e.ownerDocument.defaultView.innerHeight).then(function(a){return"function"==typeof d.onrendered&&(E("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas"),d.onrendered(a)),a})},a.html2canvas.punycode=this.punycode,a.html2canvas.proxy={},y.prototype.getMetrics=function(a,b){return this.data[a+"-"+b]===g&&(this.data[a+"-"+b]=new x(a,b)),this.data[a+"-"+b]},z.prototype.proxyLoad=function(a,b,c){var d=this.src;return p(d.src,a,d.ownerDocument,b.width,b.height,c)},A.prototype.TYPES={LINEAR:1,RADIAL:2},C.prototype.findImages=function(a){var b=[];return a.reduce(function(a,b){switch(b.node.nodeName){case"IMG":return a.concat([{args:[b.node.src],method:"url"}]);case"svg":case"IFRAME":return a.concat([{args:[b.node],method:b.node.nodeName}])}return a},[]).forEach(this.addImage(b,this.loadImage),this),b},C.prototype.findBackgroundImage=function(a,b){return b.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(a,this.loadImage),this),a},C.prototype.addImage=function(a,b){return function(c){c.args.forEach(function(d){this.imageExists(a,d)||(a.splice(0,0,b.call(this,c)),E("Added image #"+a.length,"string"==typeof d?d.substring(0,100):d))},this)}},C.prototype.hasImageBackground=function(a){return"none"!==a.method},C.prototype.loadImage=function(a){if("url"===a.method){var b=a.args[0];return!this.isSVG(b)||this.support.svg||this.options.allowTaint?b.match(/data:image\/.*;base64,/i)?new B(b.replace(/url\(['"]{0,}|['"]{0,}\)$/gi,""),!1):this.isSameOrigin(b)||this.options.allowTaint===!0||this.isSVG(b)?new B(b,!1):this.support.cors&&!this.options.allowTaint&&this.options.useCORS?new B(b,!0):this.options.proxy?new Cb(b,this.options.proxy):new w(b):new Hb(b)}return"linear-gradient"===a.method?new D(a):"gradient"===a.method?new Mb(a):"svg"===a.method?new Jb(a.args[0],this.support.svg):"IFRAME"===a.method?new z(a.args[0],this.isSameOrigin(a.args[0].src),this.options):new w(a)},C.prototype.isSVG=function(a){return"svg"===a.substring(a.length-3).toLowerCase()||Hb.prototype.isInline(a)},C.prototype.imageExists=function(a,b){return a.some(function(a){return a.src===b})},C.prototype.isSameOrigin=function(a){return this.getOrigin(a)===this.origin},C.prototype.getOrigin=function(a){var c=this.link||(this.link=b.createElement("a"));return c.href=a,c.href=c.href,c.protocol+c.hostname+c.port},C.prototype.getPromise=function(a){return this.timeout(a,this.options.imageTimeout)["catch"](function(){var b=new w(a.src);return b.promise.then(function(b){a.image=b})})},C.prototype.get=function(a){var b=null;return this.images.some(function(c){return(b=c).src===a})?b:null},C.prototype.fetch=function(a){return this.images=a.reduce(ob(this.findBackgroundImage,this),this.findImages(a)),this.images.forEach(function(a,b){a.promise.then(function(){E("Succesfully loaded image #"+(b+1),a)},function(c){E("Failed loading image #"+(b+1),a,c)})}),this.ready=Promise.all(this.images.map(this.getPromise,this)),E("Finished searching images"),this},C.prototype.timeout=function(a,b){var c;return Promise.race([a.promise,new Promise(function(d,e){c=setTimeout(function(){E("Timed out loading image",a),e(a)},b)})]).then(function(a){return clearTimeout(c),a})},D.prototype=Object.create(A.prototype),D.prototype.stepRegExp=/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/,F.prototype.cloneTo=function(a){a.visible=this.visible,a.borders=this.borders,a.bounds=this.bounds,a.clip=this.clip,a.backgroundClip=this.backgroundClip,a.computedStyles=this.computedStyles,a.styles=this.styles,a.backgroundImages=this.backgroundImages,a.opacity=this.opacity},F.prototype.getOpacity=function(){return null===this.opacity?this.opacity=this.cssFloat("opacity"):this.opacity},F.prototype.assignStack=function(a){this.stack=a,a.children.push(this)},F.prototype.isElementVisible=function(){return this.node.nodeType===Node.TEXT_NODE?this.parent.visible:"none"!==this.css("display")&&"hidden"!==this.css("visibility")&&!this.node.hasAttribute("data-html2canvas-ignore")&&("INPUT"!==this.node.nodeName||"hidden"!==this.node.getAttribute("type"))},F.prototype.css=function(a){return this.computedStyles||(this.computedStyles=this.isPseudoElement?this.parent.computedStyle(this.before?":before":":after"):this.computedStyle(null)),this.styles[a]||(this.styles[a]=this.computedStyles[a])},F.prototype.prefixedCss=function(a){var b=["webkit","moz","ms","o"],c=this.css(a); -return c===g&&b.some(function(b){return c=this.css(b+a.substr(0,1).toUpperCase()+a.substr(1)),c!==g},this),c===g?null:c},F.prototype.computedStyle=function(a){return this.node.ownerDocument.defaultView.getComputedStyle(this.node,a)},F.prototype.cssInt=function(a){var b=parseInt(this.css(a),10);return isNaN(b)?0:b},F.prototype.cssFloat=function(a){var b=parseFloat(this.css(a));return isNaN(b)?0:b},F.prototype.fontWeight=function(){var a=this.css("fontWeight");switch(parseInt(a,10)){case 401:a="bold";break;case 400:a="normal"}return a},F.prototype.parseClip=function(){var a=this.css("clip").match(this.CLIP);return a?{top:parseInt(a[1],10),right:parseInt(a[2],10),bottom:parseInt(a[3],10),left:parseInt(a[4],10)}:null},F.prototype.parseBackgroundImages=function(){return this.backgroundImages||(this.backgroundImages=J(this.css("backgroundImage")))},F.prototype.cssList=function(a,b){var c=(this.css(a)||"").split(",");return c=c[b||0]||c[0]||"auto",c=c.trim().split(" "),1===c.length&&(c=[c[0],c[0]]),c},F.prototype.parseBackgroundSize=function(a,b,c){var d,e,f=this.cssList("backgroundSize",c);if(I(f[0]))d=a.width*parseFloat(f[0])/100;else{if(/contain|cover/.test(f[0])){var g=a.width/a.height,h=b.width/b.height;return h>g^"contain"===f[0]?{width:a.height*h,height:a.height}:{width:a.width,height:a.width/h}}d=parseInt(f[0],10)}return e="auto"===f[0]&&"auto"===f[1]?b.height:"auto"===f[1]?d/b.width*b.height:I(f[1])?a.height*parseFloat(f[1])/100:parseInt(f[1],10),"auto"===f[0]&&(d=e/b.height*b.width),{width:d,height:e}},F.prototype.parseBackgroundPosition=function(a,b,c,d){var e,f,g=this.cssList("backgroundPosition",c);return e=I(g[0])?(a.width-(d||b).width)*(parseFloat(g[0])/100):parseInt(g[0],10),f="auto"===g[1]?e/b.width*b.height:I(g[1])?(a.height-(d||b).height)*parseFloat(g[1])/100:parseInt(g[1],10),"auto"===g[0]&&(e=f/b.height*b.width),{left:e,top:f}},F.prototype.parseBackgroundRepeat=function(a){return this.cssList("backgroundRepeat",a)[0]},F.prototype.parseTextShadows=function(){var a=this.css("textShadow"),b=[];if(a&&"none"!==a)for(var c=a.match(this.TEXT_SHADOW_PROPERTY),d=0;c&&dDate.now()?this.asyncRenderer(a,b,c):setTimeout(ob(function(){this.asyncRenderer(a,b)},this),0)},O.prototype.createPseudoHideStyles=function(a){this.createStyles(a,"."+Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE+':before { content: "" !important; display: none !important; }.'+Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER+':after { content: "" !important; display: none !important; }')},O.prototype.disableAnimations=function(a){this.createStyles(a,"* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; -webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}")},O.prototype.createStyles=function(a,b){var c=a.createElement("style");c.innerHTML=b,a.body.appendChild(c)},O.prototype.getPseudoElements=function(a){var b=[[a]];if(a.node.nodeType===Node.ELEMENT_NODE){var c=this.getPseudoElement(a,":before"),d=this.getPseudoElement(a,":after");c&&b.push(c),d&&b.push(d)}return sb(b)},O.prototype.getPseudoElement=function(a,c){var d=a.computedStyle(c);if(!d||!d.content||"none"===d.content||"-moz-alt-content"===d.content||"none"===d.display)return null;for(var e=tb(d.content),f="url"===e.substr(0,3),g=b.createElement(f?"img":"html2canvaspseudoelement"),h=new Db(g,a,c),i=d.length-1;i>=0;i--){var j=Q(d.item(i));g.style[j]=d[j]}if(g.className=Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE+" "+Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER,f)return g.src=J(e)[0].args[0],[h];var k=b.createTextNode(e);return g.appendChild(k),[h,new Kb(k,h)]},O.prototype.getChildren=function(a){return sb([].filter.call(a.node.childNodes,db).map(function(b){var c=[b.nodeType===Node.TEXT_NODE?new Kb(b,a):new F(b,a)].filter(rb);return b.nodeType===Node.ELEMENT_NODE&&c.length&&"TEXTAREA"!==b.tagName?c[0].isElementVisible()?c.concat(this.getChildren(c[0])):[]:c},this))},O.prototype.newStackingContext=function(a,b){var c=new Fb(b,a.getOpacity(),a.node,a.parent);a.cloneTo(c);var d=b?c.getParentStack(this):c.parent.stack;d.contexts.push(c),a.stack=c},O.prototype.createStackingContexts=function(){this.nodes.forEach(function(a){jb(a)&&(this.isRootElement(a)||nb(a)||eb(a)||this.isBodyWithTransparentRoot(a)||a.hasTransform())?this.newStackingContext(a,!0):jb(a)&&(fb(a)&&Z(a)||hb(a)||gb(a))?this.newStackingContext(a,!1):a.assignStack(a.parent.stack)},this)},O.prototype.isBodyWithTransparentRoot=function(a){return"BODY"===a.node.nodeName&&this.renderer.isTransparent(a.parent.css("backgroundColor"))},O.prototype.isRootElement=function(a){return null===a.parent},O.prototype.sortStackingContexts=function(a){a.contexts.sort(mb(a.contexts.slice(0))),a.contexts.forEach(this.sortStackingContexts,this)},O.prototype.parseTextBounds=function(a){return function(b,c,d){if("none"!==a.parent.css("textDecoration").substr(0,4)||0!==b.trim().length){if(this.support.rangeBounds&&!a.parent.hasTransform()){var e=d.slice(0,c).join("").length;return this.getRangeBounds(a.node,e,b.length)}if(a.node&&"string"==typeof a.node.data){var f=a.node.splitText(b.length),g=this.getWrapperBounds(a.node,a.parent.hasTransform());return a.node=f,g}}else(!this.support.rangeBounds||a.parent.hasTransform())&&(a.node=a.node.splitText(b.length));return{}}},O.prototype.getWrapperBounds=function(a,b){var c=a.ownerDocument.createElement("html2canvaswrapper"),d=a.parentNode,e=a.cloneNode(!0);c.appendChild(a.cloneNode(!0)),d.replaceChild(c,a);var f=b?N(c):M(c);return d.replaceChild(e,c),f},O.prototype.getRangeBounds=function(a,b,c){var d=this.range||(this.range=a.ownerDocument.createRange());return d.setStart(a,b),d.setEnd(a,b+c),d.getBoundingClientRect()},O.prototype.parse=function(a){var b=a.contexts.filter(X),c=a.children.filter(jb),d=c.filter(ib(gb)),e=d.filter(ib(fb)).filter(ib($)),f=c.filter(ib(fb)).filter(gb),g=d.filter(ib(fb)).filter($),h=a.contexts.concat(d.filter(fb)).filter(Z),i=a.children.filter(lb).filter(ab),j=a.contexts.filter(Y);b.concat(e).concat(f).concat(g).concat(h).concat(i).concat(j).forEach(function(a){this.renderQueue.push(a),_(a)&&(this.parse(a),this.renderQueue.push(new R))},this)},O.prototype.paint=function(a){try{a instanceof R?this.renderer.ctx.restore():lb(a)?(kb(a.parent)&&a.parent.appendToDOM(),this.paintText(a),kb(a.parent)&&a.parent.cleanDOM()):this.paintNode(a)}catch(b){E(b)}},O.prototype.paintNode=function(a){_(a)&&(this.renderer.setOpacity(a.opacity),this.renderer.ctx.save(),a.hasTransform()&&this.renderer.setTransform(a.parseTransform()));var b=a.parseBounds();this.renderer.clip(a.backgroundClip,function(){this.renderer.renderBackground(a,b,a.borders.borders.map(qb))},this),this.renderer.clip(a.clip,function(){this.renderer.renderBorders(a.borders.borders)},this),this.renderer.clip(a.backgroundClip,function(){switch(a.node.nodeName){case"svg":case"IFRAME":var c=this.images.get(a.node);c?this.renderer.renderImage(a,b,a.borders,c):E("Error loading <"+a.node.nodeName+">",a.node);break;case"IMG":var d=this.images.get(a.node.src);d?this.renderer.renderImage(a,b,a.borders,d):E("Error loading ",a.node.src);break;case"CANVAS":this.renderer.renderImage(a,b,a.borders,{image:a.node});break;case"SELECT":case"INPUT":case"TEXTAREA":this.paintFormValue(a)}},this)},O.prototype.paintFormValue=function(a){if(a.getValue().length>0){var b=a.node.ownerDocument,c=b.createElement("html2canvaswrapper"),d=["lineHeight","textAlign","fontFamily","fontWeight","fontSize","color","paddingLeft","paddingTop","paddingRight","paddingBottom","width","height","borderLeftStyle","borderTopStyle","borderLeftWidth","borderTopWidth","boxSizing","whiteSpace","wordWrap"];d.forEach(function(b){try{c.style[b]=a.css(b)}catch(d){E("html2canvas: Parse: Exception caught in renderFormValue: "+d.message)}});var e=a.parseBounds();c.style.position="fixed",c.style.left=e.left+"px",c.style.top=e.top+"px",c.textContent=a.getValue(),b.body.appendChild(c),this.paintText(new Kb(c.firstChild,a)),b.body.removeChild(c)}},O.prototype.paintText=function(b){b.applyTextTransform();var c=a.html2canvas.punycode.ucs2.decode(b.node.data),d=this.options.letterRendering&&!bb(b)||wb(b.node.data)?c.map(function(b){return a.html2canvas.punycode.ucs2.encode([b])}):ub(c),e=b.parent.fontWeight(),f=b.parent.css("fontSize"),g=b.parent.css("fontFamily"),h=b.parent.parseTextShadows();this.renderer.font(b.parent.css("color"),b.parent.css("fontStyle"),b.parent.css("fontVariant"),e,f,g),h.length?this.renderer.fontShadow(h[0].color,h[0].offsetX,h[0].offsetY,h[0].blur):this.renderer.clearShadow(),this.renderer.clip(b.parent.clip,function(){d.map(this.parseTextBounds(b),this).forEach(function(a,c){a&&(this.renderer.text(d[c],a.left,a.bottom),this.renderTextDecoration(b.parent,a,this.fontMetrics.getMetrics(g,f)))},this)},this)},O.prototype.renderTextDecoration=function(a,b,c){switch(a.css("textDecoration").split(" ")[0]){case"underline":this.renderer.rectangle(b.left,Math.round(b.top+c.baseline+c.lineWidth),b.width,1,a.css("color"));break;case"overline":this.renderer.rectangle(b.left,Math.round(b.top),b.width,1,a.css("color"));break;case"line-through":this.renderer.rectangle(b.left,Math.ceil(b.top+c.middle+c.lineWidth),b.width,1,a.css("color"))}},O.prototype.parseBorders=function(a){var b=a.parseBounds(),c=cb(a),d=["Top","Right","Bottom","Left"].map(function(b){return{width:a.cssInt("border"+b+"Width"),color:a.css("border"+b+"Color"),args:null}}),e=T(b,c,d);return{clip:this.parseBackgroundClip(a,e,d,c,b),borders:d.map(function(a,f){if(a.width>0){var g=b.left,h=b.top,i=b.width,j=b.height-d[2].width;switch(f){case 0:j=d[0].width,a.args=V({c1:[g,h],c2:[g+i,h],c3:[g+i-d[1].width,h+j],c4:[g+d[3].width,h+j]},c[0],c[1],e.topLeftOuter,e.topLeftInner,e.topRightOuter,e.topRightInner);break;case 1:g=b.left+b.width-d[1].width,i=d[1].width,a.args=V({c1:[g+i,h],c2:[g+i,h+j+d[2].width],c3:[g,h+j],c4:[g,h+d[0].width]},c[1],c[2],e.topRightOuter,e.topRightInner,e.bottomRightOuter,e.bottomRightInner);break;case 2:h=h+b.height-d[2].width,j=d[2].width,a.args=V({c1:[g+i,h+j],c2:[g,h+j],c3:[g+d[3].width,h],c4:[g+i-d[3].width,h]},c[2],c[3],e.bottomRightOuter,e.bottomRightInner,e.bottomLeftOuter,e.bottomLeftInner);break;case 3:i=d[3].width,a.args=V({c1:[g,h+j+d[2].width],c2:[g,h],c3:[g+i,h+d[0].width],c4:[g+i,h+j]},c[3],c[0],e.bottomLeftOuter,e.bottomLeftInner,e.topLeftOuter,e.topLeftInner)}}return a})}},O.prototype.parseBackgroundClip=function(a,b,c,d,e){var f=a.css("backgroundClip"),g=[];switch(f){case"content-box":case"padding-box":W(g,d[0],d[1],b.topLeftInner,b.topRightInner,e.left+c[3].width,e.top+c[0].width),W(g,d[1],d[2],b.topRightInner,b.bottomRightInner,e.left+e.width-c[1].width,e.top+c[0].width),W(g,d[2],d[3],b.bottomRightInner,b.bottomLeftInner,e.left+e.width-c[1].width,e.top+e.height-c[2].width),W(g,d[3],d[0],b.bottomLeftInner,b.topLeftInner,e.left+c[3].width,e.top+e.height-c[2].width);break;default:W(g,d[0],d[1],b.topLeftOuter,b.topRightOuter,e.left,e.top),W(g,d[1],d[2],b.topRightOuter,b.bottomRightOuter,e.left+e.width,e.top),W(g,d[2],d[3],b.bottomRightOuter,b.bottomLeftOuter,e.left+e.width,e.top+e.height),W(g,d[3],d[0],b.bottomLeftOuter,b.topLeftOuter,e.left,e.top+e.height)}return g};var Tb=0,Ub="withCredentials"in new XMLHttpRequest,Vb="crossOrigin"in new Image;Db.prototype.cloneTo=function(a){Db.prototype.cloneTo.call(this,a),a.isPseudoElement=!0,a.before=this.before},Db.prototype=Object.create(F.prototype),Db.prototype.appendToDOM=function(){this.before?this.parent.node.insertBefore(this.node,this.parent.node.firstChild):this.parent.node.appendChild(this.node),this.parent.node.className+=" "+this.getHideClass()},Db.prototype.cleanDOM=function(){this.node.parentNode.removeChild(this.node),this.parent.node.className=this.parent.node.className.replace(this.getHideClass(),"")},Db.prototype.getHideClass=function(){return this["PSEUDO_HIDE_ELEMENT_CLASS_"+(this.before?"BEFORE":"AFTER")]},Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE="___html2canvas___pseudoelement_before",Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER="___html2canvas___pseudoelement_after",Eb.prototype.renderImage=function(a,b,c,d){var e=a.cssInt("paddingLeft"),f=a.cssInt("paddingTop"),g=a.cssInt("paddingRight"),h=a.cssInt("paddingBottom"),i=c.borders,j=b.width-(i[1].width+i[3].width+e+g),k=b.height-(i[0].width+i[2].width+f+h);this.drawImage(d,0,0,d.image.width||j,d.image.height||k,b.left+e+i[3].width,b.top+f+i[0].width,j,k)},Eb.prototype.renderBackground=function(a,b,c){b.height>0&&b.width>0&&(this.renderBackgroundColor(a,b),this.renderBackgroundImage(a,b,c))},Eb.prototype.renderBackgroundColor=function(a,b){var c=a.css("backgroundColor");this.isTransparent(c)||this.rectangle(b.left,b.top,b.width,b.height,a.css("backgroundColor"))},Eb.prototype.renderBorders=function(a){a.forEach(this.renderBorder,this)},Eb.prototype.renderBorder=function(a){this.isTransparent(a.color)||null===a.args||this.drawShape(a.args,a.color)},Eb.prototype.renderBackgroundImage=function(a,b,c){var d=a.parseBackgroundImages();d.reverse().forEach(function(d,e,f){switch(d.method){case"url":var g=this.images.get(d.args[0]);g?this.renderBackgroundRepeating(a,b,g,f.length-(e+1),c):E("Error loading background-image",d.args[0]);break;case"linear-gradient":case"gradient":var h=this.images.get(d.value);h?this.renderBackgroundGradient(h,b,c):E("Error loading background-image",d.args[0]);break;case"none":break;default:E("Unknown background-image type",d.args[0])}},this)},Eb.prototype.renderBackgroundRepeating=function(a,b,c,d,e){var f=a.parseBackgroundSize(b,c.image,d),g=a.parseBackgroundPosition(b,c.image,d,f),h=a.parseBackgroundRepeat(d);switch(h){case"repeat-x":case"repeat no-repeat":this.backgroundRepeatShape(c,g,f,b,b.left+e[3],b.top+g.top+e[0],99999,f.height,e);break;case"repeat-y":case"no-repeat repeat":this.backgroundRepeatShape(c,g,f,b,b.left+g.left+e[3],b.top+e[0],f.width,99999,e);break;case"no-repeat":this.backgroundRepeatShape(c,g,f,b,b.left+g.left+e[3],b.top+g.top+e[0],f.width,f.height,e);break;default:this.renderBackgroundRepeat(c,g,f,{top:b.top,left:b.left},e[3],e[0])}},Eb.prototype.isTransparent=function(a){return!a||"transparent"===a||"rgba(0, 0, 0, 0)"===a},Fb.prototype=Object.create(F.prototype),Fb.prototype.getParentStack=function(a){var b=this.parent?this.parent.stack:null;return b?b.ownStacking?b:b.getParentStack(a):a.stack},Gb.prototype.testRangeBounds=function(a){var b,c,d,e,f=!1;return a.createRange&&(b=a.createRange(),b.getBoundingClientRect&&(c=a.createElement("boundtest"),c.style.height="123px",c.style.display="block",a.body.appendChild(c),b.selectNode(c),d=b.getBoundingClientRect(),e=d.height,123===e&&(f=!0),a.body.removeChild(c))),f},Gb.prototype.testCORS=function(){return"undefined"!=typeof(new Image).crossOrigin},Gb.prototype.testSVG=function(){var a=new Image,c=b.createElement("canvas"),d=c.getContext("2d");a.src="data:image/svg+xml,";try{d.drawImage(a,0,0),c.toDataURL()}catch(e){return!1}return!0},Hb.prototype.hasFabric=function(){return html2canvas.fabric?Promise.resolve():Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg"))},Hb.prototype.inlineFormatting=function(a){return/^data:image\/svg\+xml;base64,/.test(a)?this.decode64(this.removeContentType(a)):this.removeContentType(a)},Hb.prototype.removeContentType=function(a){return a.replace(/^data:image\/svg\+xml(;base64)?,/,"")},Hb.prototype.isInline=function(a){return/^data:image\/svg\+xml/i.test(a)},Hb.prototype.createCanvas=function(a){var b=this;return function(c,d){var e=new html2canvas.fabric.StaticCanvas("c");b.image=e.lowerCanvasEl,e.setWidth(d.width).setHeight(d.height).add(html2canvas.fabric.util.groupSVGElements(c,d)).renderAll(),a(e.lowerCanvasEl)}},Hb.prototype.decode64=function(b){return"function"==typeof a.atob?a.atob(b):Ib(b)},Jb.prototype=Object.create(Hb.prototype),Kb.prototype=Object.create(F.prototype),Kb.prototype.applyTextTransform=function(){this.node.data=this.transform(this.parent.css("textTransform"))},Kb.prototype.transform=function(a){var b=this.node.data;switch(a){case"lowercase":return b.toLowerCase();case"capitalize":return b.replace(/(^|\s|:|-|\(|\))([a-z])/g,Lb);case"uppercase":return b.toUpperCase();default:return b}},Mb.prototype=Object.create(A.prototype),Ob.prototype=Object.create(Eb.prototype),Ob.prototype.setFillStyle=function(a){return this.ctx.fillStyle=a,this.ctx},Ob.prototype.rectangle=function(a,b,c,d,e){this.setFillStyle(e).fillRect(a,b,c,d)},Ob.prototype.drawShape=function(a,b){this.shape(a),this.setFillStyle(b).fill()},Ob.prototype.taints=function(a){if(null===a.tainted){this.taintCtx.drawImage(a.image,0,0);try{this.taintCtx.getImageData(0,0,1,1),a.tainted=!1}catch(c){this.taintCtx=b.createElement("canvas").getContext("2d"),a.tainted=!0}}return a.tainted},Ob.prototype.drawImage=function(a,b,c,d,e,f,g,h,i){(!this.taints(a)||this.options.allowTaint)&&this.ctx.drawImage(a.image,b,c,d,e,f,g,h,i)},Ob.prototype.clip=function(a,b,c){this.ctx.save(),a.filter(Pb).forEach(function(a){this.shape(a).clip()},this),b.call(c),this.ctx.restore()},Ob.prototype.shape=function(a){return this.ctx.beginPath(),a.forEach(function(a,b){"rect"===a[0]?this.ctx.rect.apply(this.ctx,a.slice(1)):this.ctx[0===b?"moveTo":a[0]+"To"].apply(this.ctx,a.slice(1))},this),this.ctx.closePath(),this.ctx},Ob.prototype.font=function(a,b,c,d,e,f){this.setFillStyle(a).font=[b,c,d,e,f].join(" ").split(",")[0]},Ob.prototype.fontShadow=function(a,b,c,d){this.setVariable("shadowColor",a).setVariable("shadowOffsetY",b).setVariable("shadowOffsetX",c).setVariable("shadowBlur",d)},Ob.prototype.clearShadow=function(){this.setVariable("shadowColor","rgba(0,0,0,0)")},Ob.prototype.setOpacity=function(a){this.ctx.globalAlpha=a},Ob.prototype.setTransform=function(a){this.ctx.translate(a.origin[0],a.origin[1]),this.ctx.transform.apply(this.ctx,a.matrix),this.ctx.translate(-a.origin[0],-a.origin[1])},Ob.prototype.setVariable=function(a,b){return this.variables[a]!==b&&(this.variables[a]=this.ctx[a]=b),this},Ob.prototype.text=function(a,b,c){this.ctx.fillText(a,b,c)},Ob.prototype.backgroundRepeatShape=function(a,b,c,d,e,f,g,h,i){var j=[["line",Math.round(e),Math.round(f)],["line",Math.round(e+g),Math.round(f)],["line",Math.round(e+g),Math.round(h+f)],["line",Math.round(e),Math.round(h+f)]];this.clip([j],function(){this.renderBackgroundRepeat(a,b,c,d,i[3],i[0])},this)},Ob.prototype.renderBackgroundRepeat=function(a,b,c,d,e,f){var g=Math.round(d.left+b.left+e),h=Math.round(d.top+b.top+f);this.setFillStyle(this.ctx.createPattern(this.resizeImage(a,c),"repeat")),this.ctx.translate(g,h),this.ctx.fill(),this.ctx.translate(-g,-h)},Ob.prototype.renderBackgroundGradient=function(a,b){if(a instanceof D){var c=this.ctx.createLinearGradient(b.left+b.width*a.x0,b.top+b.height*a.y0,b.left+b.width*a.x1,b.top+b.height*a.y1);a.colorStops.forEach(function(a){c.addColorStop(a.stop,a.color)}),this.rectangle(b.left,b.top,b.width,b.height,c)}},Ob.prototype.resizeImage=function(a,c){var d=a.image;if(d.width===c.width&&d.height===c.height)return d;var e,f=b.createElement("canvas");return f.width=c.width,f.height=c.height,e=f.getContext("2d"),e.drawImage(d,0,0,d.width,d.height,0,0,c.width,c.height),f}}).call({},window,document); \ No newline at end of file +(function(a,b,c,d,e,f,g){function h(a,b,c,d){return o(a,a,c,d,b).then(function(e){E("Document cloned");var f="["+Qb+"='true']";a.querySelector(f).removeAttribute(Qb);var g=e.contentWindow,h=g.document.querySelector(f),j=Promise.resolve("function"==typeof b.onclone?b.onclone(g.document):!0);return j.then(function(){return i(h,e,b,c,d)})})}function i(a,c,d,e,f){var g=c.contentWindow,h=new Gb(g.document),i=new C(d,h),n=M(a),o="view"===d.type?e:l(g.document),p="view"===d.type?f:m(g.document),q=new Ob(o,p,i,d,b),r=new O(a,q,h,i,d);return r.ready.then(function(){E("Finished rendering");var b;return b="view"===d.type?k(q.canvas,{width:q.canvas.width,height:q.canvas.height,top:0,left:0,x:0,y:0}):a===g.document.body||a===g.document.documentElement||null!=d.canvas?q.canvas:k(q.canvas,{width:null!=d.width?d.width:n.width,height:null!=d.height?d.height:n.height,top:n.top,left:n.left,x:g.pageXOffset,y:g.pageYOffset}),j(c,d),b})}function j(a,b){b.removeContainer&&(a.parentNode.removeChild(a),E("Cleaned up container"))}function k(a,c){var d=b.createElement("canvas"),e=Math.min(a.width-1,Math.max(0,c.left)),f=Math.min(a.width,Math.max(1,c.left+c.width)),g=Math.min(a.height-1,Math.max(0,c.top)),h=Math.min(a.height,Math.max(1,c.top+c.height));return d.width=c.width,d.height=c.height,E("Cropping canvas at:","left:",c.left,"top:",c.top,"width:",f-e,"height:",h-g),E("Resulting crop with width",c.width,"and height",c.height," with x",e,"and y",g),d.getContext("2d").drawImage(a,e,g,f-e,h-g,c.x,c.y,f-e,h-g),d}function l(a){return Math.max(Math.max(a.body.scrollWidth,a.documentElement.scrollWidth),Math.max(a.body.offsetWidth,a.documentElement.offsetWidth),Math.max(a.body.clientWidth,a.documentElement.clientWidth))}function m(a){return Math.max(Math.max(a.body.scrollHeight,a.documentElement.scrollHeight),Math.max(a.body.offsetHeight,a.documentElement.offsetHeight),Math.max(a.body.clientHeight,a.documentElement.clientHeight))}function n(){return"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"}function o(a,b,c,d,e){r(a);var f=a.documentElement.cloneNode(!0),g=b.createElement("iframe");return g.className="html2canvas-container",g.style.visibility="hidden",g.style.position="absolute",g.style.left=g.style.top="-10000px",g.width=c,g.height=d,g.scrolling="no",b.body.appendChild(g),new Promise(function(b){var c=g.contentWindow.document;g.contentWindow.onload=g.onload=function(){var f=setInterval(function(){c.body.childNodes.length>0&&(s(a,c),clearInterval(f),"view"===e.type&&g.contentWindow.scrollTo(d,h),b(g))},50)};var d=a.defaultView.pageXOffset,h=a.defaultView.pageYOffset;c.open(),c.write(""),(d!==a.defaultView.pageXOffset||h!==a.defaultView.pageYOffset)&&a.defaultView.scrollTo(d,h),c.replaceChild(e.javascriptEnabled===!0?c.adoptNode(f):t(c.adoptNode(f)),c.documentElement),c.close()})}function p(b,c,d,e,f,g){return new xb(b,c,a.document).then(q(b)).then(function(a){return o(a,d,e,f,g)})}function q(a){return function(c){var d,e=new DOMParser;try{d=e.parseFromString(c,"text/html")}catch(f){E("DOMParser not supported, falling back to createHTMLDocument"),d=b.implementation.createHTMLDocument("");try{d.open(),d.write(c),d.close()}catch(g){E("createHTMLDocument write not supported, falling back to document.body.innerHTML"),d.body.innerHTML=c}}var h=d.querySelector("base");if(!h||!h.href.host){var i=d.createElement("base");i.href=a,d.head.insertBefore(i,d.head.firstChild)}return d}}function r(a){[].slice.call(a.querySelectorAll("canvas"),0).forEach(function(a){a.setAttribute(Rb,"canvas-"+Sb++)})}function s(a,b){[].slice.call(a.querySelectorAll("["+Rb+"]"),0).forEach(function(a){try{var c=b.querySelector("["+Rb+'="'+a.getAttribute(Rb)+'"]');c&&(c.width=a.width,c.height=a.height,c.getContext("2d").putImageData(a.getContext("2d").getImageData(0,0,a.width,a.height),0,0))}catch(d){E("Unable to copy canvas content from",a,d)}a.removeAttribute(Rb)})}function t(a){return[].slice.call(a.childNodes,0).filter(u).forEach(function(b){"SCRIPT"===b.tagName?a.removeChild(b):t(b)}),a}function u(a){return a.nodeType===Node.ELEMENT_NODE}function v(a){var c=b.createElement("a");return c.href=a,c.href=c.href,c}function w(a){if(this.src=a,E("DummyImageContainer for",a),!this.promise||!this.image){E("Initiating DummyImageContainer"),w.prototype.image=new Image;var b=this.image;w.prototype.promise=new Promise(function(a,c){b.onload=a,b.onerror=c,b.src=n(),b.complete===!0&&a(b)})}}function x(a,c){var d,e,f=b.createElement("div"),g=b.createElement("img"),h=b.createElement("span"),i="Hidden Text";f.style.visibility="hidden",f.style.fontFamily=a,f.style.fontSize=c,f.style.margin=0,f.style.padding=0,b.body.appendChild(f),g.src=n(),g.width=1,g.height=1,g.style.margin=0,g.style.padding=0,g.style.verticalAlign="baseline",h.style.fontFamily=a,h.style.fontSize=c,h.style.margin=0,h.style.padding=0,h.appendChild(b.createTextNode(i)),f.appendChild(h),f.appendChild(g),d=g.offsetTop-h.offsetTop+1,f.removeChild(h),f.appendChild(b.createTextNode(i)),f.style.lineHeight="normal",g.style.verticalAlign="super",e=g.offsetTop-f.offsetTop+1,b.body.removeChild(f),this.baseline=d,this.lineWidth=1,this.middle=e}function y(){this.data={}}function z(a,b,c){this.image=null,this.src=a;var d=this,e=M(a);this.promise=(b?new Promise(function(b){"about:blank"===a.contentWindow.document.URL||null==a.contentWindow.document.documentElement?a.contentWindow.onload=a.onload=function(){b(a)}:b(a)}):this.proxyLoad(c.proxy,e,c)).then(function(a){return html2canvas(a.contentWindow.document.documentElement,{type:"view",width:a.width,height:a.height,proxy:c.proxy,javascriptEnabled:c.javascriptEnabled,removeContainer:c.removeContainer,allowTaint:c.allowTaint,imageTimeout:c.imageTimeout/2})}).then(function(a){return d.image=a})}function A(a){this.src=a.value,this.colorStops=[],this.type=null,this.x0=.5,this.y0=.5,this.x1=.5,this.y1=.5,this.promise=Promise.resolve(!0)}function B(a,b){this.src=a,this.image=new Image;var c=this;this.tainted=null,this.promise=new Promise(function(d,e){c.image.onload=d,c.image.onerror=e,b&&(c.image.crossOrigin="anonymous"),c.image.src=a,c.image.complete===!0&&d(c.image)})}function C(b,c){this.link=null,this.options=b,this.support=c,this.origin=this.getOrigin(a.location.href)}function D(a){A.apply(this,arguments),this.type=this.TYPES.LINEAR;var b=null===a.args[0].match(this.stepRegExp);b?a.args[0].split(" ").reverse().forEach(function(a){switch(a){case"left":this.x0=0,this.x1=1;break;case"top":this.y0=0,this.y1=1;break;case"right":this.x0=1,this.x1=0;break;case"bottom":this.y0=1,this.y1=0;break;case"to":var b=this.y0,c=this.x0;this.y0=this.y1,this.x0=this.x1,this.x1=c,this.y1=b}},this):(this.y0=0,this.y1=1),this.colorStops=a.args.slice(b?1:0).map(function(a){var b=a.match(this.stepRegExp);return{color:b[1],stop:"%"===b[3]?b[2]/100:null}},this),null===this.colorStops[0].stop&&(this.colorStops[0].stop=0),null===this.colorStops[this.colorStops.length-1].stop&&(this.colorStops[this.colorStops.length-1].stop=1),this.colorStops.forEach(function(a,b){null===a.stop&&this.colorStops.slice(b).some(function(c,d){return null!==c.stop?(a.stop=(c.stop-this.colorStops[b-1].stop)/(d+1)+this.colorStops[b-1].stop,!0):!1},this)},this)}function E(){a.html2canvas.logging&&a.console&&a.console.log&&Function.prototype.bind.call(a.console.log,a.console).apply(a.console,[Date.now()-a.html2canvas.start+"ms","html2canvas:"].concat([].slice.call(arguments,0)))}function F(a,b){this.node=a,this.parent=b,this.stack=null,this.bounds=null,this.borders=null,this.clip=[],this.backgroundClip=[],this.offsetBounds=null,this.visible=null,this.computedStyles=null,this.styles={},this.backgroundImages=null,this.transformData=null,this.transformMatrix=null,this.isPseudoElement=!1,this.opacity=null}function G(a){var b=a.options[a.selectedIndex||0];return b?b.text||"":""}function H(a){return a&&"matrix"===a[1]?a[2].split(",").map(function(a){return parseFloat(a.trim())}):void 0}function I(a){return-1!==a.toString().indexOf("%")}function J(a){var b,c,d,e,f,g,h,i=" \r\n ",j=[],k=0,l=0,m=function(){b&&('"'===c.substr(0,1)&&(c=c.substr(1,c.length-2)),c&&h.push(c),"-"===b.substr(0,1)&&(e=b.indexOf("-",1)+1)>0&&(d=b.substr(0,e),b=b.substr(e)),j.push({prefix:d,method:b.toLowerCase(),value:f,args:h,image:null})),h=[],b=d=c=f=""};return h=[],b=d=c=f="",a.split("").forEach(function(a){if(!(0===k&&i.indexOf(a)>-1)){switch(a){case'"':g?g===a&&(g=null):g=a;break;case"(":if(g)break;if(0===k)return k=1,void(f+=a);l++;break;case")":if(g)break;if(1===k){if(0===l)return k=0,f+=a,void m();l--}break;case",":if(g)break;if(0===k)return void m();if(1===k&&0===l&&!b.match(/^url$/i))return h.push(c),c="",void(f+=a)}f+=a,0===k?b+=a:c+=a}}),m(),j}function K(a){return a.replace("px","")}function L(a){return parseFloat(a)}function M(a){if(a.getBoundingClientRect){var b=a.getBoundingClientRect(),c=null==a.offsetWidth?b.width:a.offsetWidth;return{top:b.top,bottom:b.bottom||b.top+b.height,right:b.left+c,left:b.left,width:c,height:null==a.offsetHeight?b.height:a.offsetHeight}}return{}}function N(a){var b=a.offsetParent?N(a.offsetParent):{top:0,left:0};return{top:a.offsetTop+b.top,bottom:a.offsetTop+a.offsetHeight+b.top,right:a.offsetLeft+b.left+a.offsetWidth,left:a.offsetLeft+b.left,width:a.offsetWidth,height:a.offsetHeight}}function O(a,b,c,d,e){E("Starting NodeParser"),this.renderer=b,this.options=e,this.range=null,this.support=c,this.renderQueue=[],this.stack=new Fb(!0,1,a.ownerDocument,null);var f=new F(a,null);if(a===a.ownerDocument.documentElement){var g=new F(this.renderer.isTransparent(f.css("backgroundColor"))?a.ownerDocument.body:a.ownerDocument.documentElement,null);b.rectangle(0,0,b.width,b.height,g.css("backgroundColor"))}f.visibile=f.isElementVisible(),this.createPseudoHideStyles(a.ownerDocument),this.disableAnimations(a.ownerDocument),this.nodes=sb([f].concat(this.getChildren(f)).filter(function(a){return a.visible=a.isElementVisible()}).map(this.getPseudoElements,this)),this.fontMetrics=new y,E("Fetched nodes, total:",this.nodes.length),E("Calculate overflow clips"),this.calculateOverflowClips(),E("Start fetching images"),this.images=d.fetch(this.nodes.filter(jb)),this.ready=this.images.ready.then(ob(function(){return E("Images loaded, starting parsing"),E("Creating stacking contexts"),this.createStackingContexts(),E("Sorting stacking contexts"),this.sortStackingContexts(this.stack),this.parse(this.stack),E("Render queue created with "+this.renderQueue.length+" items"),new Promise(ob(function(a){e.async?"function"==typeof e.async?e.async.call(this,this.renderQueue,a):this.renderQueue.length>0?(this.renderIndex=0,this.asyncRenderer(this.renderQueue,a)):a():(this.renderQueue.forEach(this.paint,this),a())},this))},this))}function P(a){return a.parent&&a.parent.clip.length}function Q(a){return a.replace(/(\-[a-z])/g,function(a){return a.toUpperCase().replace("-","")})}function R(){}function S(a,b,c,d){var e=4*((Math.sqrt(2)-1)/3),f=c*e,g=d*e,h=a+c,i=b+d;return{topLeft:U({x:a,y:i},{x:a,y:i-g},{x:h-f,y:b},{x:h,y:b}),topRight:U({x:a,y:b},{x:a+f,y:b},{x:h,y:i-g},{x:h,y:i}),bottomRight:U({x:h,y:b},{x:h,y:b+g},{x:a+f,y:i},{x:a,y:i}),bottomLeft:U({x:h,y:i},{x:h-f,y:i},{x:a,y:b+g},{x:a,y:b})}}function T(a,b,c){var d=a.left,e=a.top,f=a.width,g=a.height,h=b[0][0],i=b[0][1],j=b[1][0],k=b[1][1],l=b[2][0],m=b[2][1],n=b[3][0],o=b[3][1],p=f-j,q=g-m,r=f-l,s=g-o;return{topLeftOuter:S(d,e,h,i).topLeft.subdivide(.5),topLeftInner:S(d+c[3].width,e+c[0].width,Math.max(0,h-c[3].width),Math.max(0,i-c[0].width)).topLeft.subdivide(.5),topRightOuter:S(d+p,e,j,k).topRight.subdivide(.5),topRightInner:S(d+Math.min(p,f+c[3].width),e+c[0].width,p>f+c[3].width?0:j-c[3].width,k-c[0].width).topRight.subdivide(.5),bottomRightOuter:S(d+r,e+q,l,m).bottomRight.subdivide(.5),bottomRightInner:S(d+Math.min(r,f-c[3].width),e+Math.min(q,g+c[0].width),Math.max(0,l-c[1].width),m-c[2].width).bottomRight.subdivide(.5),bottomLeftOuter:S(d,e+s,n,o).bottomLeft.subdivide(.5),bottomLeftInner:S(d+c[3].width,e+s,Math.max(0,n-c[3].width),o-c[2].width).bottomLeft.subdivide(.5)}}function U(a,b,c,d){var e=function(a,b,c){return{x:a.x+(b.x-a.x)*c,y:a.y+(b.y-a.y)*c}};return{start:a,startControl:b,endControl:c,end:d,subdivide:function(f){var g=e(a,b,f),h=e(b,c,f),i=e(c,d,f),j=e(g,h,f),k=e(h,i,f),l=e(j,k,f);return[U(a,g,j,l),U(l,k,i,d)]},curveTo:function(a){a.push(["bezierCurve",b.x,b.y,c.x,c.y,d.x,d.y])},curveToReversed:function(d){d.push(["bezierCurve",c.x,c.y,b.x,b.y,a.x,a.y])}}}function V(a,b,c,d,e,f,g){var h=[];return b[0]>0||b[1]>0?(h.push(["line",d[1].start.x,d[1].start.y]),d[1].curveTo(h)):h.push(["line",a.c1[0],a.c1[1]]),c[0]>0||c[1]>0?(h.push(["line",f[0].start.x,f[0].start.y]),f[0].curveTo(h),h.push(["line",g[0].end.x,g[0].end.y]),g[0].curveToReversed(h)):(h.push(["line",a.c2[0],a.c2[1]]),h.push(["line",a.c3[0],a.c3[1]])),b[0]>0||b[1]>0?(h.push(["line",e[1].end.x,e[1].end.y]),e[1].curveToReversed(h)):h.push(["line",a.c4[0],a.c4[1]]),h}function W(a,b,c,d,e,f,g){b[0]>0||b[1]>0?(a.push(["line",d[0].start.x,d[0].start.y]),d[0].curveTo(a),d[1].curveTo(a)):a.push(["line",f,g]),(c[0]>0||c[1]>0)&&a.push(["line",e[0].start.x,e[0].start.y])}function X(a){return a.cssInt("zIndex")<0}function Y(a){return a.cssInt("zIndex")>0}function Z(a){return 0===a.cssInt("zIndex")}function $(a){return-1!==["inline","inline-block","inline-table"].indexOf(a.css("display"))}function _(a){return a instanceof Fb}function ab(a){return a.node.data.trim().length>0}function bb(a){return/^(normal|none|0px)$/.test(a.parent.css("letterSpacing"))}function cb(a){return["TopLeft","TopRight","BottomRight","BottomLeft"].map(function(b){var c=a.css("border"+b+"Radius"),d=c.split(" ");return d.length<=1&&(d[1]=d[0]),d.map(pb)})}function db(a){return a.nodeType===Node.TEXT_NODE||a.nodeType===Node.ELEMENT_NODE}function eb(a){var b=a.css("position"),c=-1!==["absolute","relative","fixed"].indexOf(b)?a.css("zIndex"):"auto";return"auto"!==c}function fb(a){return"static"!==a.css("position")}function gb(a){return"none"!==a.css("float")}function hb(a){return-1!==["inline-block","inline-table"].indexOf(a.css("display"))}function ib(a){var b=this;return function(){return!a.apply(b,arguments)}}function jb(a){return a.node.nodeType===Node.ELEMENT_NODE}function kb(a){return a.isPseudoElement===!0}function lb(a){return a.node.nodeType===Node.TEXT_NODE}function mb(a){return function(b,c){return b.cssInt("zIndex")+a.indexOf(b)/a.length-(c.cssInt("zIndex")+a.indexOf(c)/a.length)}}function nb(a){return a.getOpacity()<1}function ob(a,b){return function(){return a.apply(b,arguments)}}function pb(a){return parseInt(a,10)}function qb(a){return a.width}function rb(a){return a.node.nodeType!==Node.ELEMENT_NODE||-1===["SCRIPT","HEAD","TITLE","OBJECT","BR","OPTION"].indexOf(a.node.nodeName)}function sb(a){return[].concat.apply([],a)}function tb(a){var b=a.substr(0,1);return b===a.substr(a.length-1)&&b.match(/'|"/)?a.substr(1,a.length-2):a}function ub(b){for(var c,d=[],e=0,f=!1;b.length;)vb(b[e])===f?(c=b.splice(0,e),c.length&&d.push(a.html2canvas.punycode.ucs2.encode(c)),f=!f,e=0):e++,e>=b.length&&(c=b.splice(0,e),c.length&&d.push(a.html2canvas.punycode.ucs2.encode(c)));return d}function vb(a){return-1!==[32,13,10,9,45].indexOf(a)}function wb(a){return/[^\u0000-\u00ff]/.test(a)}function xb(a,b,c){var d=Ab(Ub),e=Bb(b,a,d);return Ub?Nb(e):zb(c,e,d).then(function(a){return Ib(a.content)})}function yb(a,b,c){var d=Ab(Vb),e=Bb(b,a,d);return Vb?Promise.resolve(e):zb(c,e,d).then(function(a){return"data:"+a.type+";base64,"+a.content})}function zb(b,c,d){return new Promise(function(e,f){var g=b.createElement("script"),h=function(){delete a.html2canvas.proxy[d],b.body.removeChild(g)};a.html2canvas.proxy[d]=function(a){h(),e(a)},g.src=c,g.onerror=function(a){h(),f(a)},b.body.appendChild(g)})}function Ab(a){return a?"":"html2canvas_"+Date.now()+"_"+ ++Tb+"_"+Math.round(1e5*Math.random())}function Bb(a,b,c){return a+"?url="+encodeURIComponent(b)+(c.length?"&callback=html2canvas.proxy."+c:"")}function Cb(a,c){var d=(b.createElement("script"),b.createElement("a"));d.href=a,a=d.href,this.src=a,this.image=new Image;var e=this;this.promise=new Promise(function(d,f){e.image.crossOrigin="Anonymous",e.image.onload=d,e.image.onerror=f,new yb(a,c,b).then(function(a){e.image.src=a})["catch"](f)})}function Db(a,b,c){F.call(this,a,b),this.isPseudoElement=!0,this.before=":before"===c}function Eb(a,b,c,d,e){this.width=a,this.height=b,this.images=c,this.options=d,this.document=e}function Fb(a,b,c,d){F.call(this,c,d),this.ownStacking=a,this.contexts=[],this.children=[],this.opacity=(this.parent?this.parent.stack.opacity:1)*b}function Gb(a){this.rangeBounds=this.testRangeBounds(a),this.cors=this.testCORS(),this.svg=this.testSVG()}function Hb(a){this.src=a,this.image=null;var b=this;this.promise=this.hasFabric().then(function(){return b.isInline(a)?Promise.resolve(b.inlineFormatting(a)):Nb(a)}).then(function(a){return new Promise(function(c){html2canvas.fabric.loadSVGFromString(a,b.createCanvas.call(b,c))})})}function Ib(a){var b,c,d,e,f,g,h,i,j="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",k=a.length,l="";for(b=0;k>b;b+=4)c=j.indexOf(a[b]),d=j.indexOf(a[b+1]),e=j.indexOf(a[b+2]),f=j.indexOf(a[b+3]),g=c<<2|d>>4,h=(15&d)<<4|e>>2,i=(3&e)<<6|f,l+=64===e?String.fromCharCode(g):64===f||-1===f?String.fromCharCode(g,h):String.fromCharCode(g,h,i);return l}function Jb(a,b){this.src=a,this.image=null;var c=this;this.promise=b?new Promise(function(b,d){c.image=new Image,c.image.onload=b,c.image.onerror=d,c.image.src="data:image/svg+xml,"+(new XMLSerializer).serializeToString(a),c.image.complete===!0&&b(c.image)}):this.hasFabric().then(function(){return new Promise(function(b){html2canvas.fabric.parseSVGDocument(a,c.createCanvas.call(c,b))})})}function Kb(a,b){F.call(this,a,b)}function Lb(a,b,c){return a.length>0?b+c.toUpperCase():void 0}function Mb(a){A.apply(this,arguments),this.type="linear"===a.args[0]?this.TYPES.LINEAR:this.TYPES.RADIAL}function Nb(a){return new Promise(function(b,c){var d=new XMLHttpRequest;d.open("GET",a),d.onload=function(){200===d.status?b(d.responseText):c(new Error(d.statusText))},d.onerror=function(){c(new Error("Network Error"))},d.send()})}function Ob(a,b){Eb.apply(this,arguments),this.canvas=this.options.canvas||this.document.createElement("canvas"),this.options.canvas||(this.canvas.width=a,this.canvas.height=b),this.ctx=this.canvas.getContext("2d"),this.options.background&&this.rectangle(0,0,a,b,this.options.background),this.taintCtx=this.document.createElement("canvas").getContext("2d"),this.ctx.textBaseline="bottom",this.variables={},E("Initialized CanvasRenderer with size",a,"x",b)}function Pb(a){return a.length>0}if(!function(){var c,d,f,g;!function(){var a={},b={};c=function(b,c,d){a[b]={deps:c,callback:d}},g=f=d=function(c){function e(a){if("."!==a.charAt(0))return a;for(var b=a.split("/"),d=c.split("/").slice(0,-1),e=0,f=b.length;f>e;e++){var g=b[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(g._eak_seen=a,b[c])return b[c];if(b[c]={},!a[c])throw new Error("Could not find module "+c);for(var f,h=a[c],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)k.push("exports"===i[l]?f={}:d(e(i[l])));var n=j.apply(this,k);return b[c]=f||n}}(),c("promise/all",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to all.");return new b(function(b,c){function d(a){return function(b){f(a,b)}}function f(a,c){h[a]=c,0===--i&&b(h)}var g,h=[],i=a.length;0===i&&b([]);for(var j=0;jg^"contain"===f[0]?{width:a.height*h,height:a.height}:{width:a.width,height:a.width/h}}d=parseInt(f[0],10)}return e="auto"===f[0]&&"auto"===f[1]?b.height:"auto"===f[1]?d/b.width*b.height:I(f[1])?a.height*parseFloat(f[1])/100:parseInt(f[1],10),"auto"===f[0]&&(d=e/b.height*b.width),{width:d,height:e}},F.prototype.parseBackgroundPosition=function(a,b,c,d){var e,f,g=this.cssList("backgroundPosition",c);return e=I(g[0])?(a.width-(d||b).width)*(parseFloat(g[0])/100):parseInt(g[0],10),f="auto"===g[1]?e/b.width*b.height:I(g[1])?(a.height-(d||b).height)*parseFloat(g[1])/100:parseInt(g[1],10),"auto"===g[0]&&(e=f/b.height*b.width),{left:e,top:f}},F.prototype.parseBackgroundRepeat=function(a){return this.cssList("backgroundRepeat",a)[0]},F.prototype.parseTextShadows=function(){var a=this.css("textShadow"),b=[];if(a&&"none"!==a)for(var c=a.match(this.TEXT_SHADOW_PROPERTY),d=0;c&&dDate.now()?this.asyncRenderer(a,b,c):setTimeout(ob(function(){this.asyncRenderer(a,b)},this),0)},O.prototype.createPseudoHideStyles=function(a){this.createStyles(a,"."+Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE+':before { content: "" !important; display: none !important; }.'+Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER+':after { content: "" !important; display: none !important; }')},O.prototype.disableAnimations=function(a){this.createStyles(a,"* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; -webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}")},O.prototype.createStyles=function(a,b){var c=a.createElement("style");c.innerHTML=b,a.body.appendChild(c)},O.prototype.getPseudoElements=function(a){var b=[[a]];if(a.node.nodeType===Node.ELEMENT_NODE){var c=this.getPseudoElement(a,":before"),d=this.getPseudoElement(a,":after");c&&b.push(c),d&&b.push(d)}return sb(b)},O.prototype.getPseudoElement=function(a,c){var d=a.computedStyle(c);if(!d||!d.content||"none"===d.content||"-moz-alt-content"===d.content||"none"===d.display)return null;for(var e=tb(d.content),f="url"===e.substr(0,3),g=b.createElement(f?"img":"html2canvaspseudoelement"),h=new Db(g,a,c),i=d.length-1;i>=0;i--){var j=Q(d.item(i));g.style[j]=d[j]}if(g.className=Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE+" "+Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER,f)return g.src=J(e)[0].args[0],[h];var k=b.createTextNode(e);return g.appendChild(k),[h,new Kb(k,h)]},O.prototype.getChildren=function(a){return sb([].filter.call(a.node.childNodes,db).map(function(b){var c=[b.nodeType===Node.TEXT_NODE?new Kb(b,a):new F(b,a)].filter(rb);return b.nodeType===Node.ELEMENT_NODE&&c.length&&"TEXTAREA"!==b.tagName?c[0].isElementVisible()?c.concat(this.getChildren(c[0])):[]:c},this))},O.prototype.newStackingContext=function(a,b){var c=new Fb(b,a.getOpacity(),a.node,a.parent);a.cloneTo(c);var d=b?c.getParentStack(this):c.parent.stack;d.contexts.push(c),a.stack=c},O.prototype.createStackingContexts=function(){this.nodes.forEach(function(a){jb(a)&&(this.isRootElement(a)||nb(a)||eb(a)||this.isBodyWithTransparentRoot(a)||a.hasTransform())?this.newStackingContext(a,!0):jb(a)&&(fb(a)&&Z(a)||hb(a)||gb(a))?this.newStackingContext(a,!1):a.assignStack(a.parent.stack)},this)},O.prototype.isBodyWithTransparentRoot=function(a){return"BODY"===a.node.nodeName&&this.renderer.isTransparent(a.parent.css("backgroundColor"))},O.prototype.isRootElement=function(a){return null===a.parent},O.prototype.sortStackingContexts=function(a){a.contexts.sort(mb(a.contexts.slice(0))),a.contexts.forEach(this.sortStackingContexts,this)},O.prototype.parseTextBounds=function(a){return function(b,c,d){if("none"!==a.parent.css("textDecoration").substr(0,4)||0!==b.trim().length){if(this.support.rangeBounds&&!a.parent.hasTransform()){var e=d.slice(0,c).join("").length;return this.getRangeBounds(a.node,e,b.length)}if(a.node&&"string"==typeof a.node.data){var f=a.node.splitText(b.length),g=this.getWrapperBounds(a.node,a.parent.hasTransform());return a.node=f,g}}else(!this.support.rangeBounds||a.parent.hasTransform())&&(a.node=a.node.splitText(b.length));return{}}},O.prototype.getWrapperBounds=function(a,b){var c=a.ownerDocument.createElement("html2canvaswrapper"),d=a.parentNode,e=a.cloneNode(!0);c.appendChild(a.cloneNode(!0)),d.replaceChild(c,a);var f=b?N(c):M(c);return d.replaceChild(e,c),f},O.prototype.getRangeBounds=function(a,b,c){var d=this.range||(this.range=a.ownerDocument.createRange());return d.setStart(a,b),d.setEnd(a,b+c),d.getBoundingClientRect()},O.prototype.parse=function(a){var b=a.contexts.filter(X),c=a.children.filter(jb),d=c.filter(ib(gb)),e=d.filter(ib(fb)).filter(ib($)),f=c.filter(ib(fb)).filter(gb),g=d.filter(ib(fb)).filter($),h=a.contexts.concat(d.filter(fb)).filter(Z),i=a.children.filter(lb).filter(ab),j=a.contexts.filter(Y);b.concat(e).concat(f).concat(g).concat(h).concat(i).concat(j).forEach(function(a){this.renderQueue.push(a),_(a)&&(this.parse(a),this.renderQueue.push(new R))},this)},O.prototype.paint=function(a){try{a instanceof R?this.renderer.ctx.restore():lb(a)?(kb(a.parent)&&a.parent.appendToDOM(),this.paintText(a),kb(a.parent)&&a.parent.cleanDOM()):this.paintNode(a)}catch(b){E(b)}},O.prototype.paintNode=function(a){_(a)&&(this.renderer.setOpacity(a.opacity),this.renderer.ctx.save(),a.hasTransform()&&this.renderer.setTransform(a.parseTransform()));var b=a.parseBounds();this.renderer.clip(a.backgroundClip,function(){this.renderer.renderBackground(a,b,a.borders.borders.map(qb))},this),this.renderer.clip(a.clip,function(){this.renderer.renderBorders(a.borders.borders)},this),this.renderer.clip(a.backgroundClip,function(){switch(a.node.nodeName){case"svg":case"IFRAME":var c=this.images.get(a.node);c?this.renderer.renderImage(a,b,a.borders,c):E("Error loading <"+a.node.nodeName+">",a.node);break;case"IMG":var d=this.images.get(a.node.src);d?this.renderer.renderImage(a,b,a.borders,d):E("Error loading ",a.node.src);break;case"CANVAS":this.renderer.renderImage(a,b,a.borders,{image:a.node});break;case"SELECT":case"INPUT":case"TEXTAREA":this.paintFormValue(a)}},this)},O.prototype.paintFormValue=function(a){if(a.getValue().length>0){var b=a.node.ownerDocument,c=b.createElement("html2canvaswrapper"),d=["lineHeight","textAlign","fontFamily","fontWeight","fontSize","color","paddingLeft","paddingTop","paddingRight","paddingBottom","width","height","borderLeftStyle","borderTopStyle","borderLeftWidth","borderTopWidth","boxSizing","whiteSpace","wordWrap"];d.forEach(function(b){try{c.style[b]=a.css(b)}catch(d){E("html2canvas: Parse: Exception caught in renderFormValue: "+d.message)}});var e=a.parseBounds();c.style.position="fixed",c.style.left=e.left+"px",c.style.top=e.top+"px",c.textContent=a.getValue(),b.body.appendChild(c),this.paintText(new Kb(c.firstChild,a)),b.body.removeChild(c)}},O.prototype.paintText=function(b){b.applyTextTransform();var c=a.html2canvas.punycode.ucs2.decode(b.node.data),d=this.options.letterRendering&&!bb(b)||wb(b.node.data)?c.map(function(b){return a.html2canvas.punycode.ucs2.encode([b])}):ub(c),e=b.parent.fontWeight(),f=b.parent.css("fontSize"),g=b.parent.css("fontFamily"),h=b.parent.parseTextShadows();this.renderer.font(b.parent.css("color"),b.parent.css("fontStyle"),b.parent.css("fontVariant"),e,f,g),h.length?this.renderer.fontShadow(h[0].color,h[0].offsetX,h[0].offsetY,h[0].blur):this.renderer.clearShadow(),this.renderer.clip(b.parent.clip,function(){d.map(this.parseTextBounds(b),this).forEach(function(a,c){a&&(this.renderer.text(d[c],a.left,a.bottom),this.renderTextDecoration(b.parent,a,this.fontMetrics.getMetrics(g,f)))},this)},this)},O.prototype.renderTextDecoration=function(a,b,c){switch(a.css("textDecoration").split(" ")[0]){case"underline":this.renderer.rectangle(b.left,Math.round(b.top+c.baseline+c.lineWidth),b.width,1,a.css("color"));break;case"overline":this.renderer.rectangle(b.left,Math.round(b.top),b.width,1,a.css("color"));break;case"line-through":this.renderer.rectangle(b.left,Math.ceil(b.top+c.middle+c.lineWidth),b.width,1,a.css("color"))}},O.prototype.parseBorders=function(a){var b=a.parseBounds(),c=cb(a),d=["Top","Right","Bottom","Left"].map(function(b){return{width:a.cssInt("border"+b+"Width"),color:a.css("border"+b+"Color"),args:null}}),e=T(b,c,d);return{clip:this.parseBackgroundClip(a,e,d,c,b),borders:d.map(function(a,f){if(a.width>0){var g=b.left,h=b.top,i=b.width,j=b.height-d[2].width;switch(f){case 0:j=d[0].width,a.args=V({c1:[g,h],c2:[g+i,h],c3:[g+i-d[1].width,h+j],c4:[g+d[3].width,h+j]},c[0],c[1],e.topLeftOuter,e.topLeftInner,e.topRightOuter,e.topRightInner);break;case 1:g=b.left+b.width-d[1].width,i=d[1].width,a.args=V({c1:[g+i,h],c2:[g+i,h+j+d[2].width],c3:[g,h+j],c4:[g,h+d[0].width]},c[1],c[2],e.topRightOuter,e.topRightInner,e.bottomRightOuter,e.bottomRightInner);break;case 2:h=h+b.height-d[2].width,j=d[2].width,a.args=V({c1:[g+i,h+j],c2:[g,h+j],c3:[g+d[3].width,h],c4:[g+i-d[3].width,h]},c[2],c[3],e.bottomRightOuter,e.bottomRightInner,e.bottomLeftOuter,e.bottomLeftInner);break;case 3:i=d[3].width,a.args=V({c1:[g,h+j+d[2].width],c2:[g,h],c3:[g+i,h+d[0].width],c4:[g+i,h+j]},c[3],c[0],e.bottomLeftOuter,e.bottomLeftInner,e.topLeftOuter,e.topLeftInner)}}return a})}},O.prototype.parseBackgroundClip=function(a,b,c,d,e){var f=a.css("backgroundClip"),g=[];switch(f){case"content-box":case"padding-box":W(g,d[0],d[1],b.topLeftInner,b.topRightInner,e.left+c[3].width,e.top+c[0].width),W(g,d[1],d[2],b.topRightInner,b.bottomRightInner,e.left+e.width-c[1].width,e.top+c[0].width),W(g,d[2],d[3],b.bottomRightInner,b.bottomLeftInner,e.left+e.width-c[1].width,e.top+e.height-c[2].width),W(g,d[3],d[0],b.bottomLeftInner,b.topLeftInner,e.left+c[3].width,e.top+e.height-c[2].width);break;default:W(g,d[0],d[1],b.topLeftOuter,b.topRightOuter,e.left,e.top),W(g,d[1],d[2],b.topRightOuter,b.bottomRightOuter,e.left+e.width,e.top),W(g,d[2],d[3],b.bottomRightOuter,b.bottomLeftOuter,e.left+e.width,e.top+e.height),W(g,d[3],d[0],b.bottomLeftOuter,b.topLeftOuter,e.left,e.top+e.height)}return g};var Tb=0,Ub="withCredentials"in new XMLHttpRequest,Vb="crossOrigin"in new Image;Db.prototype.cloneTo=function(a){Db.prototype.cloneTo.call(this,a),a.isPseudoElement=!0,a.before=this.before},Db.prototype=Object.create(F.prototype),Db.prototype.appendToDOM=function(){this.before?this.parent.node.insertBefore(this.node,this.parent.node.firstChild):this.parent.node.appendChild(this.node),this.parent.node.className+=" "+this.getHideClass()},Db.prototype.cleanDOM=function(){this.node.parentNode.removeChild(this.node),this.parent.node.className=this.parent.node.className.replace(this.getHideClass(),"")},Db.prototype.getHideClass=function(){return this["PSEUDO_HIDE_ELEMENT_CLASS_"+(this.before?"BEFORE":"AFTER")]},Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE="___html2canvas___pseudoelement_before",Db.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER="___html2canvas___pseudoelement_after",Eb.prototype.renderImage=function(a,b,c,d){var e=a.cssInt("paddingLeft"),f=a.cssInt("paddingTop"),g=a.cssInt("paddingRight"),h=a.cssInt("paddingBottom"),i=c.borders,j=b.width-(i[1].width+i[3].width+e+g),k=b.height-(i[0].width+i[2].width+f+h);this.drawImage(d,0,0,d.image.width||j,d.image.height||k,b.left+e+i[3].width,b.top+f+i[0].width,j,k)},Eb.prototype.renderBackground=function(a,b,c){b.height>0&&b.width>0&&(this.renderBackgroundColor(a,b),this.renderBackgroundImage(a,b,c))},Eb.prototype.renderBackgroundColor=function(a,b){var c=a.css("backgroundColor");this.isTransparent(c)||this.rectangle(b.left,b.top,b.width,b.height,a.css("backgroundColor"))},Eb.prototype.renderBorders=function(a){a.forEach(this.renderBorder,this)},Eb.prototype.renderBorder=function(a){this.isTransparent(a.color)||null===a.args||this.drawShape(a.args,a.color)},Eb.prototype.renderBackgroundImage=function(a,b,c){var d=a.parseBackgroundImages();d.reverse().forEach(function(d,e,f){switch(d.method){case"url":var g=this.images.get(d.args[0]);g?this.renderBackgroundRepeating(a,b,g,f.length-(e+1),c):E("Error loading background-image",d.args[0]);break;case"linear-gradient":case"gradient":var h=this.images.get(d.value);h?this.renderBackgroundGradient(h,b,c):E("Error loading background-image",d.args[0]);break;case"none":break;default:E("Unknown background-image type",d.args[0])}},this)},Eb.prototype.renderBackgroundRepeating=function(a,b,c,d,e){var f=a.parseBackgroundSize(b,c.image,d),g=a.parseBackgroundPosition(b,c.image,d,f),h=a.parseBackgroundRepeat(d);switch(h){case"repeat-x":case"repeat no-repeat":this.backgroundRepeatShape(c,g,f,b,b.left+e[3],b.top+g.top+e[0],99999,f.height,e);break;case"repeat-y":case"no-repeat repeat":this.backgroundRepeatShape(c,g,f,b,b.left+g.left+e[3],b.top+e[0],f.width,99999,e);break;case"no-repeat":this.backgroundRepeatShape(c,g,f,b,b.left+g.left+e[3],b.top+g.top+e[0],f.width,f.height,e);break;default:this.renderBackgroundRepeat(c,g,f,{top:b.top,left:b.left},e[3],e[0])}},Eb.prototype.isTransparent=function(a){return!a||"transparent"===a||"rgba(0, 0, 0, 0)"===a},Fb.prototype=Object.create(F.prototype),Fb.prototype.getParentStack=function(a){var b=this.parent?this.parent.stack:null;return b?b.ownStacking?b:b.getParentStack(a):a.stack},Gb.prototype.testRangeBounds=function(a){var b,c,d,e,f=!1;return a.createRange&&(b=a.createRange(),b.getBoundingClientRect&&(c=a.createElement("boundtest"),c.style.height="123px",c.style.display="block",a.body.appendChild(c),b.selectNode(c),d=b.getBoundingClientRect(),e=d.height,123===e&&(f=!0),a.body.removeChild(c))),f},Gb.prototype.testCORS=function(){return"undefined"!=typeof(new Image).crossOrigin},Gb.prototype.testSVG=function(){var a=new Image,c=b.createElement("canvas"),d=c.getContext("2d");a.src="data:image/svg+xml,";try{d.drawImage(a,0,0),c.toDataURL()}catch(e){return!1}return!0},Hb.prototype.hasFabric=function(){return html2canvas.fabric?Promise.resolve():Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg"))},Hb.prototype.inlineFormatting=function(a){return/^data:image\/svg\+xml;base64,/.test(a)?this.decode64(this.removeContentType(a)):this.removeContentType(a)},Hb.prototype.removeContentType=function(a){return a.replace(/^data:image\/svg\+xml(;base64)?,/,"")},Hb.prototype.isInline=function(a){return/^data:image\/svg\+xml/i.test(a)},Hb.prototype.createCanvas=function(a){var b=this;return function(c,d){var e=new html2canvas.fabric.StaticCanvas("c");b.image=e.lowerCanvasEl,e.setWidth(d.width).setHeight(d.height).add(html2canvas.fabric.util.groupSVGElements(c,d)).renderAll(),a(e.lowerCanvasEl)}},Hb.prototype.decode64=function(b){return"function"==typeof a.atob?a.atob(b):Ib(b)},Jb.prototype=Object.create(Hb.prototype),Kb.prototype=Object.create(F.prototype),Kb.prototype.applyTextTransform=function(){this.node.data=this.transform(this.parent.css("textTransform"))},Kb.prototype.transform=function(a){var b=this.node.data;switch(a){case"lowercase":return b.toLowerCase();case"capitalize":return b.replace(/(^|\s|:|-|\(|\))([a-z])/g,Lb);case"uppercase":return b.toUpperCase();default:return b}},Mb.prototype=Object.create(A.prototype),Ob.prototype=Object.create(Eb.prototype),Ob.prototype.setFillStyle=function(a){return this.ctx.fillStyle=a,this.ctx},Ob.prototype.rectangle=function(a,b,c,d,e){this.setFillStyle(e).fillRect(a,b,c,d)},Ob.prototype.drawShape=function(a,b){this.shape(a),this.setFillStyle(b).fill()},Ob.prototype.taints=function(a){if(null===a.tainted){this.taintCtx.drawImage(a.image,0,0);try{this.taintCtx.getImageData(0,0,1,1),a.tainted=!1}catch(c){this.taintCtx=b.createElement("canvas").getContext("2d"),a.tainted=!0}}return a.tainted},Ob.prototype.drawImage=function(a,b,c,d,e,f,g,h,i){(!this.taints(a)||this.options.allowTaint)&&this.ctx.drawImage(a.image,b,c,d,e,f,g,h,i)},Ob.prototype.clip=function(a,b,c){this.ctx.save(),a.filter(Pb).forEach(function(a){this.shape(a).clip()},this),b.call(c),this.ctx.restore()},Ob.prototype.shape=function(a){return this.ctx.beginPath(),a.forEach(function(a,b){"rect"===a[0]?this.ctx.rect.apply(this.ctx,a.slice(1)):this.ctx[0===b?"moveTo":a[0]+"To"].apply(this.ctx,a.slice(1))},this),this.ctx.closePath(),this.ctx},Ob.prototype.font=function(a,b,c,d,e,f){this.setFillStyle(a).font=[b,c,d,e,f].join(" ").split(",")[0]},Ob.prototype.fontShadow=function(a,b,c,d){this.setVariable("shadowColor",a).setVariable("shadowOffsetY",b).setVariable("shadowOffsetX",c).setVariable("shadowBlur",d)},Ob.prototype.clearShadow=function(){this.setVariable("shadowColor","rgba(0,0,0,0)")},Ob.prototype.setOpacity=function(a){this.ctx.globalAlpha=a},Ob.prototype.setTransform=function(a){this.ctx.translate(a.origin[0],a.origin[1]),this.ctx.transform.apply(this.ctx,a.matrix),this.ctx.translate(-a.origin[0],-a.origin[1])},Ob.prototype.setVariable=function(a,b){return this.variables[a]!==b&&(this.variables[a]=this.ctx[a]=b),this},Ob.prototype.text=function(a,b,c){this.ctx.fillText(a,b,c)},Ob.prototype.backgroundRepeatShape=function(a,b,c,d,e,f,g,h,i){var j=[["line",Math.round(e),Math.round(f)],["line",Math.round(e+g),Math.round(f)],["line",Math.round(e+g),Math.round(h+f)],["line",Math.round(e),Math.round(h+f)]];this.clip([j],function(){this.renderBackgroundRepeat(a,b,c,d,i[3],i[0])},this)},Ob.prototype.renderBackgroundRepeat=function(a,b,c,d,e,f){var g=Math.round(d.left+b.left+e),h=Math.round(d.top+b.top+f);this.setFillStyle(this.ctx.createPattern(this.resizeImage(a,c),"repeat")),this.ctx.translate(g,h),this.ctx.fill(),this.ctx.translate(-g,-h)},Ob.prototype.renderBackgroundGradient=function(a,b){if(a instanceof D){var c=this.ctx.createLinearGradient(b.left+b.width*a.x0,b.top+b.height*a.y0,b.left+b.width*a.x1,b.top+b.height*a.y1);a.colorStops.forEach(function(a){c.addColorStop(a.stop,a.color)}),this.rectangle(b.left,b.top,b.width,b.height,c)}},Ob.prototype.resizeImage=function(a,c){var d=a.image;if(d.width===c.width&&d.height===c.height)return d;var e,f=b.createElement("canvas");return f.width=c.width,f.height=c.height,e=f.getContext("2d"),e.drawImage(d,0,0,d.width,d.height,0,0,c.width,c.height),f}}).call({},window,document); \ No newline at end of file diff --git a/dist/html2canvas.svg.js b/dist/html2canvas.svg.js index f446066..75ce2e0 100644 --- a/dist/html2canvas.svg.js +++ b/dist/html2canvas.svg.js @@ -7,17696 +7,5 @@ (function(window, document, exports, undefined){ -/* build: `node build.js modules=text,serialization,parser,gradient,pattern,shadow,freedrawing,image_filters,serialization no-es5-compat minifier=uglifyjs` */ -/*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */ - -var fabric = fabric || { version: "1.4.11" }; -if (typeof exports !== 'undefined') { - exports.fabric = fabric; -} - -if (typeof document !== 'undefined' && typeof window !== 'undefined') { - fabric.document = document; - fabric.window = window; -} -else { - // assume we're running under node.js when document/window are not present - fabric.document = require("jsdom") - .jsdom(""); - - fabric.window = fabric.document.createWindow(); -} - -/** - * True when in environment that supports touch events - * @type boolean - */ -fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; - -/** - * True when in environment that's probably Node.js - * @type boolean - */ -fabric.isLikelyNode = typeof Buffer !== 'undefined' && - typeof window === 'undefined'; - - -/** - * Attributes parsed from all SVG elements - * @type array - */ -fabric.SHARED_ATTRIBUTES = [ - "display", - "transform", - "fill", "fill-opacity", "fill-rule", - "opacity", - "stroke", "stroke-dasharray", "stroke-linecap", - "stroke-linejoin", "stroke-miterlimit", - "stroke-opacity", "stroke-width" -]; - -/** - * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. - */ -fabric.DPI = 96; - - -/*! - * Copyright (c) 2009 Simo Kinnunen. - * Licensed under the MIT license. - */ - -var Cufon = (function() { - - /** @ignore */ - var api = function() { - return api.replace.apply(null, arguments); - }; - - /** @ignore */ - var DOM = api.DOM = { - - ready: (function() { - - var complete = false, readyStatus = { loaded: 1, complete: 1 }; - - var queue = [], /** @ignore */ 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); - }; - - })() - - }; - - /** @ignore */ - var CSS = api.CSS = /** @ignore */ { - - /** @ignore */ - Size: function(value, base) { - - this.value = parseFloat(value); - this.unit = String(value).match(/[a-z%]*$/)[0] || 'px'; - - /** @ignore */ - this.convert = function(value) { - return value / base * this.value; - }; - - /** @ignore */ - this.convertFrom = function(value) { - return value / this.value * base; - }; - - /** @ignore */ - this.toString = function() { - return this.value + this.unit; - }; - - }, - - /** @ignore */ - 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 - 2014-02-04 - - 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. - -if (typeof JSON !== 'object') { - 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 () { - - 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 () { - return this.valueOf(); - }; - } - - var cx, - escapable, - gap, - indent, - meta, - 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') { - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; - 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') { - cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - 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'); - }; - } -}()); - - -(function(){ - - /** - * @private - * @param {String} eventName - * @param {Function} handler - */ - function _removeEventListener(eventName, handler) { - if (!this.__eventListeners[eventName]) { - return; - } - - if (handler) { - fabric.util.removeFromArray(this.__eventListeners[eventName], handler); - } - else { - this.__eventListeners[eventName].length = 0; - } - } - - /** - * Observes specified event - * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) - * @memberOf fabric.Observable - * @alias on - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function that receives a notification when an event of the specified type occurs - * @return {Self} thisArg - * @chainable - */ - function observe(eventName, handler) { - if (!this.__eventListeners) { - this.__eventListeners = { }; - } - // one object with key/value pairs was passed - if (arguments.length === 1) { - for (var prop in eventName) { - this.on(prop, eventName[prop]); - } - } - else { - if (!this.__eventListeners[eventName]) { - this.__eventListeners[eventName] = [ ]; - } - this.__eventListeners[eventName].push(handler); - } - return this; - } - - /** - * Stops event observing for a particular event handler. Calling this method - * without arguments removes all handlers for all events - * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) - * @memberOf fabric.Observable - * @alias off - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function to be deleted from EventListeners - * @return {Self} thisArg - * @chainable - */ - function stopObserving(eventName, handler) { - if (!this.__eventListeners) { - return; - } - - // remove all key/value pairs (event name -> event handler) - if (arguments.length === 0) { - this.__eventListeners = { }; - } - // one object with key/value pairs was passed - else if (arguments.length === 1 && typeof arguments[0] === 'object') { - for (var prop in eventName) { - _removeEventListener.call(this, prop, eventName[prop]); - } - } - else { - _removeEventListener.call(this, eventName, handler); - } - return this; - } - - /** - * Fires event with an optional options object - * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) - * @memberOf fabric.Observable - * @alias trigger - * @param {String} eventName Event name to fire - * @param {Object} [options] Options object - * @return {Self} thisArg - * @chainable - */ - function fire(eventName, options) { - if (!this.__eventListeners) { - return; - } - - var listenersForEvent = this.__eventListeners[eventName]; - if (!listenersForEvent) { - return; - } - - for (var i = 0, len = listenersForEvent.length; i < len; i++) { - // avoiding try/catch for perf. reasons - listenersForEvent[i].call(this, options || { }); - } - return this; - } - - /** - * @namespace fabric.Observable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events} - * @see {@link http://fabricjs.com/events/|Events demo} - */ - fabric.Observable = { - observe: observe, - stopObserving: stopObserving, - fire: fire, - - on: observe, - off: stopObserving, - trigger: fire - }; -})(); - - -/** - * @namespace fabric.Collection - */ -fabric.Collection = { - - /** - * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * Objects should be instances of (or inherit from) fabric.Object - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - */ - add: function () { - this._objects.push.apply(this._objects, arguments); - for (var i = 0, length = arguments.length; i < length; i++) { - this._onObjectAdded(arguments[i]); - } - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) - * An object should be an instance of (or inherit from) fabric.Object - * @param {Object} object Object to insert - * @param {Number} index Index to insert object at - * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs - * @return {Self} thisArg - * @chainable - */ - insertAt: function (object, index, nonSplicing) { - var objects = this.getObjects(); - if (nonSplicing) { - objects[index] = object; - } - else { - objects.splice(index, 0, object); - } - this._onObjectAdded(object); - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - * @chainable - */ - remove: function() { - var objects = this.getObjects(), - index; - - for (var i = 0, length = arguments.length; i < length; i++) { - index = objects.indexOf(arguments[i]); - - // only call onObjectRemoved if an object was actually removed - if (index !== -1) { - objects.splice(index, 1); - this._onObjectRemoved(arguments[i]); - } - } - - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Executes given function for each object in this group - * @param {Function} callback - * Callback invoked with current object as first argument, - * index - as second and an array of all objects - as third. - * Iteration happens in reverse order (for performance reasons). - * Callback is invoked in a context of Global Object (e.g. `window`) - * when no `context` argument is given - * - * @param {Object} context Context (aka thisObject) - * @return {Self} thisArg - */ - forEachObject: function(callback, context) { - var objects = this.getObjects(), - i = objects.length; - while (i--) { - callback.call(context, objects[i], i, objects); - } - return this; - }, - - /** - * Returns an array of children objects of this instance - * Type parameter introduced in 1.3.10 - * @param {String} [type] When specified, only objects of this type are returned - * @return {Array} - */ - getObjects: function(type) { - if (typeof type === 'undefined') { - return this._objects; - } - return this._objects.filter(function(o) { - return o.type === type; - }); - }, - - /** - * Returns object at specified index - * @param {Number} index - * @return {Self} thisArg - */ - item: function (index) { - return this.getObjects()[index]; - }, - - /** - * Returns true if collection contains no objects - * @return {Boolean} true if collection is empty - */ - isEmpty: function () { - return this.getObjects().length === 0; - }, - - /** - * Returns a size of a collection (i.e: length of an array containing its objects) - * @return {Number} Collection size - */ - size: function() { - return this.getObjects().length; - }, - - /** - * Returns true if collection contains an object - * @param {Object} object Object to check against - * @return {Boolean} `true` if collection contains an object - */ - contains: function(object) { - return this.getObjects().indexOf(object) > -1; - }, - - /** - * Returns number representation of a collection complexity - * @return {Number} complexity - */ - complexity: function () { - return this.getObjects().reduce(function (memo, current) { - memo += current.complexity ? current.complexity() : 0; - return memo; - }, 0); - } -}; - - -(function(global) { - - var sqrt = Math.sqrt, - atan2 = Math.atan2, - PiBy180 = Math.PI / 180; - - /** - * @namespace fabric.util - */ - fabric.util = { - - /** - * Removes value from an array. - * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` - * @static - * @memberOf fabric.util - * @param {Array} array - * @param {Any} value - * @return {Array} original array - */ - removeFromArray: function(array, value) { - var idx = array.indexOf(value); - if (idx !== -1) { - array.splice(idx, 1); - } - return array; - }, - - /** - * Returns random number between 2 specified ones. - * @static - * @memberOf fabric.util - * @param {Number} min lower limit - * @param {Number} max upper limit - * @return {Number} random value (between min and max) - */ - getRandomInt: function(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - }, - - /** - * Transforms degrees to radians. - * @static - * @memberOf fabric.util - * @param {Number} degrees value in degrees - * @return {Number} value in radians - */ - degreesToRadians: function(degrees) { - return degrees * PiBy180; - }, - - /** - * Transforms radians to degrees. - * @static - * @memberOf fabric.util - * @param {Number} radians value in radians - * @return {Number} value in degrees - */ - radiansToDegrees: function(radians) { - return radians / PiBy180; - }, - - /** - * Rotates `point` around `origin` with `radians` - * @static - * @memberOf fabric.util - * @param {fabric.Point} point The point to rotate - * @param {fabric.Point} origin The origin of the rotation - * @param {Number} radians The radians of the angle for the rotation - * @return {fabric.Point} The new rotated point - */ - rotatePoint: function(point, origin, radians) { - var sin = Math.sin(radians), - cos = Math.cos(radians); - - point.subtractEquals(origin); - - var rx = point.x * cos - point.y * sin, - ry = point.x * sin + point.y * cos; - - return new fabric.Point(rx, ry).addEquals(origin); - }, - - /** - * Apply transform t to point p - * @static - * @memberOf fabric.util - * @param {fabric.Point} p The point to transform - * @param {Array} t The transform - * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied - * @return {fabric.Point} The transformed point - */ - transformPoint: function(p, t, ignoreOffset) { - if (ignoreOffset) { - return new fabric.Point( - t[0] * p.x + t[1] * p.y, - t[2] * p.x + t[3] * p.y - ); - } - return new fabric.Point( - t[0] * p.x + t[1] * p.y + t[4], - t[2] * p.x + t[3] * p.y + t[5] - ); - }, - - /** - * Invert transformation t - * @static - * @memberOf fabric.util - * @param {Array} t The transform - * @return {Array} The inverted transform - */ - invertTransform: function(t) { - var r = t.slice(), - a = 1 / (t[0] * t[3] - t[1] * t[2]); - r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; - var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r); - r[4] = -o.x; - r[5] = -o.y; - return r; - }, - - /** - * A wrapper around Number#toFixed, which contrary to native method returns number, not string. - * @static - * @memberOf fabric.util - * @param {Number|String} number number to operate on - * @param {Number} fractionDigits number of fraction digits to "leave" - * @return {Number} - */ - toFixed: function(number, fractionDigits) { - return parseFloat(Number(number).toFixed(fractionDigits)); - }, - - /** - * Converts from attribute value to pixel value if applicable. - * Returns converted pixels or original value not converted. - * @param {Number|String} value number to operate on - * @return {Number|String} - */ - parseUnit: function(value) { - var unit = /\D{0,2}$/.exec(value), - number = parseFloat(value); - - switch (unit[0]) { - case 'mm': - return number * fabric.DPI / 25.4; - - case 'cm': - return number * fabric.DPI / 2.54; - - case 'in': - return number * fabric.DPI; - - case 'pt': - return number * fabric.DPI / 72; // or * 4 / 3 - - case 'pc': - return number * fabric.DPI / 72 * 12; // or * 16 - - default: - return number; - } - }, - - /** - * Function which always returns `false`. - * @static - * @memberOf fabric.util - * @return {Boolean} - */ - falseFunction: function() { - return false; - }, - - /** - * Returns klass "Class" object of given namespace - * @memberOf fabric.util - * @param {String} type Type of object (eg. 'circle') - * @param {String} namespace Namespace to get klass "Class" object from - * @return {Object} klass "Class" - */ - getKlass: function(type, namespace) { - // capitalize first letter only - type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); - return fabric.util.resolveNamespace(namespace)[type]; - }, - - /** - * Returns object of given namespace - * @memberOf fabric.util - * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' - * @return {Object} Object for given namespace (default fabric) - */ - resolveNamespace: function(namespace) { - if (!namespace) { - return fabric; - } - - var parts = namespace.split('.'), - len = parts.length, - obj = global || fabric.window; - - for (var i = 0; i < len; ++i) { - obj = obj[parts[i]]; - } - - return obj; - }, - - /** - * Loads image element from given url and passes it to a callback - * @memberOf fabric.util - * @param {String} url URL representing an image - * @param {Function} callback Callback; invoked with loaded image - * @param {Any} [context] Context to invoke callback in - * @param {Object} [crossOrigin] crossOrigin value to set image element to - */ - loadImage: function(url, callback, context, crossOrigin) { - if (!url) { - callback && callback.call(context, url); - return; - } - - var img = fabric.util.createImage(); - - /** @ignore */ - img.onload = function () { - callback && callback.call(context, img); - img = img.onload = img.onerror = null; - }; - - /** @ignore */ - img.onerror = function() { - fabric.log('Error loading ' + img.src); - callback && callback.call(context, null, true); - img = img.onload = img.onerror = null; - }; - - // data-urls appear to be buggy with crossOrigin - // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 - // see https://code.google.com/p/chromium/issues/detail?id=315152 - // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 - if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') { - img.crossOrigin = crossOrigin; - } - - img.src = url; - }, - - /** - * Creates corresponding fabric instances from their object representations - * @static - * @memberOf fabric.util - * @param {Array} objects Objects to enliven - * @param {Function} callback Callback to invoke when all objects are created - * @param {String} namespace Namespace to get klass "Class" object from - * @param {Function} reviver Method for further parsing of object elements, - * called after each fabric object created. - */ - enlivenObjects: function(objects, callback, namespace, reviver) { - objects = objects || [ ]; - - function onLoaded() { - if (++numLoadedObjects === numTotalObjects) { - callback && callback(enlivenedObjects); - } - } - - var enlivenedObjects = [ ], - numLoadedObjects = 0, - numTotalObjects = objects.length; - - if (!numTotalObjects) { - callback && callback(enlivenedObjects); - return; - } - - objects.forEach(function (o, index) { - // if sparse array - if (!o || !o.type) { - onLoaded(); - return; - } - var klass = fabric.util.getKlass(o.type, namespace); - if (klass.async) { - klass.fromObject(o, function (obj, error) { - if (!error) { - enlivenedObjects[index] = obj; - reviver && reviver(o, enlivenedObjects[index]); - } - onLoaded(); - }); - } - else { - enlivenedObjects[index] = klass.fromObject(o); - reviver && reviver(o, enlivenedObjects[index]); - onLoaded(); - } - }); - }, - - /** - * Groups SVG elements (usually those retrieved from SVG document) - * @static - * @memberOf fabric.util - * @param {Array} elements SVG elements to group - * @param {Object} [options] Options object - * @return {fabric.Object|fabric.PathGroup} - */ - groupSVGElements: function(elements, options, path) { - var object; - - object = new fabric.PathGroup(elements, options); - - if (typeof path !== 'undefined') { - object.setSourcePath(path); - } - return object; - }, - - /** - * Populates an object with properties of another object - * @static - * @memberOf fabric.util - * @param {Object} source Source object - * @param {Object} destination Destination object - * @return {Array} properties Propertie names to include - */ - populateWithProperties: function(source, destination, properties) { - if (properties && Object.prototype.toString.call(properties) === '[object Array]') { - for (var i = 0, len = properties.length; i < len; i++) { - if (properties[i] in source) { - destination[properties[i]] = source[properties[i]]; - } - } - } - }, - - /** - * Draws a dashed line between two points - * - * This method is used to draw dashed line around selection area. - * See dotted stroke in canvas - * - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x start x coordinate - * @param {Number} y start y coordinate - * @param {Number} x2 end x coordinate - * @param {Number} y2 end y coordinate - * @param {Array} da dash array pattern - */ - drawDashedLine: function(ctx, x, y, x2, y2, da) { - var dx = x2 - x, - dy = y2 - y, - len = sqrt(dx * dx + dy * dy), - rot = atan2(dy, dx), - dc = da.length, - di = 0, - draw = true; - - ctx.save(); - ctx.translate(x, y); - ctx.moveTo(0, 0); - ctx.rotate(rot); - - x = 0; - while (len > x) { - x += da[di++ % dc]; - if (x > len) { - x = len; - } - ctx[draw ? 'lineTo' : 'moveTo'](x, 0); - draw = !draw; - } - - ctx.restore(); - }, - - /** - * Creates canvas element and initializes it via excanvas if necessary - * @static - * @memberOf fabric.util - * @param {CanvasElement} [canvasEl] optional canvas element to initialize; - * when not given, element is created implicitly - * @return {CanvasElement} initialized canvas element - */ - createCanvasElement: function(canvasEl) { - canvasEl || (canvasEl = fabric.document.createElement('canvas')); - //jscs:disable requireCamelCaseOrUpperCaseIdentifiers - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } - //jscs:enable requireCamelCaseOrUpperCaseIdentifiers - return canvasEl; - }, - - /** - * Creates image element (works on client and node) - * @static - * @memberOf fabric.util - * @return {HTMLImageElement} HTML image element - */ - createImage: function() { - return fabric.isLikelyNode - ? new (require('canvas').Image)() - : fabric.document.createElement('img'); - }, - - /** - * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array - * @static - * @memberOf fabric.util - * @param {Object} klass "Class" to create accessors for - */ - createAccessors: function(klass) { - var proto = klass.prototype; - - for (var i = proto.stateProperties.length; i--; ) { - - var propName = proto.stateProperties[i], - capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), - setterName = 'set' + capitalizedPropName, - getterName = 'get' + capitalizedPropName; - - // using `new Function` for better introspection - if (!proto[getterName]) { - proto[getterName] = (function(property) { - return new Function('return this.get("' + property + '")'); - })(propName); - } - if (!proto[setterName]) { - proto[setterName] = (function(property) { - return new Function('value', 'return this.set("' + property + '", value)'); - })(propName); - } - } - }, - - /** - * @static - * @memberOf fabric.util - * @param {fabric.Object} receiver Object implementing `clipTo` method - * @param {CanvasRenderingContext2D} ctx Context to clip - */ - clipContext: function(receiver, ctx) { - ctx.save(); - ctx.beginPath(); - receiver.clipTo(ctx); - ctx.clip(); - }, - - /** - * Multiply matrix A by matrix B to nest transformations - * @static - * @memberOf fabric.util - * @param {Array} matrixA First transformMatrix - * @param {Array} matrixB Second transformMatrix - * @return {Array} The product of the two transform matrices - */ - multiplyTransformMatrices: function(matrixA, matrixB) { - // Matrix multiply matrixA * matrixB - var a = [ - [matrixA[0], matrixA[2], matrixA[4]], - [matrixA[1], matrixA[3], matrixA[5]], - [0, 0, 1 ] - ], - - b = [ - [matrixB[0], matrixB[2], matrixB[4]], - [matrixB[1], matrixB[3], matrixB[5]], - [0, 0, 1 ] - ], - - result = []; - - for (var r = 0; r < 3; r++) { - result[r] = []; - for (var c = 0; c < 3; c++) { - var sum = 0; - for (var k = 0; k < 3; k++) { - sum += a[r][k] * b[k][c]; - } - - result[r][c] = sum; - } - } - - return [ - result[0][0], - result[1][0], - result[0][1], - result[1][1], - result[0][2], - result[1][2] - ]; - }, - - /** - * Returns string representation of function body - * @param {Function} fn Function to get body of - * @return {String} Function body - */ - getFunctionBody: function(fn) { - return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; - }, - - /** - * Returns true if context has transparent pixel - * at specified location (taking tolerance into account) - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x x coordinate - * @param {Number} y y coordinate - * @param {Number} tolerance Tolerance - */ - isTransparent: function(ctx, x, y, tolerance) { - - // If tolerance is > 0 adjust start coords to take into account. - // If moves off Canvas fix to 0 - if (tolerance > 0) { - if (x > tolerance) { - x -= tolerance; - } - else { - x = 0; - } - if (y > tolerance) { - y -= tolerance; - } - else { - y = 0; - } - } - - var _isTransparent = true, - imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); - - // Split image data - for tolerance > 1, pixelDataSize = 4; - for (var i = 3, l = imageData.data.length; i < l; i += 4) { - var temp = imageData.data[i]; - _isTransparent = temp <= 0; - if (_isTransparent === false) { - break; // Stop if colour found - } - } - - imageData = null; - - return _isTransparent; - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var arcToSegmentsCache = { }, - segmentToBezierCache = { }, - _join = Array.prototype.join; - - /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp - * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here - * http://mozilla.org/MPL/2.0/ - */ - function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { - var argsString = _join.call(arguments); - if (arcToSegmentsCache[argsString]) { - return arcToSegmentsCache[argsString]; - } - - var PI = Math.PI, th = rotateX * (PI / 180), - sinTh = Math.sin(th), - cosTh = Math.cos(th), - fromX = 0, fromY = 0; - - rx = Math.abs(rx); - ry = Math.abs(ry); - - var px = -cosTh * toX - sinTh * toY, - py = -cosTh * toY + sinTh * toX, - rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, - pl = 4 * rx2 * ry2 - rx2 * py2 - ry2 * px2, - root = 0; - - if (pl < 0) { - var s = Math.sqrt(1 - 0.25 * pl/(rx2 * ry2)); - rx *= s; - ry *= s; - } - else { - root = (large === sweep ? -0.5 : 0.5) * - Math.sqrt( pl /(rx2 * py2 + ry2 * px2)); - } - - var cx = root * rx * py / ry, - cy = -root * ry * px / rx, - cx1 = cosTh * cx - sinTh * cy + toX / 2, - cy1 = sinTh * cx + cosTh * cy + toY / 2, - mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), - dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); - - if (sweep === 0 && dtheta > 0) { - dtheta -= 2 * PI; - } - else if (sweep === 1 && dtheta < 0) { - dtheta += 2 * PI; - } - - // Convert into cubic bezier segments <= 90deg - var segments = Math.ceil(Math.abs(dtheta / (PI * 0.5))), - result = [], mDelta = dtheta / segments, - mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), - th3 = mTheta + mDelta; - - for (var i = 0; i < segments; i++) { - result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); - fromX = result[i][4]; - fromY = result[i][5]; - mTheta += mDelta; - th3 += mDelta; - } - arcToSegmentsCache[argsString] = result; - return result; - } - - function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { - var argsString2 = _join.call(arguments); - if (segmentToBezierCache[argsString2]) { - return segmentToBezierCache[argsString2]; - } - - var costh2 = Math.cos(th2), - sinth2 = Math.sin(th2), - costh3 = Math.cos(th3), - sinth3 = Math.sin(th3), - toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, - toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, - cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2), - cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2), - cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), - cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); - - segmentToBezierCache[argsString2] = [ - cp1X, cp1Y, - cp2X, cp2Y, - toX, toY - ]; - return segmentToBezierCache[argsString2]; - } - - /* - * Private - */ - function calcVectorAngle(ux, uy, vx, vy) { - var ta = Math.atan2(uy, ux), - tb = Math.atan2(vy, vx); - if (tb >= ta) { - return tb - ta; - } - else { - return 2 * Math.PI - (ta - tb); - } - } - - /** - * Draws arc - * @param {CanvasRenderingContext2D} ctx - * @param {Number} fx - * @param {Number} fy - * @param {Array} coords - */ - fabric.util.drawArc = function(ctx, fx, fy, coords) { - var rx = coords[0], - ry = coords[1], - rot = coords[2], - large = coords[3], - sweep = coords[4], - tx = coords[5], - ty = coords[6], - segs = [[ ], [ ], [ ], [ ]], - segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); - - for (var i = 0, len = segsNorm.length; i < len; i++) { - segs[i][0] = segsNorm[i][0] + fx; - segs[i][1] = segsNorm[i][1] + fy; - segs[i][2] = segsNorm[i][2] + fx; - segs[i][3] = segsNorm[i][3] + fy; - segs[i][4] = segsNorm[i][4] + fx; - segs[i][5] = segsNorm[i][5] + fy; - ctx.bezierCurveTo.apply(ctx, segs[i]); - } - }; -})(); - - -(function() { - - var slice = Array.prototype.slice; - - - - /** - * Invokes method on all items in a given array - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} method Name of a method to invoke - * @return {Array} - */ - function invoke(array, method) { - var args = slice.call(arguments, 2), result = [ ]; - for (var i = 0, len = array.length; i < len; i++) { - result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); - } - return result; - } - - /** - * Finds maximum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {Any} - */ - function max(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 >= value2; - }); - } - - /** - * Finds minimum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {Any} - */ - function min(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 < value2; - }); - } - - /** - * @private - */ - function find(array, byProperty, condition) { - if (!array || array.length === 0) { - return; - } - - var i = array.length - 1, - result = byProperty ? array[i][byProperty] : array[i]; - if (byProperty) { - while (i--) { - if (condition(array[i][byProperty], result)) { - result = array[i][byProperty]; - } - } - } - else { - while (i--) { - if (condition(array[i], result)) { - result = array[i]; - } - } - } - return result; - } - - /** - * @namespace fabric.util.array - */ - fabric.util.array = { - invoke: invoke, - min: min, - max: max - }; - -})(); - - -(function(){ - - /** - * Copies all enumerable properties of one object to another - * @memberOf fabric.util.object - * @param {Object} destination Where to copy to - * @param {Object} source Where to copy from - * @return {Object} - */ - function extend(destination, source) { - // JScript DontEnum bug is not taken care of - for (var property in source) { - destination[property] = source[property]; - } - return destination; - } - - /** - * Creates an empty object and copies all enumerable properties of another object to it - * @memberOf fabric.util.object - * @param {Object} object Object to clone - * @return {Object} - */ - function clone(object) { - return extend({ }, object); - } - - /** @namespace fabric.util.object */ - fabric.util.object = { - extend: extend, - clone: clone - }; - -})(); - - -(function() { - - - - /** - * Camelizes a string - * @memberOf fabric.util.string - * @param {String} string String to camelize - * @return {String} Camelized version of a string - */ - function camelize(string) { - return string.replace(/-+(.)?/g, function(match, character) { - return character ? character.toUpperCase() : ''; - }); - } - - /** - * Capitalizes a string - * @memberOf fabric.util.string - * @param {String} string String to capitalize - * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized - * and other letters stay untouched, if false first letter is capitalized - * and other letters are converted to lowercase. - * @return {String} Capitalized version of a string - */ - function capitalize(string, firstLetterOnly) { - return string.charAt(0).toUpperCase() + - (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); - } - - /** - * Escapes XML in a string - * @memberOf fabric.util.string - * @param {String} string String to escape - * @return {String} Escaped version of a string - */ - function escapeXml(string) { - return string.replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>'); - } - - /** - * String utilities - * @namespace fabric.util.string - */ - fabric.util.string = { - camelize: camelize, - capitalize: capitalize, - escapeXml: escapeXml - }; -}()); - - - - - -(function() { - - var slice = Array.prototype.slice, emptyFunction = function() { }, - - IS_DONTENUM_BUGGY = (function(){ - for (var p in { toString: 1 }) { - if (p === 'toString') { - return false; - } - } - return true; - })(), - - /** @ignore */ - addMethods = function(klass, source, parent) { - for (var property in source) { - - if (property in klass.prototype && - typeof klass.prototype[property] === 'function' && - (source[property] + '').indexOf('callSuper') > -1) { - - klass.prototype[property] = (function(property) { - return function() { - - var superclass = this.constructor.superclass; - this.constructor.superclass = parent; - var returnValue = source[property].apply(this, arguments); - this.constructor.superclass = superclass; - - if (property !== 'initialize') { - return returnValue; - } - }; - })(property); - } - else { - klass.prototype[property] = source[property]; - } - - if (IS_DONTENUM_BUGGY) { - if (source.toString !== Object.prototype.toString) { - klass.prototype.toString = source.toString; - } - if (source.valueOf !== Object.prototype.valueOf) { - klass.prototype.valueOf = source.valueOf; - } - } - } - }; - - function Subclass() { } - - function callSuper(methodName) { - var fn = this.constructor.superclass.prototype[methodName]; - return (arguments.length > 1) - ? fn.apply(this, slice.call(arguments, 1)) - : fn.call(this); - } - - /** - * Helper for creation of "classes". - * @memberOf fabric.util - * @param {Function} [parent] optional "Class" to inherit from - * @param {Object} [properties] Properties shared by all instances of this class - * (be careful modifying objects defined here as this would affect all instances) - */ - function createClass() { - var parent = null, - properties = slice.call(arguments, 0); - - if (typeof properties[0] === 'function') { - parent = properties.shift(); - } - function klass() { - this.initialize.apply(this, arguments); - } - - klass.superclass = parent; - klass.subclasses = [ ]; - - if (parent) { - Subclass.prototype = parent.prototype; - klass.prototype = new Subclass(); - parent.subclasses.push(klass); - } - for (var i = 0, length = properties.length; i < length; i++) { - addMethods(klass, properties[i], parent); - } - if (!klass.prototype.initialize) { - klass.prototype.initialize = emptyFunction; - } - klass.prototype.constructor = klass; - klass.prototype.callSuper = callSuper; - return klass; - } - - fabric.util.createClass = createClass; -})(); - - -(function () { - - var unknown = 'unknown'; - - /* EVENT HANDLING */ - - function areHostMethods(object) { - var methodNames = Array.prototype.slice.call(arguments, 1), - t, i, len = methodNames.length; - for (i = 0; i < len; i++) { - t = typeof object[methodNames[i]]; - if (!(/^(?:function|object|unknown)$/).test(t)) { - return false; - } - } - return true; - } - - /** @ignore */ - var getElement, - setElement, - getUniqueId = (function () { - var uid = 0; - return function (element) { - return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); - }; - })(); - - (function () { - var elements = { }; - /** @ignore */ - getElement = function (uid) { - return elements[uid]; - }; - /** @ignore */ - setElement = function (uid, element) { - elements[uid] = element; - }; - })(); - - function createListener(uid, handler) { - return { - handler: handler, - wrappedHandler: createWrappedHandler(uid, handler) - }; - } - - function createWrappedHandler(uid, handler) { - return function (e) { - handler.call(getElement(uid), e || fabric.window.event); - }; - } - - function createDispatcher(uid, eventName) { - return function (e) { - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - handlersForEvent[i].call(this, e || fabric.window.event); - } - } - }; - } - - var shouldUseAddListenerRemoveListener = ( - areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && - areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), - - shouldUseAttachEventDetachEvent = ( - areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && - areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), - - // IE branch - listeners = { }, - - // DOM L0 branch - handlers = { }, - - addListener, removeListener; - - if (shouldUseAddListenerRemoveListener) { - /** @ignore */ - addListener = function (element, eventName, handler) { - element.addEventListener(eventName, handler, false); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - element.removeEventListener(eventName, handler, false); - }; - } - - else if (shouldUseAttachEventDetachEvent) { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - setElement(uid, element); - if (!listeners[uid]) { - listeners[uid] = { }; - } - if (!listeners[uid][eventName]) { - listeners[uid][eventName] = [ ]; - - } - var listener = createListener(uid, handler); - listeners[uid][eventName].push(listener); - element.attachEvent('on' + eventName, listener.wrappedHandler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element), listener; - if (listeners[uid] && listeners[uid][eventName]) { - for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { - listener = listeners[uid][eventName][i]; - if (listener && listener.handler === handler) { - element.detachEvent('on' + eventName, listener.wrappedHandler); - listeners[uid][eventName][i] = null; - } - } - } - }; - } - else { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (!handlers[uid]) { - handlers[uid] = { }; - } - if (!handlers[uid][eventName]) { - handlers[uid][eventName] = [ ]; - var existingHandler = element['on' + eventName]; - if (existingHandler) { - handlers[uid][eventName].push(existingHandler); - } - element['on' + eventName] = createDispatcher(uid, eventName); - } - handlers[uid][eventName].push(handler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - if (handlersForEvent[i] === handler) { - handlersForEvent.splice(i, 1); - } - } - } - }; - } - - /** - * Adds an event listener to an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.addListener = addListener; - - /** - * Removes an event listener from an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.removeListener = removeListener; - - /** - * Cross-browser wrapper for getting event's coordinates - * @memberOf fabric.util - * @param {Event} event Event object - * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn - */ - function getPointer(event, upperCanvasEl) { - event || (event = fabric.window.event); - - var element = event.target || - (typeof event.srcElement !== unknown ? event.srcElement : null), - - scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); - - return { - x: pointerX(event) + scroll.left, - y: pointerY(event) + scroll.top - }; - } - - var pointerX = function(event) { - // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) - // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] - // need to investigate later - return (typeof event.clientX !== unknown ? event.clientX : 0); - }, - - pointerY = function(event) { - return (typeof event.clientY !== unknown ? event.clientY : 0); - }; - - function _getPointer(event, pageProp, clientProp) { - var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; - - return (event[touchProp] && event[touchProp][0] - ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) - || event[clientProp] - : event[clientProp]); - } - - if (fabric.isTouchSupported) { - pointerX = function(event) { - return _getPointer(event, 'pageX', 'clientX'); - }; - pointerY = function(event) { - return _getPointer(event, 'pageY', 'clientY'); - }; - } - - fabric.util.getPointer = getPointer; - - fabric.util.object.extend(fabric.util, fabric.Observable); - -})(); - - -(function () { - - /** - * Cross-browser wrapper for setting element's style - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {Object} styles - * @return {HTMLElement} Element that was passed as a first argument - */ - function setStyle(element, styles) { - var elementStyle = element.style; - if (!elementStyle) { - return element; - } - if (typeof styles === 'string') { - element.style.cssText += ';' + styles; - return styles.indexOf('opacity') > -1 - ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) - : element; - } - for (var property in styles) { - if (property === 'opacity') { - setOpacity(element, styles[property]); - } - else { - var normalizedProperty = (property === 'float' || property === 'cssFloat') - ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') - : property; - elementStyle[normalizedProperty] = styles[property]; - } - } - return element; - } - - var parseEl = fabric.document.createElement('div'), - supportsOpacity = typeof parseEl.style.opacity === 'string', - supportsFilters = typeof parseEl.style.filter === 'string', - reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, - - /** @ignore */ - setOpacity = function (element) { return element; }; - - if (supportsOpacity) { - /** @ignore */ - setOpacity = function(element, value) { - element.style.opacity = value; - return element; - }; - } - else if (supportsFilters) { - /** @ignore */ - setOpacity = function(element, value) { - var es = element.style; - if (element.currentStyle && !element.currentStyle.hasLayout) { - es.zoom = 1; - } - if (reOpacity.test(es.filter)) { - value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); - es.filter = es.filter.replace(reOpacity, value); - } - else { - es.filter += ' alpha(opacity=' + (value * 100) + ')'; - } - return element; - }; - } - - fabric.util.setStyle = setStyle; - -})(); - - -(function() { - - var _slice = Array.prototype.slice; - - /** - * Takes id and returns an element with that id (if one exists in a document) - * @memberOf fabric.util - * @param {String|HTMLElement} id - * @return {HTMLElement|null} - */ - function getById(id) { - return typeof id === 'string' ? fabric.document.getElementById(id) : id; - } - - var sliceCanConvertNodelists, - /** - * Converts an array-like object (e.g. arguments or NodeList) to an array - * @memberOf fabric.util - * @param {Object} arrayLike - * @return {Array} - */ - toArray = function(arrayLike) { - return _slice.call(arrayLike, 0); - }; - - try { - sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; - } - catch (err) { } - - if (!sliceCanConvertNodelists) { - toArray = function(arrayLike) { - var arr = new Array(arrayLike.length), i = arrayLike.length; - while (i--) { - arr[i] = arrayLike[i]; - } - return arr; - }; - } - - /** - * Creates specified element with specified attributes - * @memberOf fabric.util - * @param {String} tagName Type of an element to create - * @param {Object} [attributes] Attributes to set on an element - * @return {HTMLElement} Newly created element - */ - function makeElement(tagName, attributes) { - var el = fabric.document.createElement(tagName); - for (var prop in attributes) { - if (prop === 'class') { - el.className = attributes[prop]; - } - else if (prop === 'for') { - el.htmlFor = attributes[prop]; - } - else { - el.setAttribute(prop, attributes[prop]); - } - } - return el; - } - - /** - * Adds class to an element - * @memberOf fabric.util - * @param {HTMLElement} element Element to add class to - * @param {String} className Class to add to an element - */ - function addClass(element, className) { - if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { - element.className += (element.className ? ' ' : '') + className; - } - } - - /** - * Wraps element with another element - * @memberOf fabric.util - * @param {HTMLElement} element Element to wrap - * @param {HTMLElement|String} wrapper Element to wrap with - * @param {Object} [attributes] Attributes to set on a wrapper - * @return {HTMLElement} wrapper - */ - function wrapElement(element, wrapper, attributes) { - if (typeof wrapper === 'string') { - wrapper = makeElement(wrapper, attributes); - } - if (element.parentNode) { - element.parentNode.replaceChild(wrapper, element); - } - wrapper.appendChild(element); - return wrapper; - } - - /** - * Returns element scroll offsets - * @memberOf fabric.util - * @param {HTMLElement} element Element to operate on - * @param {HTMLElement} upperCanvasEl Upper canvas element - * @return {Object} Object with left/top values - */ - function getScrollLeftTop(element, upperCanvasEl) { - - var firstFixedAncestor, - origElement, - left = 0, - top = 0, - docElement = fabric.document.documentElement, - body = fabric.document.body || { - scrollLeft: 0, scrollTop: 0 - }; - - origElement = element; - - while (element && element.parentNode && !firstFixedAncestor) { - - element = element.parentNode; - - if (element !== fabric.document && - fabric.util.getElementStyle(element, 'position') === 'fixed') { - firstFixedAncestor = element; - } - - if (element !== fabric.document && - origElement !== upperCanvasEl && - fabric.util.getElementStyle(element, 'position') === 'absolute') { - left = 0; - top = 0; - } - else if (element === fabric.document) { - left = body.scrollLeft || docElement.scrollLeft || 0; - top = body.scrollTop || docElement.scrollTop || 0; - } - else { - left += element.scrollLeft || 0; - top += element.scrollTop || 0; - } - } - - return { left: left, top: top }; - } - - /** - * Returns offset for a given element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element Element to get offset for - * @return {Object} Object with "left" and "top" properties - */ - function getElementOffset(element) { - var docElem, - doc = element && element.ownerDocument, - box = { left: 0, top: 0 }, - offset = { left: 0, top: 0 }, - scrollLeftTop, - offsetAttributes = { - borderLeftWidth: 'left', - borderTopWidth: 'top', - paddingLeft: 'left', - paddingTop: 'top' - }; - - if (!doc) { - return { left: 0, top: 0 }; - } - - for (var attr in offsetAttributes) { - offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; - } - - docElem = doc.documentElement; - if ( typeof element.getBoundingClientRect !== 'undefined' ) { - box = element.getBoundingClientRect(); - } - - scrollLeftTop = fabric.util.getScrollLeftTop(element, null); - - return { - left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, - top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top - }; - } - - /** - * Returns style attribute value of a given element - * @memberOf fabric.util - * @param {HTMLElement} element Element to get style attribute for - * @param {String} attr Style attribute to get for element - * @return {String} Style attribute value of the given element. - */ - var getElementStyle; - if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { - getElementStyle = function(element, attr) { - return fabric.document.defaultView.getComputedStyle(element, null)[attr]; - }; - } - else { - getElementStyle = function(element, attr) { - var value = element.style[attr]; - if (!value && element.currentStyle) { - value = element.currentStyle[attr]; - } - return value; - }; - } - - (function () { - var style = fabric.document.documentElement.style, - selectProp = 'userSelect' in style - ? 'userSelect' - : 'MozUserSelect' in style - ? 'MozUserSelect' - : 'WebkitUserSelect' in style - ? 'WebkitUserSelect' - : 'KhtmlUserSelect' in style - ? 'KhtmlUserSelect' - : ''; - - /** - * Makes element unselectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make unselectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementUnselectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = fabric.util.falseFunction; - } - if (selectProp) { - element.style[selectProp] = 'none'; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = 'on'; - } - return element; - } - - /** - * Makes element selectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make selectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementSelectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = null; - } - if (selectProp) { - element.style[selectProp] = ''; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = ''; - } - return element; - } - - fabric.util.makeElementUnselectable = makeElementUnselectable; - fabric.util.makeElementSelectable = makeElementSelectable; - })(); - - (function() { - - /** - * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading - * @memberOf fabric.util - * @param {String} url URL of a script to load - * @param {Function} callback Callback to execute when script is finished loading - */ - function getScript(url, callback) { - var headEl = fabric.document.getElementsByTagName('head')[0], - scriptEl = fabric.document.createElement('script'), - loading = true; - - /** @ignore */ - scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { - if (loading) { - if (typeof this.readyState === 'string' && - this.readyState !== 'loaded' && - this.readyState !== 'complete') { - return; - } - loading = false; - callback(e || fabric.window.event); - scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; - } - }; - scriptEl.src = url; - headEl.appendChild(scriptEl); - // causes issue in Opera - // headEl.removeChild(scriptEl); - } - - fabric.util.getScript = getScript; - })(); - - fabric.util.getById = getById; - fabric.util.toArray = toArray; - fabric.util.makeElement = makeElement; - fabric.util.addClass = addClass; - fabric.util.wrapElement = wrapElement; - fabric.util.getScrollLeftTop = getScrollLeftTop; - fabric.util.getElementOffset = getElementOffset; - fabric.util.getElementStyle = getElementStyle; - -})(); - - -(function(){ - - function addParamToUrl(url, param) { - return url + (/\?/.test(url) ? '&' : '?') + param; - } - - var makeXHR = (function() { - var factories = [ - function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, - function() { return new XMLHttpRequest(); } - ]; - for (var i = factories.length; i--; ) { - try { - var req = factories[i](); - if (req) { - return factories[i]; - } - } - catch (err) { } - } - })(); - - function emptyFn() { } - - /** - * Cross-browser abstraction for sending XMLHttpRequest - * @memberOf fabric.util - * @param {String} url URL to send XMLHttpRequest to - * @param {Object} [options] Options object - * @param {String} [options.method="GET"] - * @param {Function} options.onComplete Callback to invoke when request is completed - * @return {XMLHttpRequest} request - */ - function request(url, options) { - - options || (options = { }); - - var method = options.method ? options.method.toUpperCase() : 'GET', - onComplete = options.onComplete || function() { }, - xhr = makeXHR(), - body; - - /** @ignore */ - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - onComplete(xhr); - xhr.onreadystatechange = emptyFn; - } - }; - - if (method === 'GET') { - body = null; - if (typeof options.parameters === 'string') { - url = addParamToUrl(url, options.parameters); - } - } - - xhr.open(method, url, true); - - if (method === 'POST' || method === 'PUT') { - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - } - - xhr.send(body); - return xhr; - } - - fabric.util.request = request; -})(); - - -/** - * Wrapper around `console.log` (when available) - * @param {Any} [values] Values to log - */ -fabric.log = function() { }; - -/** - * Wrapper around `console.warn` (when available) - * @param {Any} [values] Values to log as a warning - */ -fabric.warn = function() { }; - -if (typeof console !== 'undefined') { - ['log', 'warn'].forEach(function(methodName) { - if (typeof console[methodName] !== 'undefined' && console[methodName].apply) { - fabric[methodName] = function() { - return console[methodName].apply(console, arguments); - }; - } - }); -} - - -(function(global) { - - 'use strict'; - - /** - * @name fabric - * @namespace - */ - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - capitalize = fabric.util.string.capitalize, - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - parseUnit = fabric.util.parseUnit, - multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, - - attributesMap = { - cx: 'left', - x: 'left', - r: 'radius', - cy: 'top', - y: 'top', - display: 'visible', - visibility: 'visible', - transform: 'transformMatrix', - 'fill-opacity': 'fillOpacity', - 'fill-rule': 'fillRule', - 'font-family': 'fontFamily', - 'font-size': 'fontSize', - 'font-style': 'fontStyle', - 'font-weight': 'fontWeight', - 'stroke-dasharray': 'strokeDashArray', - 'stroke-linecap': 'strokeLineCap', - 'stroke-linejoin': 'strokeLineJoin', - 'stroke-miterlimit': 'strokeMiterLimit', - 'stroke-opacity': 'strokeOpacity', - 'stroke-width': 'strokeWidth', - 'text-decoration': 'textDecoration', - 'text-anchor': 'originX' - }, - - colorAttributes = { - stroke: 'strokeOpacity', - fill: 'fillOpacity' - }; - - function normalizeAttr(attr) { - // transform attribute names - if (attr in attributesMap) { - return attributesMap[attr]; - } - return attr; - } - - function normalizeValue(attr, value, parentAttributes) { - var isArray = Object.prototype.toString.call(value) === '[object Array]', - parsed; - - if ((attr === 'fill' || attr === 'stroke') && value === 'none') { - value = ''; - } - else if (attr === 'fillRule') { - value = (value === 'evenodd') ? 'destination-over' : value; - } - else if (attr === 'strokeDashArray') { - value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) { - return parseInt(n); - }); - } - else if (attr === 'transformMatrix') { - if (parentAttributes && parentAttributes.transformMatrix) { - value = multiplyTransformMatrices( - parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); - } - else { - value = fabric.parseTransformAttribute(value); - } - } - else if (attr === 'visible') { - value = (value === 'none' || value === 'hidden') ? false : true; - // display=none on parent element always takes precedence over child element - if (parentAttributes && parentAttributes.visible === false) { - value = false; - } - } - else if (attr === 'originX' /* text-anchor */) { - value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; - } - else { - parsed = isArray ? value.map(parseUnit) : parseUnit(value); - } - - return (!isArray && isNaN(parsed) ? value : parsed); - } - - /** - * @private - * @param {Object} attributes Array of attributes to parse - */ - function _setStrokeFillOpacity(attributes) { - for (var attr in colorAttributes) { - - if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') { - continue; - } - - if (attributes[attr].indexOf('url(') === 0) { - continue; - } - - var color = new fabric.Color(attributes[attr]); - attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); - } - return attributes; - } - - /** - * Parses "transform" attribute, returning an array of values - * @static - * @function - * @memberOf fabric - * @param {String} attributeValue String containing attribute value - * @return {Array} Array of 6 elements representing transformation matrix - */ - fabric.parseTransformAttribute = (function() { - function rotateMatrix(matrix, args) { - var angle = args[0]; - - matrix[0] = Math.cos(angle); - matrix[1] = Math.sin(angle); - matrix[2] = -Math.sin(angle); - matrix[3] = Math.cos(angle); - } - - function scaleMatrix(matrix, args) { - var multiplierX = args[0], - multiplierY = (args.length === 2) ? args[1] : args[0]; - - matrix[0] = multiplierX; - matrix[3] = multiplierY; - } - - function skewXMatrix(matrix, args) { - matrix[2] = 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*\\.\\d+)(?:e[-+]?\\d+)?)', - - commaWsp = '(?:\\s+,?\\s*|,\\s*)', - - skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', - - skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', - - rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + ')' + - commaWsp + '(' + number + '))?\\s*\\))', - - scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - matrix = '(?:(matrix)\\s*\\(\\s*' + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + - '\\s*\\))', - - transform = '(?:' + - matrix + '|' + - translate + '|' + - scale + '|' + - rotate + '|' + - skewX + '|' + - skewY + - ')', - - transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', - - transformList = '^\\s*(?:' + transforms + '?)\\s*$', - - // http://www.w3.org/TR/SVG/coords.html#TransformAttribute - reTransformList = new RegExp(transformList), - // == end transform regexp - - reTransform = new RegExp(transform, 'g'); - - return function(attributeValue) { - - // start with identity matrix - var matrix = iMatrix.concat(), - matrices = [ ]; - - // return if no argument was given or - // an argument does not match transform attribute regexp - if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { - return matrix; - } - - attributeValue.replace(reTransform, function(match) { - - var m = new RegExp(transform).exec(match).filter(function (match) { - return (match !== '' && match != null); - }), - operation = m[1], - args = m.slice(2).map(parseFloat); - - switch (operation) { - case 'translate': - translateMatrix(matrix, args); - break; - case 'rotate': - args[0] = fabric.util.degreesToRadians(args[0]); - rotateMatrix(matrix, args); - break; - case 'scale': - scaleMatrix(matrix, args); - break; - case 'skewX': - skewXMatrix(matrix, args); - break; - case 'skewY': - skewYMatrix(matrix, args); - break; - case 'matrix': - matrix = args; - break; - } - - // snapshot current matrix into matrices array - matrices.push(matrix.concat()); - // reset - matrix = iMatrix.concat(); - }); - - var combinedMatrix = matrices[0]; - while (matrices.length > 1) { - matrices.shift(); - combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); - } - return combinedMatrix; - }; - })(); - - function parseFontDeclaration(value, oStyle) { - - // TODO: support non-px font size - var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/); - - if (!match) { - return; - } - - var fontStyle = match[1], - // font variant is not used - // fontVariant = match[2], - fontWeight = match[3], - fontSize = match[4], - lineHeight = match[5], - fontFamily = match[6]; - - if (fontStyle) { - oStyle.fontStyle = fontStyle; - } - if (fontWeight) { - oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); - } - if (fontSize) { - oStyle.fontSize = parseFloat(fontSize); - } - if (fontFamily) { - oStyle.fontFamily = fontFamily; - } - if (lineHeight) { - oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; - } - } - - /** - * @private - */ - function parseStyleString(style, oStyle) { - var attr, value; - style.replace(/;$/, '').split(';').forEach(function (chunk) { - var pair = chunk.split(':'); - - attr = normalizeAttr(pair[0].trim().toLowerCase()); - value = normalizeValue(attr, pair[1].trim()); - - if (attr === 'font') { - parseFontDeclaration(value, oStyle); - } - else { - oStyle[attr] = value; - } - }); - } - - /** - * @private - */ - function parseStyleObject(style, oStyle) { - var attr, value; - for (var prop in style) { - if (typeof style[prop] === 'undefined') { - continue; - } - - attr = normalizeAttr(prop.toLowerCase()); - value = normalizeValue(attr, style[prop]); - - if (attr === 'font') { - parseFontDeclaration(value, oStyle); - } - else { - oStyle[attr] = value; - } - } - } - - /** - * @private - */ - function getGlobalStylesForElement(element) { - var styles = { }; - - for (var rule in fabric.cssRules) { - if (elementMatchesRule(element, rule.split(' '))) { - for (var property in fabric.cssRules[rule]) { - styles[property] = fabric.cssRules[rule][property]; - } - } - } - return styles; - } - - /** - * @private - */ - function elementMatchesRule(element, selectors) { - var firstMatching, parentMatching = true; - //start from rightmost selector. - firstMatching = selectorMatches(element, selectors.pop()); - if (firstMatching && selectors.length) { - parentMatching = doesSomeParentMatch(element, selectors); - } - return firstMatching && parentMatching && (selectors.length === 0); - } - - function doesSomeParentMatch(element, selectors) { - var selector, parentMatching = true; - while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { - if (parentMatching) { - selector = selectors.pop(); - } - element = element.parentNode; - parentMatching = selectorMatches(element, selector); - } - return selectors.length === 0; - } - /** - * @private - */ - function selectorMatches(element, selector) { - var nodeName = element.nodeName, - classNames = element.getAttribute('class'), - id = element.getAttribute('id'), matcher; - // i check if a selector matches slicing away part from it. - // if i get empty string i should match - matcher = new RegExp('^' + nodeName, 'i'); - selector = selector.replace(matcher, ''); - if (id && selector.length) { - matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); - selector = selector.replace(matcher, ''); - } - if (classNames && selector.length) { - classNames = classNames.split(' '); - for (var i = classNames.length; i--;) { - matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); - selector = selector.replace(matcher, ''); - } - } - return selector.length === 0; - } - - /** - * @private - */ - function parseUseDirectives(doc) { - var nodelist = doc.getElementsByTagName('use'); - while (nodelist.length) { - var el = nodelist[0], - xlink = el.getAttribute('xlink:href').substr(1), - x = el.getAttribute('x') || 0, - y = el.getAttribute('y') || 0, - el2 = doc.getElementById(xlink).cloneNode(true), - currentTrans = (el.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', - parentNode; - - for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { - var attr = attrs.item(j); - if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { - continue; - } - - if (attr.nodeName === 'transform') { - currentTrans = currentTrans + ' ' + attr.nodeValue; - } - else { - el2.setAttribute(attr.nodeName, attr.nodeValue); - } - } - - el2.setAttribute('transform', currentTrans); - el2.removeAttribute('id'); - parentNode = el.parentNode; - parentNode.replaceChild(el2, el); - } - } - - /** - * Add a element that envelop all SCG elements and makes the viewbox transformMatrix descend on all elements - */ - function addSvgTransform(doc, matrix) { - matrix[3] = matrix[0] = (matrix[0] > matrix[3] ? matrix[3] : matrix[0]); - if (!(matrix[0] !== 1 || matrix[3] !== 1 || matrix[4] !== 0 || matrix[5] !== 0)) { - return; - } - // default is to preserve aspect ratio - // preserveAspectRatio attribute to be implemented - var el = doc.ownerDocument.createElement('g'); - while (doc.firstChild != null) { - el.appendChild(doc.firstChild); - } - el.setAttribute('transform','matrix(' + matrix[0] + ' ' + matrix[1] + ' ' + matrix[2] + ' ' + matrix[3] + ' ' + matrix[4] + ' ' + matrix[5] + ')'); - doc.appendChild(el); - } - - /** - * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - fabric.parseSVGDocument = (function() { - - var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, - - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute - // \d doesn't quite cut it (as we need to match an actual float number) - - // matches, e.g.: +14.56e-12, etc. - reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - - 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(); - - parseUseDirectives(doc); - /* http://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute - * as per spec, width and height attributes are to be considered - * 100% if no value is specified. - */ - var viewBoxAttr = doc.getAttribute('viewBox'), - widthAttr = parseUnit(doc.getAttribute('width') || '100%'), - heightAttr = parseUnit(doc.getAttribute('height') || '100%'), - viewBoxWidth, - viewBoxHeight; - - if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { - var minX = parseFloat(viewBoxAttr[1]), - minY = parseFloat(viewBoxAttr[2]), - scaleX = 1, scaleY = 1; - viewBoxWidth = parseFloat(viewBoxAttr[3]); - viewBoxHeight = parseFloat(viewBoxAttr[4]); - if (widthAttr && widthAttr !== viewBoxWidth ) { - scaleX = widthAttr / viewBoxWidth; - } - if (heightAttr && heightAttr !== viewBoxHeight) { - scaleY = heightAttr / viewBoxHeight; - } - addSvgTransform(doc, [scaleX, 0, 0, scaleY, scaleX * -minX, scaleY * -minY]); - } - - var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - - if (descendants.length === 0 && fabric.isLikelyNode) { - // we're likely in node, where "o3-xml" library fails to gEBTN("*") - // https://github.com/ajaxorg/node-o3-xml/issues/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)) { - callback && callback([], {}); - return; - } - - var options = { - width: widthAttr ? widthAttr : viewBoxWidth, - height: heightAttr ? heightAttr : viewBoxHeight, - widthAttr: widthAttr, - heightAttr: heightAttr - }; - - fabric.gradientDefs = fabric.getGradientDefs(doc); - fabric.cssRules = fabric.getCSSRules(doc); - // Precedence of rules: style > class > attribute - - fabric.parseElements(elements, function(instances) { - fabric.documentParsingTime = new Date() - startTime; - if (callback) { - callback(instances, options); - } - }, clone(options), reviver); - }; - })(); - - /** - * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) - * @namespace - */ - var svgCache = { - - /** - * @param {String} name - * @param {Function} callback - */ - has: function (name, callback) { - callback(false); - }, - - get: function () { - /* NOOP */ - }, - - set: function () { - /* NOOP */ - } - }; - - /** - * @private - */ - function _enlivenCachedObject(cachedObject) { - - var objects = cachedObject.objects, - options = cachedObject.options; - - objects = objects.map(function (o) { - return fabric[capitalize(o.type)].fromObject(o); - }); - - return ({ objects: objects, options: options }); - } - - /** - * @private - */ - function _createSVGPattern(markup, canvas, property) { - if (canvas[property] && canvas[property].toSVG) { - markup.push( - '', - '' - ); - } - } - - extend(fabric, { - - /** - * Parses an SVG document, returning all of the gradient declarations found in it - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element - */ - getGradientDefs: function(doc) { - var linearGradientEls = doc.getElementsByTagName('linearGradient'), - radialGradientEls = doc.getElementsByTagName('radialGradient'), - el, i, j = 0, id, xlink, elList = [ ], - gradientDefs = { }, idsToXlinkMap = { }; - - elList.length = linearGradientEls.length + radialGradientEls.length; - i = linearGradientEls.length; - while (i--) { - elList[j++] = linearGradientEls[i]; - } - i = radialGradientEls.length; - while (i--) { - elList[j++] = radialGradientEls[i]; - } - - while (j--) { - el = elList[j]; - xlink = el.getAttribute('xlink:href'); - id = el.getAttribute('id'); - if (xlink) { - idsToXlinkMap[id] = xlink.substr(1); - } - gradientDefs[id] = el; - } - - for (id in idsToXlinkMap) { - var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); - el = gradientDefs[id]; - while (el2.firstChild) { - el.appendChild(el2.firstChild); - } - } - return gradientDefs; - }, - - /** - * Returns an object of attributes' name/value, given element and an array of attribute names; - * Parses parent "g" nodes recursively upwards. - * @static - * @memberOf fabric - * @param {DOMElement} element Element to parse - * @param {Array} attributes Array of attributes to parse - * @return {Object} object containing parsed attributes' names/values - */ - parseAttributes: function(element, attributes) { - - if (!element) { - return; - } - - var value, - parentAttributes = { }; - - // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards - if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) { - parentAttributes = fabric.parseAttributes(element.parentNode, attributes); - } - - var ownAttributes = attributes.reduce(function(memo, attr) { - value = element.getAttribute(attr); - if (value) { - attr = normalizeAttr(attr); - value = normalizeValue(attr, value, parentAttributes); - - memo[attr] = value; - } - return memo; - }, { }); - - // add values parsed from style, which take precedence over attributes - // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) - ownAttributes = extend(ownAttributes, - extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element))); - - return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); - }, - - /** - * Transforms an array of svg elements to corresponding fabric.* instances - * @static - * @memberOf fabric - * @param {Array} elements Array of elements to parse - * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) - * @param {Object} [options] Options object - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - parseElements: function(elements, callback, options, reviver) { - new fabric.ElementsParser(elements, callback, options, reviver).parse(); - }, - - /** - * Parses "style" attribute, retuning an object with values - * @static - * @memberOf fabric - * @param {SVGElement} element Element to parse - * @return {Object} Objects with values parsed from style attribute of an element - */ - parseStyleAttribute: function(element) { - var oStyle = { }, - style = element.getAttribute('style'); - - if (!style) { - return oStyle; - } - - if (typeof style === 'string') { - parseStyleString(style, oStyle); - } - else { - parseStyleObject(style, oStyle); - } - - return oStyle; - }, - - /** - * Parses "points" attribute, returning an array of values - * @static - * @memberOf fabric - * @param {String} points points attribute string - * @return {Array} array of points - */ - parsePointsAttribute: function(points) { - - // points attribute is required and must not be empty - if (!points) { - return null; - } - - // replace commas with whitespace and remove bookending whitespace - points = points.replace(/,/g, ' ').trim(); - - points = points.split(/\s+/); - var parsedPoints = [ ], i, len; - - i = 0; - len = points.length; - for (; i < len; i+=2) { - parsedPoints.push({ - x: parseFloat(points[i]), - y: parseFloat(points[i + 1]) - }); - } - - // odd number of points is an error - // if (parsedPoints.length % 2 !== 0) { - // return null; - // } - - return parsedPoints; - }, - - /** - * Returns CSS rules for a given SVG document - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} CSS rules of this document - */ - getCSSRules: function(doc) { - var styles = doc.getElementsByTagName('style'), - allRules = { }, rules; - - // very crude parsing of style contents - for (var i = 0, len = styles.length; i < len; i++) { - var styleContents = styles[0].textContent; - - // remove comments - styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); - - rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); - rules = rules.map(function(rule) { return rule.trim(); }); - - rules.forEach(function(rule) { - - var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), - ruleObj = { }, declaration = match[2].trim(), - propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); - - for (var i = 0, len = propertyValuePairs.length; i < len; i++) { - var pair = propertyValuePairs[i].split(/\s*:\s*/), - property = normalizeAttr(pair[0]), - value = normalizeValue(property,pair[1],pair[0]); - ruleObj[property] = value; - } - rule = match[1]; - rule.split(',').forEach(function(_rule) { - allRules[_rule.trim()] = fabric.util.object.clone(ruleObj); - }); - }); - } - return allRules; - }, - - /** - * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) - * @memberof fabric - * @param {String} url - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromURL: function(url, callback, reviver) { - - url = url.replace(/^\n\s*/, '').trim(); - svgCache.has(url, function (hasUrl) { - if (hasUrl) { - svgCache.get(url, function (value) { - var enlivedRecord = _enlivenCachedObject(value); - callback(enlivedRecord.objects, enlivedRecord.options); - }); - } - else { - new fabric.util.request(url, { - method: 'get', - onComplete: onComplete - }); - } - }); - - function onComplete(r) { - - var xml = r.responseXML; - if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { - xml = new ActiveXObject('Microsoft.XMLDOM'); - xml.async = 'false'; - //IE chokes on DOCTYPE - xml.loadXML(r.responseText.replace(//i,'')); - } - if (!xml || !xml.documentElement) { - return; - } - - fabric.parseSVGDocument(xml.documentElement, function (results, options) { - svgCache.set(url, { - objects: fabric.util.array.invoke(results, 'toObject'), - options: options - }); - callback(results, options); - }, reviver); - } - }, - - /** - * Takes string corresponding to an SVG document, and parses it into a set of fabric objects - * @memberof fabric - * @param {String} string - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromString: function(string, callback, reviver) { - string = string.trim(); - var doc; - if (typeof DOMParser !== 'undefined') { - var parser = new DOMParser(); - if (parser && parser.parseFromString) { - doc = parser.parseFromString(string, 'text/xml'); - } - } - else if (fabric.window.ActiveXObject) { - doc = new ActiveXObject('Microsoft.XMLDOM'); - doc.async = 'false'; - //IE chokes on DOCTYPE - doc.loadXML(string.replace(//i,'')); - } - - fabric.parseSVGDocument(doc.documentElement, function (results, options) { - callback(results, options); - }, reviver); - }, - - /** - * Creates markup containing SVG font faces - * @param {Array} objects Array of fabric objects - * @return {String} - */ - createSVGFontFacesMarkup: function(objects) { - var markup = ''; - - for (var i = 0, len = objects.length; i < len; i++) { - if (objects[i].type !== 'text' || !objects[i].path) { - continue; - } - - markup += [ - //jscs:disable validateIndentation - '@font-face {', - 'font-family: ', objects[i].fontFamily, '; ', - 'src: url(\'', objects[i].path, '\')', - '}' - //jscs:enable validateIndentation - ].join(''); - } - - if (markup) { - markup = [ - //jscs:disable validateIndentation - '' - //jscs:enable validateIndentation - ].join(''); - } - - return markup; - }, - - /** - * Creates markup containing SVG referenced elements like patterns, gradients etc. - * @param {fabric.Canvas} canvas instance of fabric.Canvas - * @return {String} - */ - createSVGRefElementsMarkup: function(canvas) { - var markup = [ ]; - - _createSVGPattern(markup, canvas, 'backgroundColor'); - _createSVGPattern(markup, canvas, 'overlayColor'); - - return markup.join(''); - } - }); - -})(typeof exports !== 'undefined' ? exports : this); - - -fabric.ElementsParser = function(elements, callback, options, reviver) { - this.elements = elements; - this.callback = callback; - this.options = options; - this.reviver = reviver; -}; - -fabric.ElementsParser.prototype.parse = function() { - this.instances = new Array(this.elements.length); - this.numElements = this.elements.length; - - this.createObjects(); -}; - -fabric.ElementsParser.prototype.createObjects = function() { - for (var i = 0, len = this.elements.length; i < len; i++) { - (function(_this, i) { - setTimeout(function() { - _this.createObject(_this.elements[i], i); - }, 0); - })(this, i); - } -}; - -fabric.ElementsParser.prototype.createObject = function(el, index) { - var klass = fabric[fabric.util.string.capitalize(el.tagName)]; - if (klass && klass.fromElement) { - try { - this._createObject(klass, el, index); - } - catch (err) { - fabric.log(err); - } - } - else { - this.checkIfDone(); - } -}; - -fabric.ElementsParser.prototype._createObject = function(klass, el, index) { - if (klass.async) { - klass.fromElement(el, this.createCallback(index, el), this.options); - } - else { - var obj = klass.fromElement(el, this.options); - this.resolveGradient(obj, 'fill'); - this.resolveGradient(obj, 'stroke'); - this.reviver && this.reviver(el, obj); - this.instances[index] = obj; - this.checkIfDone(); - } -}; - -fabric.ElementsParser.prototype.createCallback = function(index, el) { - var _this = this; - return function(obj) { - _this.resolveGradient(obj, 'fill'); - _this.resolveGradient(obj, 'stroke'); - _this.reviver && _this.reviver(el, obj); - _this.instances[index] = obj; - _this.checkIfDone(); - }; -}; - -fabric.ElementsParser.prototype.resolveGradient = function(obj, property) { - - var instanceFillValue = obj.get(property); - if (!(/^url\(/).test(instanceFillValue)) { - return; - } - var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); - if (fabric.gradientDefs[gradientId]) { - obj.set(property, - fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], obj)); - } -}; - -fabric.ElementsParser.prototype.checkIfDone = function() { - if (--this.numElements === 0) { - this.instances = this.instances.filter(function(el) { - return el != null; - }); - this.callback(this.instances); - } -}; - - -(function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Point) { - fabric.warn('fabric.Point is already defined'); - return; - } - - fabric.Point = Point; - - /** - * Point class - * @class fabric.Point - * @memberOf fabric - * @constructor - * @param {Number} x - * @param {Number} y - * @return {fabric.Point} thisArg - */ - function Point(x, y) { - this.x = x; - this.y = y; - } - - Point.prototype = /** @lends fabric.Point.prototype */ { - - constructor: Point, - - /** - * Adds another point to this one and returns another one - * @param {fabric.Point} that - * @return {fabric.Point} new Point instance with added values - */ - add: function (that) { - return new Point(this.x + that.x, this.y + that.y); - }, - - /** - * Adds another point to this one - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - */ - addEquals: function (that) { - this.x += that.x; - this.y += that.y; - return this; - }, - - /** - * Adds value to this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} new Point with added value - */ - scalarAdd: function (scalar) { - return new Point(this.x + scalar, this.y + scalar); - }, - - /** - * Adds value to this point - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - scalarAddEquals: function (scalar) { - this.x += scalar; - this.y += scalar; - return this; - }, - - /** - * Subtracts another point from this point and returns a new one - * @param {fabric.Point} that - * @return {fabric.Point} new Point object with subtracted values - */ - subtract: function (that) { - return new Point(this.x - that.x, this.y - that.y); - }, - - /** - * Subtracts another point from this point - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - */ - subtractEquals: function (that) { - this.x -= that.x; - this.y -= that.y; - return this; - }, - - /** - * Subtracts value from this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - scalarSubtract: function (scalar) { - return new Point(this.x - scalar, this.y - scalar); - }, - - /** - * Subtracts value from this point - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - scalarSubtractEquals: function (scalar) { - this.x -= scalar; - this.y -= scalar; - return this; - }, - - /** - * Miltiplies this point by a value and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - multiply: function (scalar) { - return new Point(this.x * scalar, this.y * scalar); - }, - - /** - * Miltiplies this point by a value - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - multiplyEquals: function (scalar) { - this.x *= scalar; - this.y *= scalar; - return this; - }, - - /** - * Divides this point by a value and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - divide: function (scalar) { - return new Point(this.x / scalar, this.y / scalar); - }, - - /** - * Divides this point by a value - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - divideEquals: function (scalar) { - this.x /= scalar; - this.y /= scalar; - return this; - }, - - /** - * Returns true if this point is equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - eq: function (that) { - return (this.x === that.x && this.y === that.y); - }, - - /** - * Returns true if this point is less than another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lt: function (that) { - return (this.x < that.x && this.y < that.y); - }, - - /** - * Returns true if this point is less than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lte: function (that) { - return (this.x <= that.x && this.y <= that.y); - }, - - /** - - * Returns true if this point is greater another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gt: function (that) { - return (this.x > that.x && this.y > that.y); - }, - - /** - * Returns true if this point is greater than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gte: function (that) { - return (this.x >= that.x && this.y >= that.y); - }, - - /** - * Returns new point which is the result of linear interpolation with this one and another one - * @param {fabric.Point} that - * @param {Number} t - * @return {fabric.Point} - */ - lerp: function (that, t) { - return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); - }, - - /** - * Returns distance from this point and another one - * @param {fabric.Point} that - * @return {Number} - */ - distanceFrom: function (that) { - var dx = this.x - that.x, - dy = this.y - that.y; - return Math.sqrt(dx * dx + dy * dy); - }, - - /** - * Returns the point between this point and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - midPointFrom: function (that) { - return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2); - }, - - /** - * Returns a new point which is the min of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - min: function (that) { - return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); - }, - - /** - * Returns a new point which is the max of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - max: function (that) { - return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); - }, - - /** - * Returns string representation of this point - * @return {String} - */ - toString: function () { - return this.x + ',' + this.y; - }, - - /** - * Sets x/y of this point - * @param {Number} x - * @return {Number} y - */ - setXY: function (x, y) { - this.x = x; - this.y = y; - }, - - /** - * Sets x/y of this point from another point - * @param {fabric.Point} that - */ - setFromPoint: function (that) { - this.x = that.x; - this.y = that.y; - }, - - /** - * Swaps x/y of this point and another point - * @param {fabric.Point} that - */ - swap: function (that) { - var x = this.x, - y = this.y; - this.x = that.x; - this.y = that.y; - that.x = x; - that.y = y; - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Intersection) { - fabric.warn('fabric.Intersection is already defined'); - return; - } - - /** - * Intersection class - * @class fabric.Intersection - * @memberOf fabric - * @constructor - */ - function Intersection(status) { - this.status = status; - this.points = []; - } - - fabric.Intersection = Intersection; - - fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { - - /** - * Appends a point to intersection - * @param {fabric.Point} point - */ - appendPoint: function (point) { - this.points.push(point); - }, - - /** - * Appends points to intersection - * @param {Array} points - */ - appendPoints: function (points) { - this.points = this.points.concat(points); - } - }; - - /** - * Checks if one line intersects another - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {fabric.Point} b1 - * @param {fabric.Point} b2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { - var result, - uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), - ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), - uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); - if (uB !== 0) { - var ua = uaT / uB, - ub = ubT / uB; - if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { - result = new Intersection('Intersection'); - result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); - } - else { - result = new Intersection(); - } - } - else { - if (uaT === 0 || ubT === 0) { - result = new Intersection('Coincident'); - } - else { - result = new Intersection('Parallel'); - } - } - return result; - }; - - /** - * Checks if line intersects polygon - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {Array} points - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ - var result = new Intersection(), - length = points.length; - - for (var i = 0; i < length; i++) { - var b1 = points[i], - b2 = points[(i + 1) % length], - inter = Intersection.intersectLineLine(a1, a2, b1, b2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects another polygon - * @static - * @param {Array} points1 - * @param {Array} points2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { - var result = new Intersection(), - length = points1.length; - - for (var i = 0; i < length; i++) { - var a1 = points1[i], - a2 = points1[(i + 1) % length], - inter = Intersection.intersectLinePolygon(a1, a2, points2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects rectangle - * @static - * @param {Array} points - * @param {Number} r1 - * @param {Number} r2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { - var min = r1.min(r2), - max = r1.max(r2), - topRight = new fabric.Point(max.x, min.y), - bottomLeft = new fabric.Point(min.x, max.y), - inter1 = Intersection.intersectLinePolygon(min, topRight, points), - inter2 = Intersection.intersectLinePolygon(topRight, max, points), - inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), - inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), - result = new Intersection(); - - result.appendPoints(inter1.points); - result.appendPoints(inter2.points); - result.appendPoints(inter3.points); - result.appendPoints(inter4.points); - - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Color) { - fabric.warn('fabric.Color is already defined.'); - return; - } - - /** - * Color class - * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; - * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. - * - * @class fabric.Color - * @param {String} color optional in hex or rgb(a) format - * @return {fabric.Color} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} - */ - function Color(color) { - if (!color) { - this.setSource([0, 0, 0, 1]); - } - else { - this._tryParsingColor(color); - } - } - - fabric.Color = Color; - - fabric.Color.prototype = /** @lends fabric.Color.prototype */ { - - /** - * @private - * @param {String|Array} color Color value to parse - */ - _tryParsingColor: function(color) { - var source; - - if (color in Color.colorNameMap) { - color = Color.colorNameMap[color]; - } - - if (color === 'transparent') { - this.setSource([255,255,255,0]); - return; - } - - source = Color.sourceFromHex(color); - - if (!source) { - source = Color.sourceFromRgb(color); - } - if (!source) { - source = Color.sourceFromHsl(color); - } - if (source) { - this.setSource(source); - } - }, - - /** - * Adapted from https://github.com/mjijackson - * @private - * @param {Number} r Red color value - * @param {Number} g Green color value - * @param {Number} b Blue color value - * @return {Array} Hsl color - */ - _rgbToHsl: function(r, g, b) { - r /= 255, g /= 255, b /= 255; - - var h, s, l, - max = fabric.util.array.max([r, g, b]), - min = fabric.util.array.min([r, g, b]); - - l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } - else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - h /= 6; - } - - return [ - Math.round(h * 360), - Math.round(s * 100), - Math.round(l * 100) - ]; - }, - - /** - * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {Array} - */ - getSource: function() { - return this._source; - }, - - /** - * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {Array} source - */ - setSource: function(source) { - this._source = source; - }, - - /** - * Returns color represenation in RGB format - * @return {String} ex: rgb(0-255,0-255,0-255) - */ - toRgb: function() { - var source = this.getSource(); - return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; - }, - - /** - * Returns color represenation in RGBA format - * @return {String} ex: rgba(0-255,0-255,0-255,0-1) - */ - toRgba: function() { - var source = this.getSource(); - return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; - }, - - /** - * Returns color represenation in HSL format - * @return {String} ex: hsl(0-360,0%-100%,0%-100%) - */ - toHsl: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; - }, - - /** - * Returns color represenation in HSLA format - * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) - */ - toHsla: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; - }, - - /** - * Returns color represenation in HEX format - * @return {String} ex: FF5555 - */ - toHex: function() { - var source = this.getSource(), r, g, b; - - r = source[0].toString(16); - r = (r.length === 1) ? ('0' + r) : r; - - g = source[1].toString(16); - g = (g.length === 1) ? ('0' + g) : g; - - b = source[2].toString(16); - b = (b.length === 1) ? ('0' + b) : b; - - return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); - }, - - /** - * Gets value of alpha channel for this color - * @return {Number} 0-1 - */ - getAlpha: function() { - return this.getSource()[3]; - }, - - /** - * Sets value of alpha channel for this color - * @param {Number} alpha Alpha value 0-1 - * @return {fabric.Color} thisArg - */ - setAlpha: function(alpha) { - var source = this.getSource(); - source[3] = alpha; - this.setSource(source); - return this; - }, - - /** - * Transforms color to its grayscale representation - * @return {fabric.Color} thisArg - */ - toGrayscale: function() { - var source = this.getSource(), - average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), - currentAlpha = source[3]; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Transforms color to its black and white representation - * @param {Number} threshold - * @return {fabric.Color} thisArg - */ - toBlackWhite: function(threshold) { - var source = this.getSource(), - average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), - currentAlpha = source[3]; - - threshold = threshold || 127; - - average = (Number(average) < Number(threshold)) ? 0 : 255; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Overlays color with another color - * @param {String|fabric.Color} otherColor - * @return {fabric.Color} thisArg - */ - overlayWith: function(otherColor) { - if (!(otherColor instanceof Color)) { - otherColor = new Color(otherColor); - } - - var result = [], - alpha = this.getAlpha(), - otherAlpha = 0.5, - source = this.getSource(), - otherSource = otherColor.getSource(); - - for (var i = 0; i < 3; i++) { - result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); - } - - result[3] = alpha; - this.setSource(result); - return this; - } - }; - - /** - * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HEX format (ex: #FF5555, 010155, aff) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; - - /** - * Map of the 17 basic color names with HEX code - * @static - * @field - * @memberOf fabric.Color - * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units - */ - fabric.Color.colorNameMap = { - aqua: '#00FFFF', - black: '#000000', - blue: '#0000FF', - fuchsia: '#FF00FF', - gray: '#808080', - green: '#008000', - lime: '#00FF00', - maroon: '#800000', - navy: '#000080', - olive: '#808000', - orange: '#FFA500', - purple: '#800080', - red: '#FF0000', - silver: '#C0C0C0', - teal: '#008080', - white: '#FFFFFF', - yellow: '#FFFF00' - }; - - /** - * @private - * @param {Number} p - * @param {Number} q - * @param {Number} t - * @return {Number} - */ - function hue2rgb(p, q, t){ - if (t < 0) { - t += 1; - } - if (t > 1) { - t -= 1; - } - if (t < 1/6) { - return p + (q - p) * 6 * t; - } - if (t < 1/2) { - return q; - } - if (t < 2/3) { - return p + (q - p) * (2/3 - t) * 6; - } - return p; - } - - /** - * Returns new color object, when given a color in RGB format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255) - * @return {fabric.Color} - */ - fabric.Color.fromRgb = function(color) { - return Color.fromSource(Color.sourceFromRgb(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) - * @return {Array} source - */ - fabric.Color.sourceFromRgb = function(color) { - var match = color.match(Color.reRGBa); - if (match) { - var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), - g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), - b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); - - return [ - parseInt(r, 10), - parseInt(g, 10), - parseInt(b, 10), - match[4] ? parseFloat(match[4]) : 1 - ]; - } - }; - - /** - * Returns new color object, when given a color in RGBA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromRgba = Color.fromRgb; - - /** - * Returns new color object, when given a color in HSL format - * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) - * @memberOf fabric.Color - * @return {fabric.Color} - */ - fabric.Color.fromHsl = function(color) { - return Color.fromSource(Color.sourceFromHsl(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. - * Adapted from https://github.com/mjijackson - * @memberOf fabric.Color - * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) - * @return {Array} source - * @see http://http://www.w3.org/TR/css3-color/#hsl-color - */ - fabric.Color.sourceFromHsl = function(color) { - var match = color.match(Color.reHSLa); - if (!match) { - return; - } - - var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, - s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), - l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), - r, g, b; - - if (s === 0) { - r = g = b = l; - } - else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, - p = l * 2 - q; - - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [ - Math.round(r * 255), - Math.round(g * 255), - Math.round(b * 255), - match[4] ? parseFloat(match[4]) : 1 - ]; - }; - - /** - * Returns new color object, when given a color in HSLA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromHsla = Color.fromHsl; - - /** - * Returns new color object, when given a color in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color Color value ex: FF5555 - * @return {fabric.Color} - */ - fabric.Color.fromHex = function(color) { - return Color.fromSource(Color.sourceFromHex(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color ex: FF5555 - * @return {Array} source - */ - fabric.Color.sourceFromHex = function(color) { - if (color.match(Color.reHex)) { - var value = color.slice(color.indexOf('#') + 1), - isShortNotation = (value.length === 3), - r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), - g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), - b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6); - - return [ - parseInt(r, 16), - parseInt(g, 16), - parseInt(b, 16), - 1 - ]; - } - }; - - /** - * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) - * @static - * @memberOf fabric.Color - * @param {Array} source - * @return {fabric.Color} - */ - fabric.Color.fromSource = function(source) { - var oColor = new Color(); - oColor.setSource(source); - return oColor; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - /* _FROM_SVG_START_ */ - function getColorStop(el) { - var style = el.getAttribute('style'), - offset = el.getAttribute('offset'), - color, colorAlpha, opacity; - - // convert percents to absolute values - offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); - offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; - if (style) { - var keyValuePairs = style.split(/\s*;\s*/); - - if (keyValuePairs[keyValuePairs.length - 1] === '') { - keyValuePairs.pop(); - } - - for (var i = keyValuePairs.length; i--; ) { - - var split = keyValuePairs[i].split(/\s*:\s*/), - key = split[0].trim(), - value = split[1].trim(); - - if (key === 'stop-color') { - color = value; - } - else if (key === 'stop-opacity') { - opacity = value; - } - } - } - - if (!color) { - color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; - } - if (!opacity) { - opacity = el.getAttribute('stop-opacity'); - } - - color = new fabric.Color(color); - colorAlpha = color.getAlpha(); - opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); - opacity *= colorAlpha; - - return { - offset: offset, - color: color.toRgb(), - opacity: opacity - }; - } - - function getLinearCoords(el) { - return { - x1: el.getAttribute('x1') || 0, - y1: el.getAttribute('y1') || 0, - x2: el.getAttribute('x2') || '100%', - y2: el.getAttribute('y2') || 0 - }; - } - - function getRadialCoords(el) { - return { - x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', - y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', - r1: 0, - x2: el.getAttribute('cx') || '50%', - y2: el.getAttribute('cy') || '50%', - r2: el.getAttribute('r') || '50%' - }; - } - /* _FROM_SVG_END_ */ - - /** - * Gradient class - * @class fabric.Gradient - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients} - * @see {@link fabric.Gradient#initialize} for constructor definition - */ - fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { - - /** - * Horizontal offset for aligning gradients coming from SVG when outside pathgroups - * @type Number - * @default 0 - */ - offsetX: 0, - - /** - * Vertical offset for aligning gradients coming from SVG when outside pathgroups - * @type Number - * @default 0 - */ - offsetY: 0, - - /** - * Constructor - * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops - * @return {fabric.Gradient} thisArg - */ - initialize: function(options) { - options || (options = { }); - - var coords = { }; - - this.id = fabric.Object.__uid++; - this.type = options.type || 'linear'; - - coords = { - x1: options.coords.x1 || 0, - y1: options.coords.y1 || 0, - x2: options.coords.x2 || 0, - y2: options.coords.y2 || 0 - }; - - if (this.type === 'radial') { - coords.r1 = options.coords.r1 || 0; - coords.r2 = options.coords.r2 || 0; - } - this.coords = coords; - this.colorStops = options.colorStops.slice(); - if (options.gradientTransform) { - this.gradientTransform = options.gradientTransform; - } - this.offsetX = options.offsetX || this.offsetX; - this.offsetY = options.offsetY || this.offsetY; - }, - - /** - * Adds another colorStop - * @param {Object} colorStop Object with offset and color - * @return {fabric.Gradient} thisArg - */ - addColorStop: function(colorStop) { - for (var position in colorStop) { - var color = new fabric.Color(colorStop[position]); - this.colorStops.push({ - offset: position, - color: color.toRgb(), - opacity: color.getAlpha() - }); - } - return this; - }, - - /** - * Returns object representation of a gradient - * @return {Object} - */ - toObject: function() { - return { - type: this.type, - coords: this.coords, - colorStops: this.colorStops, - offsetX: this.offsetX, - offsetY: this.offsetY - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an gradient - * @param {Object} object Object to create a gradient for - * @param {Boolean} normalize Whether coords should be normalized - * @return {String} SVG representation of an gradient (linear/radial) - */ - toSVG: function(object) { - var coords = fabric.util.object.clone(this.coords), - markup, commonAttributes; - - // colorStops must be sorted ascending - this.colorStops.sort(function(a, b) { - return a.offset - b.offset; - }); - - if (!(object.group && object.group.type === 'path-group')) { - for (var prop in coords) { - if (prop === 'x1' || prop === 'x2' || prop === 'r2') { - coords[prop] += this.offsetX - object.width / 2; - } - else if (prop === 'y1' || prop === 'y2') { - coords[prop] += this.offsetY - object.height / 2; - } - } - } - - commonAttributes = 'id="SVGID_' + this.id + - '" gradientUnits="userSpaceOnUse"'; - if (this.gradientTransform) { - commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" '; - } - if (this.type === 'linear') { - markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - } - else if (this.type === 'radial') { - markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - } - - for (var i = 0; i < this.colorStops.length; i++) { - markup.push( - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ); - } - - markup.push((this.type === 'linear' ? '\n' : '\n')); - - return markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns an instance of CanvasGradient - * @param {CanvasRenderingContext2D} ctx Context to render on - * @return {CanvasGradient} - */ - toLive: function(ctx) { - var gradient; - - if (!this.type) { - return; - } - - if (this.type === 'linear') { - gradient = ctx.createLinearGradient( - this.coords.x1, this.coords.y1, this.coords.x2, this.coords.y2); - } - else if (this.type === 'radial') { - gradient = ctx.createRadialGradient( - this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2); - } - - for (var i = 0, len = this.colorStops.length; i < len; i++) { - var color = this.colorStops[i].color, - opacity = this.colorStops[i].opacity, - offset = this.colorStops[i].offset; - - if (typeof opacity !== 'undefined') { - color = new fabric.Color(color).setAlpha(opacity).toRgba(); - } - gradient.addColorStop(parseFloat(offset), color); - } - - return gradient; - } - }); - - fabric.util.object.extend(fabric.Gradient, { - - /* _FROM_SVG_START_ */ - /** - * Returns {@link fabric.Gradient} instance from an SVG element - * @static - * @memberof fabric.Gradient - * @param {SVGGradientElement} el SVG gradient element - * @param {fabric.Object} instance - * @return {fabric.Gradient} Gradient instance - * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement - * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement - */ - fromElement: function(el, instance) { - - /** - * @example: - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * - */ - - var colorStopEls = el.getElementsByTagName('stop'), - type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'), - gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', - gradientTransform = el.getAttribute('gradientTransform'), - colorStops = [], - coords = { }, ellipseMatrix; - - if (type === 'linear') { - coords = getLinearCoords(el); - } - else if (type === 'radial') { - coords = getRadialCoords(el); - } - - for (var i = colorStopEls.length; i--; ) { - colorStops.push(getColorStop(colorStopEls[i])); - } - - ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); - - var gradient = new fabric.Gradient({ - type: type, - coords: coords, - colorStops: colorStops, - offsetX: -instance.left, - offsetY: -instance.top - }); - - if (gradientTransform || ellipseMatrix !== '') { - gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); - } - return gradient; - }, - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Gradient} instance from its object representation - * @static - * @memberof fabric.Gradient - * @param {Object} obj - * @param {Object} [options] Options object - */ - forObject: function(obj, options) { - options || (options = { }); - _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); - return new fabric.Gradient(options); - } - }); - - /** - * @private - */ - function _convertPercentUnitsToValues(object, options, gradientUnits) { - var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; - for (var prop in options) { - propValue = parseFloat(options[prop], 10); - if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { - multFactor = 0.01; - } - else { - multFactor = 1; - } - if (prop === 'x1' || prop === 'x2' || prop === 'r2') { - multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; - addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; - } - else if (prop === 'y1' || prop === 'y2') { - multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; - addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; - } - options[prop] = propValue * multFactor + addFactor; - } - if (object.type === 'ellipse' && options.r2 !== null && gradientUnits === 'objectBoundingBox' && object.rx !== object.ry) { - var scaleFactor = object.ry/object.rx; - ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; - if (options.y1) { - options.y1 /= scaleFactor; - } - if (options.y2) { - options.y2 /= scaleFactor; - } - } - return ellipseMatrix; - } -})(); - - -/** - * Pattern class - * @class fabric.Pattern - * @see {@link http://fabricjs.com/patterns/|Pattern demo} - * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo} - * @see {@link fabric.Pattern#initialize} for constructor definition - */ -fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { - - /** - * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @type String - * @default - */ - repeat: 'repeat', - - /** - * Pattern horizontal offset from object's left/top corner - * @type Number - * @default - */ - offsetX: 0, - - /** - * Pattern vertical offset from object's left/top corner - * @type Number - * @default - */ - offsetY: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Pattern} thisArg - */ - initialize: function(options) { - options || (options = { }); - - this.id = fabric.Object.__uid++; - - if (options.source) { - if (typeof options.source === 'string') { - // function string - if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { - this.source = new Function(fabric.util.getFunctionBody(options.source)); - } - else { - // img src string - var _this = this; - this.source = fabric.util.createImage(); - fabric.util.loadImage(options.source, function(img) { - _this.source = img; - }); - } - } - else { - // img element - this.source = options.source; - } - } - if (options.repeat) { - this.repeat = options.repeat; - } - if (options.offsetX) { - this.offsetX = options.offsetX; - } - if (options.offsetY) { - this.offsetY = options.offsetY; - } - }, - - /** - * Returns object representation of a pattern - * @return {Object} Object representation of a pattern instance - */ - toObject: function() { - - var source; - - // callback - if (typeof this.source === 'function') { - source = String(this.source); - } - // element - else if (typeof this.source.src === 'string') { - source = this.source.src; - } - - return { - source: source, - repeat: this.repeat, - offsetX: this.offsetX, - offsetY: this.offsetY - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a pattern - * @param {fabric.Object} object - * @return {String} SVG representation of a pattern - */ - toSVG: function(object) { - var patternSource = typeof this.source === 'function' ? this.source() : this.source, - patternWidth = patternSource.width / object.getWidth(), - patternHeight = patternSource.height / object.getHeight(), - patternImgSrc = ''; - - if (patternSource.src) { - patternImgSrc = patternSource.src; - } - else if (patternSource.toDataURL) { - patternImgSrc = patternSource.toDataURL(); - } - - return '' + - '' + - ''; - }, - /* _TO_SVG_END_ */ - - /** - * Returns an instance of CanvasPattern - * @param {CanvasRenderingContext2D} ctx Context to create pattern - * @return {CanvasPattern} - */ - toLive: function(ctx) { - var source = typeof this.source === 'function' - ? this.source() - : this.source; - - // if the image failed to load, return, and allow rest to continue loading - if (!source) { - return ''; - } - - // if an image - if (typeof source.src !== 'undefined') { - if (!source.complete) { - return ''; - } - if (source.naturalWidth === 0 || source.naturalHeight === 0) { - return ''; - } - } - return ctx.createPattern(source, this.repeat); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Shadow) { - fabric.warn('fabric.Shadow is already defined.'); - return; - } - - /** - * Shadow class - * @class fabric.Shadow - * @see {@link http://fabricjs.com/shadows/|Shadow demo} - * @see {@link fabric.Shadow#initialize} for constructor definition - */ - fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { - - /** - * Shadow color - * @type String - * @default - */ - color: 'rgb(0,0,0)', - - /** - * Shadow blur - * @type Number - */ - blur: 0, - - /** - * Shadow horizontal offset - * @type Number - * @default - */ - offsetX: 0, - - /** - * Shadow vertical offset - * @type Number - * @default - */ - offsetY: 0, - - /** - * Whether the shadow should affect stroke operations - * @type Boolean - * @default - */ - affectStroke: false, - - /** - * Indicates whether toObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Constructor - * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Shadow} thisArg - */ - initialize: function(options) { - - if (typeof options === 'string') { - options = this._parseShadow(options); - } - - for (var prop in options) { - this[prop] = options[prop]; - } - - this.id = fabric.Object.__uid++; - }, - - /** - * @private - * @param {String} shadow Shadow value to parse - * @return {Object} Shadow object with color, offsetX, offsetY and blur - */ - _parseShadow: function(shadow) { - var shadowStr = shadow.trim(), - offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], - color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; - - return { - color: color.trim(), - offsetX: parseInt(offsetsAndBlur[1], 10) || 0, - offsetY: parseInt(offsetsAndBlur[2], 10) || 0, - blur: parseInt(offsetsAndBlur[3], 10) || 0 - }; - }, - - /** - * Returns a string representation of an instance - * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow - * @return {String} Returns CSS3 text-shadow declaration - */ - toString: function() { - return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a shadow - * @param {fabric.Object} object - * @return {String} SVG representation of a shadow - */ - toSVG: function(object) { - var mode = 'SourceAlpha'; - - if (object && (object.fill === this.color || object.stroke === this.color)) { - mode = 'SourceGraphic'; - } - - return ( - '' + - '' + - '' + - '' + - '' + - '' + - '' + - ''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns object representation of a shadow - * @return {Object} Object representation of a shadow instance - */ - toObject: function() { - if (this.includeDefaultValues) { - return { - color: this.color, - blur: this.blur, - offsetX: this.offsetX, - offsetY: this.offsetY - }; - } - var obj = { }, proto = fabric.Shadow.prototype; - if (this.color !== proto.color) { - obj.color = this.color; - } - if (this.blur !== proto.blur) { - obj.blur = this.blur; - } - if (this.offsetX !== proto.offsetX) { - obj.offsetX = this.offsetX; - } - if (this.offsetY !== proto.offsetY) { - obj.offsetY = this.offsetY; - } - return obj; - } - }); - - /** - * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") - * @static - * @field - * @memberOf fabric.Shadow - */ - fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function () { - - 'use strict'; - - if (fabric.StaticCanvas) { - fabric.warn('fabric.StaticCanvas is already defined.'); - return; - } - - // aliases for faster resolution - var extend = fabric.util.object.extend, - getElementOffset = fabric.util.getElementOffset, - removeFromArray = fabric.util.removeFromArray, - - CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); - - /** - * Static canvas class - * @class fabric.StaticCanvas - * @mixes fabric.Collection - * @mixes fabric.Observable - * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo} - * @see {@link fabric.StaticCanvas#initialize} for constructor definition - * @fires before:render - * @fires after:render - * @fires canvas:cleared - * @fires object:added - * @fires object:removed - */ - fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { - - /** - * Constructor - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(el, options) { - options || (options = { }); - - this._initStatic(el, options); - fabric.StaticCanvas.activeInstance = this; - }, - - /** - * Background color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. - * @type {(String|fabric.Pattern)} - * @default - */ - backgroundColor: '', - - /** - * Background image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. - * Backwards incompatibility note: The "backgroundImageOpacity" - * and "backgroundImageStretch" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. - * @type fabric.Image - * @default - */ - backgroundImage: null, - - /** - * Overlay color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayColor} - * @since 1.3.9 - * @type {(String|fabric.Pattern)} - * @default - */ - overlayColor: '', - - /** - * Overlay image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. - * Backwards incompatibility note: The "overlayImageLeft" - * and "overlayImageTop" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#left} and {@link fabric.Image#top}. - * @type fabric.Image - * @default - */ - overlayImage: null, - - /** - * Indicates whether toObject/toDatalessObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Indicates whether objects' state should be saved - * @type Boolean - * @default - */ - stateful: true, - - /** - * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. - * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once - * (followed by a manual rendering after addition/deletion) - * @type Boolean - * @default - */ - renderOnAddRemove: true, - - /** - * Function that determines clipping of entire canvas area - * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} - * @type Function - * @default - */ - clipTo: null, - - /** - * Indicates whether object controls (borders/controls) are rendered above overlay image - * @type Boolean - * @default - */ - controlsAboveOverlay: false, - - /** - * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas - * @type Boolean - * @default - */ - allowTouchScrolling: false, - - /** - * Indicates whether this canvas will use image smoothing, this is on by default in browsers - * @type Boolean - * @default - */ - imageSmoothingEnabled: true, - - /** - * The transformation (in the format of Canvas transform) which focuses the viewport - * @type Array - * @default - */ - viewportTransform: [1, 0, 0, 1, 0, 0], - - /** - * Callback; invoked right before object is about to be scaled/rotated - */ - onBeforeScaleRotate: function () { - /* NOOP */ - }, - - /** - * @private - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - */ - _initStatic: function(el, options) { - this._objects = []; - - this._createLowerCanvas(el); - this._initOptions(options); - this._setImageSmoothing(); - - if (options.overlayImage) { - this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); - } - if (options.backgroundImage) { - this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); - } - if (options.backgroundColor) { - this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); - } - if (options.overlayColor) { - this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); - } - this.calcOffset(); - }, - - /** - * Calculates canvas element offset relative to the document - * This method is also attached as "resize" event handler of window - * @return {fabric.Canvas} instance - * @chainable - */ - calcOffset: function () { - this._offset = getElementOffset(this.lowerCanvasEl); - return this; - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to - * @param {Function} callback callback to invoke when image is loaded and set as an overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} - * @example Normal overlayImage with left/top = 0 - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example overlayImage with different properties - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched overlayImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched overlayImage #2 - width/height correspond to canvas width/height - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - */ - setOverlayImage: function (image, callback, options) { - return this.__setBgOverlayImage('overlayImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to - * @param {Function} callback Callback to invoke when image is loaded and set as background - * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} - * @example Normal backgroundImage with left/top = 0 - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example backgroundImage with different properties - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - */ - setBackgroundImage: function (image, callback, options) { - return this.__setBgOverlayImage('backgroundImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas - * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} - * @example Normal overlayColor - color value - * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor with repeat and offset - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setOverlayColor: function(overlayColor, callback) { - return this.__setBgOverlayColor('overlayColor', overlayColor, callback); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas - * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} - * @example Normal backgroundColor - color value - * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor with repeat and offset - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setBackgroundColor: function(backgroundColor, callback) { - return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); - }, - - /** - * @private - * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} - */ - _setImageSmoothing: function(){ - var ctx = this.getContext(); - - ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} - * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) - * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to - * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. - */ - __setBgOverlayImage: function(property, image, callback, options) { - if (typeof image === 'string') { - fabric.util.loadImage(image, function(img) { - this[property] = new fabric.Image(img, options); - callback && callback(); - }, this); - } - else { - this[property] = image; - callback && callback(); - } - - return this; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} - * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) - * @param {(Object|String|null)} color Object with pattern information, color value or null - * @param {Function} [callback] Callback is invoked when color is set - */ - __setBgOverlayColor: function(property, color, callback) { - if (color && color.source) { - var _this = this; - fabric.util.loadImage(color.source, function(img) { - _this[property] = new fabric.Pattern({ - source: img, - repeat: color.repeat, - offsetX: color.offsetX, - offsetY: color.offsetY - }); - callback && callback(); - }); - } - else { - this[property] = color; - callback && callback(); - } - - return this; - }, - - /** - * @private - */ - _createCanvasElement: function() { - var element = fabric.document.createElement('canvas'); - if (!element.style) { - element.style = { }; - } - if (!element) { - throw CANVAS_INIT_ERROR; - } - this._initCanvasElement(element); - return element; - }, - - /** - * @private - * @param {HTMLElement} element - */ - _initCanvasElement: function(element) { - fabric.util.createCanvasElement(element); - - if (typeof element.getContext === 'undefined') { - throw CANVAS_INIT_ERROR; - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initOptions: function (options) { - for (var prop in options) { - this[prop] = options[prop]; - } - - this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; - this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; - - if (!this.lowerCanvasEl.style) { - return; - } - - this.lowerCanvasEl.width = this.width; - this.lowerCanvasEl.height = this.height; - - this.lowerCanvasEl.style.width = this.width + 'px'; - this.lowerCanvasEl.style.height = this.height + 'px'; - - this.viewportTransform = this.viewportTransform.slice(); - }, - - /** - * Creates a bottom canvas - * @private - * @param {HTMLElement} [canvasEl] - */ - _createLowerCanvas: function (canvasEl) { - this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); - this._initCanvasElement(this.lowerCanvasEl); - - fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); - - if (this.interactive) { - this._applyCanvasStyle(this.lowerCanvasEl); - } - - this.contextContainer = this.lowerCanvasEl.getContext('2d'); - }, - - /** - * Returns canvas width (in px) - * @return {Number} - */ - getWidth: function () { - return this.width; - }, - - /** - * Returns canvas height (in px) - * @return {Number} - */ - getHeight: function () { - return this.height; - }, - - /** - * Sets width of this canvas instance - * @param {Number|String} value Value to set width to - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} instance - * @chainable true - */ - setWidth: function (value, options) { - return this.setDimensions({ width: value }, options); - }, - - /** - * Sets height of this canvas instance - * @param {Number|String} value Value to set height to - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} instance - * @chainable true - */ - setHeight: function (value, options) { - return this.setDimensions({ height: value }, options); - }, - - /** - * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) - * @param {Object} dimensions Object with width/height properties - * @param {Number|String} [dimensions.width] Width of canvas element - * @param {Number|String} [dimensions.height] Height of canvas element - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} thisArg - * @chainable - */ - setDimensions: function (dimensions, options) { - var cssValue; - - options = options || {}; - - for (var prop in dimensions) { - cssValue = dimensions[prop]; - - if (!options.cssOnly) { - this._setBackstoreDimension(prop, dimensions[prop]); - cssValue += 'px'; - } - - if (!options.backstoreOnly) { - this._setCssDimension(prop, cssValue); - } - } - - if (!options.cssOnly) { - this.renderAll(); - } - - this.calcOffset(); - - return this; - }, - - /** - * Helper for setting width/height - * @private - * @param {String} prop property (width|height) - * @param {Number} value value to set property to - * @return {fabric.Canvas} instance - * @chainable true - */ - _setBackstoreDimension: function (prop, value) { - this.lowerCanvasEl[prop] = value; - - if (this.upperCanvasEl) { - this.upperCanvasEl[prop] = value; - } - - if (this.cacheCanvasEl) { - this.cacheCanvasEl[prop] = value; - } - - this[prop] = value; - - return this; - }, - - /** - * Helper for setting css width/height - * @private - * @param {String} prop property (width|height) - * @param {String} value value to set property to - * @return {fabric.Canvas} instance - * @chainable true - */ - _setCssDimension: function (prop, value) { - this.lowerCanvasEl.style[prop] = value; - - if (this.upperCanvasEl) { - this.upperCanvasEl.style[prop] = value; - } - - if (this.wrapperEl) { - this.wrapperEl.style[prop] = value; - } - - return this; - }, - - /** - * Returns canvas zoom level - * @return {Number} - */ - getZoom: function () { - return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); - }, - - /** - * Sets viewport transform of this canvas instance - * @param {Array} vpt the transform in the form of context.transform - * @return {fabric.Canvas} instance - * @chainable true - */ - setViewportTransform: function (vpt) { - this.viewportTransform = vpt; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Sets zoom level of this canvas instance, zoom centered around point - * @param {fabric.Point} point to zoom with respect to - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - zoomToPoint: function (point, value) { - // TODO: just change the scale, preserve other transformations - var before = point; - point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); - this.viewportTransform[0] = value; - this.viewportTransform[3] = value; - var after = fabric.util.transformPoint(point, this.viewportTransform); - this.viewportTransform[4] += before.x - after.x; - this.viewportTransform[5] += before.y - after.y; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Sets zoom level of this canvas instance - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - setZoom: function (value) { - this.zoomToPoint(new fabric.Point(0, 0), value); - return this; - }, - - /** - * Pan viewport so as to place point at top left corner of canvas - * @param {fabric.Point} point to move to - * @return {fabric.Canvas} instance - * @chainable true - */ - absolutePan: function (point) { - this.viewportTransform[4] = -point.x; - this.viewportTransform[5] = -point.y; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Pans viewpoint relatively - * @param {fabric.Point} point (position vector) to move by - * @return {fabric.Canvas} instance - * @chainable true - */ - relativePan: function (point) { - return this.absolutePan(new fabric.Point( - -point.x - this.viewportTransform[4], - -point.y - this.viewportTransform[5] - )); - }, - - /** - * Returns <canvas> element corresponding to this instance - * @return {HTMLCanvasElement} - */ - getElement: function () { - return this.lowerCanvasEl; - }, - - /** - * Returns currently selected object, if any - * @return {fabric.Object} - */ - getActiveObject: function() { - return null; - }, - - /** - * Returns currently selected group of object, if any - * @return {fabric.Group} - */ - getActiveGroup: function() { - return null; - }, - - /** - * Given a context, renders an object on that context - * @param {CanvasRenderingContext2D} ctx Context to render object on - * @param {fabric.Object} object Object to render - * @private - */ - _draw: function (ctx, object) { - if (!object) { - return; - } - - ctx.save(); - var v = this.viewportTransform; - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - object.render(ctx); - ctx.restore(); - if (!this.controlsAboveOverlay) { - object._renderControls(ctx); - } - }, - - /** - * @private - * @param {fabric.Object} obj Object that was added - */ - _onObjectAdded: function(obj) { - this.stateful && obj.setupState(); - obj.canvas = this; - obj.setCoords(); - this.fire('object:added', { target: obj }); - obj.fire('added'); - }, - - /** - * @private - * @param {fabric.Object} obj Object that was removed - */ - _onObjectRemoved: function(obj) { - // removing active object should fire "selection:cleared" events - if (this.getActiveObject() === obj) { - this.fire('before:selection:cleared', { target: obj }); - this._discardActiveObject(); - this.fire('selection:cleared'); - } - - this.fire('object:removed', { target: obj }); - obj.fire('removed'); - }, - - /** - * Clears specified context of canvas element - * @param {CanvasRenderingContext2D} ctx Context to clear - * @return {fabric.Canvas} thisArg - * @chainable - */ - clearContext: function(ctx) { - ctx.clearRect(0, 0, this.width, this.height); - return this; - }, - - /** - * Returns context of canvas where objects are drawn - * @return {CanvasRenderingContext2D} - */ - getContext: function () { - return this.contextContainer; - }, - - /** - * Clears all contexts (background, main, top) of an instance - * @return {fabric.Canvas} thisArg - * @chainable - */ - clear: function () { - this._objects.length = 0; - if (this.discardActiveGroup) { - this.discardActiveGroup(); - } - if (this.discardActiveObject) { - this.discardActiveObject(); - } - this.clearContext(this.contextContainer); - if (this.contextTop) { - this.clearContext(this.contextTop); - } - this.fire('canvas:cleared'); - this.renderAll(); - return this; - }, - - /** - * Renders both the top canvas and the secondary container canvas. - * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas - * @return {fabric.Canvas} instance - * @chainable - */ - renderAll: function (allOnTop) { - var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], - activeGroup = this.getActiveGroup(); - - if (this.contextTop && this.selection && !this._groupSelector) { - this.clearContext(this.contextTop); - } - - if (!allOnTop) { - this.clearContext(canvasToDrawOn); - } - - this.fire('before:render'); - - if (this.clipTo) { - fabric.util.clipContext(this, canvasToDrawOn); - } - - this._renderBackground(canvasToDrawOn); - this._renderObjects(canvasToDrawOn, activeGroup); - this._renderActiveGroup(canvasToDrawOn, activeGroup); - - if (this.clipTo) { - canvasToDrawOn.restore(); - } - - this._renderOverlay(canvasToDrawOn); - - if (this.controlsAboveOverlay && this.interactive) { - this.drawControls(canvasToDrawOn); - } - - this.fire('after:render'); - - return this; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {fabric.Group} activeGroup - */ - _renderObjects: function(ctx, activeGroup) { - var i, length; - - // fast path - if (!activeGroup) { - for (i = 0, length = this._objects.length; i < length; ++i) { - this._draw(ctx, this._objects[i]); - } - } - else { - for (i = 0, length = this._objects.length; i < length; ++i) { - if (this._objects[i] && !activeGroup.contains(this._objects[i])) { - this._draw(ctx, this._objects[i]); - } - } - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {fabric.Group} activeGroup - */ - _renderActiveGroup: function(ctx, activeGroup) { - - // delegate rendering to group selection (if one exists) - if (activeGroup) { - - //Store objects in group preserving order, then replace - var sortedObjects = []; - this.forEachObject(function (object) { - if (activeGroup.contains(object)) { - sortedObjects.push(object); - } - }); - activeGroup._set('objects', sortedObjects); - this._draw(ctx, activeGroup); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderBackground: function(ctx) { - if (this.backgroundColor) { - ctx.fillStyle = this.backgroundColor.toLive - ? this.backgroundColor.toLive(ctx) - : this.backgroundColor; - - ctx.fillRect( - this.backgroundColor.offsetX || 0, - this.backgroundColor.offsetY || 0, - this.width, - this.height); - } - if (this.backgroundImage) { - this._draw(ctx, this.backgroundImage); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderOverlay: function(ctx) { - if (this.overlayColor) { - ctx.fillStyle = this.overlayColor.toLive - ? this.overlayColor.toLive(ctx) - : this.overlayColor; - - ctx.fillRect( - this.overlayColor.offsetX || 0, - this.overlayColor.offsetY || 0, - this.width, - this.height); - } - if (this.overlayImage) { - this._draw(ctx, this.overlayImage); - } - }, - - /** - * Method to render only the top canvas. - * Also used to render the group selection box. - * @return {fabric.Canvas} thisArg - * @chainable - */ - renderTop: function () { - var ctx = this.contextTop || this.contextContainer; - this.clearContext(ctx); - - // we render the top context - last object - if (this.selection && this._groupSelector) { - this._drawSelection(); - } - - // delegate rendering to group selection if one exists - // used for drawing selection borders/controls - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - activeGroup.render(ctx); - } - - this._renderOverlay(ctx); - - this.fire('after:render'); - - return this; - }, - - /** - * Returns coordinates of a center of canvas. - * Returned value is an object with top and left properties - * @return {Object} object with "top" and "left" number values - */ - getCenter: function () { - return { - top: this.getHeight() / 2, - left: this.getWidth() / 2 - }; - }, - - /** - * Centers object horizontally. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center horizontally - * @return {fabric.Canvas} thisArg - */ - centerObjectH: function (object) { - this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); - this.renderAll(); - return this; - }, - - /** - * Centers object vertically. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObjectV: function (object) { - this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); - this.renderAll(); - return this; - }, - - /** - * Centers object vertically and horizontally. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically and horizontally - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObject: function(object) { - var center = this.getCenter(); - - this._centerObject(object, new fabric.Point(center.left, center.top)); - this.renderAll(); - return this; - }, - - /** - * @private - * @param {fabric.Object} object Object to center - * @param {fabric.Point} center Center point - * @return {fabric.Canvas} thisArg - * @chainable - */ - _centerObject: function(object, center) { - object.setPositionByOrigin(center, 'center', 'center'); - return this; - }, - - /** - * Returs dataless JSON representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} json string - */ - toDatalessJSON: function (propertiesToInclude) { - return this.toDatalessObject(propertiesToInclude); - }, - - /** - * Returns object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function (propertiesToInclude) { - return this._toObjectMethod('toObject', propertiesToInclude); - }, - - /** - * Returns dataless object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function (propertiesToInclude) { - return this._toObjectMethod('toDatalessObject', propertiesToInclude); - }, - - /** - * @private - */ - _toObjectMethod: function (methodName, propertiesToInclude) { - - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - this.discardActiveGroup(); - } - - var data = { - objects: this._toObjects(methodName, propertiesToInclude) - }; - - extend(data, this.__serializeBgOverlay()); - - fabric.util.populateWithProperties(this, data, propertiesToInclude); - - if (activeGroup) { - this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), { - originX: 'center', - originY: 'center' - })); - activeGroup.forEachObject(function(o) { - o.set('active', true); - }); - - if (this._currentTransform) { - this._currentTransform.target = this.getActiveGroup(); - } - } - - return data; - }, - - /** - * @private - */ - _toObjects: function(methodName, propertiesToInclude) { - return this.getObjects().map(function(instance) { - return this._toObject(instance, methodName, propertiesToInclude); - }, this); - }, - - /** - * @private - */ - _toObject: function(instance, methodName, propertiesToInclude) { - var originalValue; - - if (!this.includeDefaultValues) { - originalValue = instance.includeDefaultValues; - instance.includeDefaultValues = false; - } - var object = instance[methodName](propertiesToInclude); - if (!this.includeDefaultValues) { - instance.includeDefaultValues = originalValue; - } - return object; - }, - - /** - * @private - */ - __serializeBgOverlay: function() { - var data = { - background: (this.backgroundColor && this.backgroundColor.toObject) - ? this.backgroundColor.toObject() - : this.backgroundColor - }; - - if (this.overlayColor) { - data.overlay = this.overlayColor.toObject - ? this.overlayColor.toObject() - : this.overlayColor; - } - if (this.backgroundImage) { - data.backgroundImage = this.backgroundImage.toObject(); - } - if (this.overlayImage) { - data.overlayImage = this.overlayImage.toObject(); - } - - return data; - }, - - /* _TO_SVG_START_ */ - /** - * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, - * a zoomed canvas will then produce zoomed SVG output. - * @type Boolean - * @default - */ - svgViewportTransformation: true, - - /** - * Returns SVG representation of canvas - * @function - * @param {Object} [options] Options object for SVG output - * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included - * @param {Object} [options.viewBox] SVG viewbox object - * @param {Number} [options.viewBox.x] x-cooridnate of viewbox - * @param {Number} [options.viewBox.y] y-coordinate of viewbox - * @param {Number} [options.viewBox.width] Width of viewbox - * @param {Number} [options.viewBox.height] Height of viewbox - * @param {String} [options.encoding=UTF-8] Encoding of SVG output - * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. - * @return {String} SVG string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} - * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} - * @example Normal SVG output - * var svg = canvas.toSVG(); - * @example SVG output without preamble (without <?xml ../>) - * var svg = canvas.toSVG({suppressPreamble: true}); - * @example SVG output with viewBox attribute - * var svg = canvas.toSVG({ - * viewBox: { - * x: 100, - * y: 100, - * width: 200, - * height: 300 - * } - * }); - * @example SVG output with different encoding (default: UTF-8) - * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); - * @example Modify SVG output with reviver function - * var svg = canvas.toSVG(null, function(svg) { - * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); - * }); - */ - toSVG: function(options, reviver) { - options || (options = { }); - - var markup = []; - - this._setSVGPreamble(markup, options); - this._setSVGHeader(markup, options); - - this._setSVGBgOverlayColor(markup, 'backgroundColor'); - this._setSVGBgOverlayImage(markup, 'backgroundImage'); - - this._setSVGObjects(markup, reviver); - - this._setSVGBgOverlayColor(markup, 'overlayColor'); - this._setSVGBgOverlayImage(markup, 'overlayImage'); - - markup.push(''); - - return markup.join(''); - }, - - /** - * @private - */ - _setSVGPreamble: function(markup, options) { - if (!options.suppressPreamble) { - markup.push( - '', - '\n' - ); - } - }, - - /** - * @private - */ - _setSVGHeader: function(markup, options) { - var width, height, vpt; - - if (options.viewBox) { - width = options.viewBox.width; - height = options.viewBox.height; - } - else { - width = this.width; - height = this.height; - if (!this.svgViewportTransformation) { - vpt = this.viewportTransform; - width /= vpt[0]; - height /= vpt[3]; - } - } - - markup.push( - '', - 'Created with Fabric.js ', fabric.version, '', - '', - fabric.createSVGFontFacesMarkup(this.getObjects()), - fabric.createSVGRefElementsMarkup(this), - '' - ); - }, - - /** - * @private - */ - _setSVGObjects: function(markup, reviver) { - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - this.discardActiveGroup(); - } - for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { - markup.push(objects[i].toSVG(reviver)); - } - if (activeGroup) { - this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); - activeGroup.forEachObject(function(o) { - o.set('active', true); - }); - } - }, - - /** - * @private - */ - _setSVGBgOverlayImage: function(markup, property) { - if (this[property] && this[property].toSVG) { - markup.push(this[property].toSVG()); - } - }, - - /** - * @private - */ - _setSVGBgOverlayColor: function(markup, property) { - if (this[property] && this[property].source) { - markup.push( - '' - ); - } - else if (this[property] && property === 'overlayColor') { - markup.push( - '' - ); - } - }, - /* _TO_SVG_END_ */ - - /** - * Moves an object to the bottom of the stack of drawn objects - * @param {fabric.Object} object Object to send to back - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendToBack: function (object) { - removeFromArray(this._objects, object); - this._objects.unshift(object); - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object to the top of the stack of drawn objects - * @param {fabric.Object} object Object to send - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringToFront: function (object) { - removeFromArray(this._objects, object); - this._objects.push(object); - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object down in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendBackwards: function (object, intersecting) { - var idx = this._objects.indexOf(object); - - // if object is not on the bottom of stack - if (idx !== 0) { - var newIdx = this._findNewLowerIndex(object, idx, intersecting); - - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - this.renderAll && this.renderAll(); - } - return this; - }, - - /** - * @private - */ - _findNewLowerIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse down the stack looking for the nearest intersecting object - for (var i = idx - 1; i >= 0; --i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx - 1; - } - - return newIdx; - }, - - /** - * Moves an object up in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringForward: function (object, intersecting) { - var idx = this._objects.indexOf(object); - - // if object is not on top of stack (last item in an array) - if (idx !== this._objects.length - 1) { - var newIdx = this._findNewUpperIndex(object, idx, intersecting); - - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - this.renderAll && this.renderAll(); - } - return this; - }, - - /** - * @private - */ - _findNewUpperIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse up the stack looking for the nearest intersecting object - for (var i = idx + 1; i < this._objects.length; ++i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx + 1; - } - - return newIdx; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Number} index Position to move to - * @return {fabric.Canvas} thisArg - * @chainable - */ - moveTo: function (object, index) { - removeFromArray(this._objects, object); - this._objects.splice(index, 0, object); - return this.renderAll && this.renderAll(); - }, - - /** - * Clears a canvas element and removes all event listeners - * @return {fabric.Canvas} thisArg - * @chainable - */ - dispose: function () { - this.clear(); - this.interactive && this.removeListeners(); - return this; - }, - - /** - * Returns a string representation of an instance - * @return {String} string representation of an instance - */ - toString: function () { - return '#'; - } - }); - - extend(fabric.StaticCanvas.prototype, fabric.Observable); - extend(fabric.StaticCanvas.prototype, fabric.Collection); - extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); - - extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { - - /** - * @static - * @type String - * @default - */ - EMPTY_JSON: '{"objects": [], "background": "white"}', - - /** - * Provides a way to check support of some of the canvas methods - * (either those of HTMLCanvasElement itself, or rendering context) - * - * @param {String} methodName Method to check support for; - * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" - * @return {Boolean | null} `true` if method is supported (or at least exists), - * `null` if canvas element or context can not be initialized - */ - supports: function (methodName) { - var el = fabric.util.createCanvasElement(); - - if (!el || !el.getContext) { - return null; - } - - var ctx = el.getContext('2d'); - if (!ctx) { - return null; - } - - switch (methodName) { - - case 'getImageData': - return typeof ctx.getImageData !== 'undefined'; - - case 'setLineDash': - return typeof ctx.setLineDash !== 'undefined'; - - case 'toDataURL': - return typeof el.toDataURL !== 'undefined'; - - case 'toDataURLWithQuality': - try { - el.toDataURL('image/jpeg', 0); - return true; - } - catch (e) { } - return false; - - default: - return null; - } - } - }); - - /** - * Returns JSON representation of canvas - * @function - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} JSON string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} - * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} - * @example JSON without additional properties - * var json = canvas.toJSON(); - * @example JSON with additional properties included - * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); - * @example JSON without default values - * canvas.includeDefaultValues = false; - * var json = canvas.toJSON(); - */ - fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; - -})(); - - -/** - * BaseBrush class - * @class fabric.BaseBrush - * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} - */ -fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { - - /** - * Color of a brush - * @type String - * @default - */ - color: 'rgb(0, 0, 0)', - - /** - * Width of a brush - * @type Number - * @default - */ - width: 1, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), - * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Line endings style of a brush (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'round', - - /** - * Corner style of a brush (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'round', - - /** - * Sets shadow of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Object} thisArg - * @chainable - */ - setShadow: function(options) { - this.shadow = new fabric.Shadow(options); - return this; - }, - - /** - * Sets brush styles - * @private - */ - _setBrushStyles: function() { - var ctx = this.canvas.contextTop; - - ctx.strokeStyle = this.color; - ctx.lineWidth = this.width; - ctx.lineCap = this.strokeLineCap; - ctx.lineJoin = this.strokeLineJoin; - }, - - /** - * Sets brush shadow styles - * @private - */ - _setShadow: function() { - if (!this.shadow) { - return; - } - - var ctx = this.canvas.contextTop; - - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur; - ctx.shadowOffsetX = this.shadow.offsetX; - ctx.shadowOffsetY = this.shadow.offsetY; - }, - - /** - * Removes brush shadow styles - * @private - */ - _resetShadow: function() { - var ctx = this.canvas.contextTop; - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - } -}); - - -(function() { - - var utilMin = fabric.util.array.min, - utilMax = fabric.util.array.max; - - /** - * PencilBrush class - * @class fabric.PencilBrush - * @extends fabric.BaseBrush - */ - fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.PencilBrush} Instance of a pencil brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this._points = [ ]; - }, - - /** - * Inovoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this._prepareForDrawing(pointer); - // capture coordinates immediately - // this allows to draw dots (when movement never occurs) - this._captureDrawingPath(pointer); - this._render(); - }, - - /** - * Inovoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this._captureDrawingPath(pointer); - // redraw curve - // clear top canvas - this.canvas.clearContext(this.canvas.contextTop); - this._render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - this._finalizeAndAddPath(); - }, - - /** - * @private - * @param {Object} pointer Actual mouse position related to the canvas. - */ - _prepareForDrawing: function(pointer) { - - var p = new fabric.Point(pointer.x, pointer.y); - - this._reset(); - this._addPoint(p); - - this.canvas.contextTop.moveTo(p.x, p.y); - }, - - /** - * @private - * @param {fabric.Point} point Point to be added to points array - */ - _addPoint: function(point) { - this._points.push(point); - }, - - /** - * Clear points array and set contextTop canvas style. - * @private - */ - _reset: function() { - this._points.length = 0; - - this._setBrushStyles(); - this._setShadow(); - }, - - /** - * @private - * @param {Object} pointer Actual mouse position related to the canvas. - */ - _captureDrawingPath: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); - this._addPoint(pointerPoint); - }, - - /** - * Draw a smooth path on the topCanvas using quadraticCurveTo - * @private - */ - _render: function() { - var ctx = this.canvas.contextTop, - v = this.canvas.viewportTransform, - p1 = this._points[0], - p2 = this._points[1]; - - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - ctx.beginPath(); - - //if we only have 2 points in the path and they are the same - //it means that the user only clicked the canvas without moving the mouse - //then we should be drawing a dot. A path isn't drawn between two identical dots - //that's why we set them apart a bit - if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { - p1.x -= 0.5; - p2.x += 0.5; - } - ctx.moveTo(p1.x, p1.y); - - for (var i = 1, len = this._points.length; i < len; i++) { - // we pick the point between pi + 1 & pi + 2 as the - // end point and p1 as our control point. - var midPoint = p1.midPointFrom(p2); - ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); - - p1 = this._points[i]; - p2 = this._points[i + 1]; - } - // Draw last line as a straight line while - // we wait for the next point to be able to calculate - // the bezier control point - ctx.lineTo(p1.x, p1.y); - ctx.stroke(); - ctx.restore(); - }, - - /** - * Return an SVG path based on our captured points and their bounding box - * @private - */ - _getSVGPathData: function() { - this.box = this.getPathBoundingBox(this._points); - return this.convertPointsToSVGPath( - this._points, this.box.minX, this.box.minY); - }, - - /** - * Returns bounding box of a path based on given points - * @param {Array} points Array of points - * @return {Object} Object with minX, minY, maxX, maxY - */ - getPathBoundingBox: function(points) { - var xBounds = [], - yBounds = [], - p1 = points[0], - p2 = points[1], - startPoint = p1; - - for (var i = 1, len = points.length; i < len; i++) { - var midPoint = p1.midPointFrom(p2); - // with startPoint, p1 as control point, midpoint as end point - xBounds.push(startPoint.x); - xBounds.push(midPoint.x); - yBounds.push(startPoint.y); - yBounds.push(midPoint.y); - - p1 = points[i]; - p2 = points[i + 1]; - startPoint = midPoint; - } - - xBounds.push(p1.x); - yBounds.push(p1.y); - - return { - minX: utilMin(xBounds), - minY: utilMin(yBounds), - maxX: utilMax(xBounds), - maxY: utilMax(yBounds) - }; - }, - - /** - * Converts points to SVG path - * @param {Array} points Array of points - * @param {Number} minX - * @param {Number} minY - * @return {String} SVG path - */ - convertPointsToSVGPath: function(points, minX, minY) { - var path = [], - p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), - p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); - - path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' '); - for (var i = 1, len = points.length; i < len; i++) { - var midPoint = p1.midPointFrom(p2); - // p1 is our bezier control point - // midpoint is our endpoint - // start point is p(i-1) value. - path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); - p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); - if ((i + 1) < points.length) { - p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); - } - } - path.push('L ', p1.x, ' ', p1.y, ' '); - return path; - }, - - /** - * Creates fabric.Path object to add on canvas - * @param {String} pathData Path data - * @return {fabric.Path} Path to add on canvas - */ - createPath: function(pathData) { - var path = new fabric.Path(pathData); - path.fill = null; - path.stroke = this.color; - path.strokeWidth = this.width; - path.strokeLineCap = this.strokeLineCap; - path.strokeLineJoin = this.strokeLineJoin; - - if (this.shadow) { - this.shadow.affectStroke = true; - path.setShadow(this.shadow); - } - - return path; - }, - - /** - * On mouseup after drawing the path on contextTop canvas - * we use the points captured to create an new fabric path object - * and add it to the fabric canvas. - */ - _finalizeAndAddPath: function() { - var ctx = this.canvas.contextTop; - ctx.closePath(); - - var pathData = this._getSVGPathData().join(''); - if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { - // do not create 0 width/height paths, as they are - // rendered inconsistently across browsers - // Firefox 4, for example, renders a dot, - // whereas Chrome 10 renders nothing - this.canvas.renderAll(); - return; - } - - // set path origin coordinates based on our bounding box - var originLeft = this.box.minX + (this.box.maxX - this.box.minX) / 2, - originTop = this.box.minY + (this.box.maxY - this.box.minY) / 2; - - this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); - - var path = this.createPath(pathData); - path.set({ - left: originLeft, - top: originTop, - originX: 'center', - originY: 'center' - }); - - this.canvas.add(path); - path.setCoords(); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderAll(); - - // fire event 'path' created - this.canvas.fire('path:created', { path: path }); - } - }); -})(); - - -/** - * CircleBrush class - * @class fabric.CircleBrush - */ -fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { - - /** - * Width of a brush - * @type Number - * @default - */ - width: 10, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.CircleBrush} Instance of a circle brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.points = [ ]; - }, - /** - * Invoked inside on mouse down and mouse move - * @param {Object} pointer - */ - drawDot: function(pointer) { - var point = this.addPoint(pointer), - ctx = this.canvas.contextTop, - v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - ctx.fillStyle = point.fill; - ctx.beginPath(); - ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); - ctx.closePath(); - ctx.fill(); - - ctx.restore(); - }, - - /** - * Invoked on mouse down - */ - onMouseDown: function(pointer) { - this.points.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - this.drawDot(pointer); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.drawDot(pointer); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var circles = [ ]; - - for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i], - circle = new fabric.Circle({ - radius: point.radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: point.fill - }); - - this.shadow && circle.setShadow(this.shadow); - - circles.push(circle); - } - var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @param {Object} pointer - * @return {fabric.Point} Just added pointer point - */ - addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y), - - circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2, - - circleColor = new fabric.Color(this.color) - .setAlpha(fabric.util.getRandomInt(0, 100) / 100) - .toRgba(); - - pointerPoint.radius = circleRadius; - pointerPoint.fill = circleColor; - - this.points.push(pointerPoint); - - return pointerPoint; - } -}); - - -/** - * SprayBrush class - * @class fabric.SprayBrush - */ -fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { - - /** - * Width of a spray - * @type Number - * @default - */ - width: 10, - - /** - * Density of a spray (number of dots per chunk) - * @type Number - * @default - */ - density: 20, - - /** - * Width of spray dots - * @type Number - * @default - */ - dotWidth: 1, - - /** - * Width variance of spray dots - * @type Number - * @default - */ - dotWidthVariance: 1, - - /** - * Whether opacity of a dot should be random - * @type Boolean - * @default - */ - randomOpacity: false, - - /** - * Whether overlapping dots (rectangles) should be removed (for performance reasons) - * @type Boolean - * @default - */ - optimizeOverlapping: true, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.SprayBrush} Instance of a spray brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.sprayChunks = [ ]; - }, - - /** - * Invoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this.sprayChunks.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var rects = [ ]; - - for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - var sprayChunk = this.sprayChunks[i]; - - for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { - - var rect = new fabric.Rect({ - width: sprayChunk[j].width, - height: sprayChunk[j].width, - left: sprayChunk[j].x + 1, - top: sprayChunk[j].y + 1, - originX: 'center', - originY: 'center', - fill: this.color - }); - - this.shadow && rect.setShadow(this.shadow); - rects.push(rect); - } - } - - if (this.optimizeOverlapping) { - rects = this._getOptimizedRects(rects); - } - - var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @private - * @param {Array} rects - */ - _getOptimizedRects: function(rects) { - - // avoid creating duplicate rects at the same coordinates - var uniqueRects = { }, key; - - for (var i = 0, len = rects.length; i < len; i++) { - key = rects[i].left + '' + rects[i].top; - if (!uniqueRects[key]) { - uniqueRects[key] = rects[i]; - } - } - var uniqueRectsArray = [ ]; - for (key in uniqueRects) { - uniqueRectsArray.push(uniqueRects[key]); - } - - return uniqueRectsArray; - }, - - /** - * Renders brush - */ - render: function() { - var ctx = this.canvas.contextTop; - ctx.fillStyle = this.color; - - var v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { - var point = this.sprayChunkPoints[i]; - if (typeof point.opacity !== 'undefined') { - ctx.globalAlpha = point.opacity; - } - ctx.fillRect(point.x, point.y, point.width, point.width); - } - ctx.restore(); - }, - - /** - * @param {Object} pointer - */ - addSprayChunk: function(pointer) { - this.sprayChunkPoints = [ ]; - - var x, y, width, radius = this.width / 2; - - for (var i = 0; i < this.density; i++) { - - x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); - y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); - - if (this.dotWidthVariance) { - width = fabric.util.getRandomInt( - // bottom clamp width to 1 - Math.max(1, this.dotWidth - this.dotWidthVariance), - this.dotWidth + this.dotWidthVariance); - } - else { - width = this.dotWidth; - } - - var point = new fabric.Point(x, y); - point.width = width; - - if (this.randomOpacity) { - point.opacity = fabric.util.getRandomInt(0, 100) / 100; - } - - this.sprayChunkPoints.push(point); - } - - this.sprayChunks.push(this.sprayChunkPoints); - } -}); - - -/** - * PatternBrush class - * @class fabric.PatternBrush - * @extends fabric.BaseBrush - */ -fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { - - getPatternSrc: function() { - - var dotWidth = 20, - dotDistance = 5, - patternCanvas = fabric.document.createElement('canvas'), - patternCtx = patternCanvas.getContext('2d'); - - patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; - - patternCtx.fillStyle = this.color; - patternCtx.beginPath(); - patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); - patternCtx.closePath(); - patternCtx.fill(); - - return patternCanvas; - }, - - getPatternSrcFunction: function() { - return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); - }, - - /** - * Creates "pattern" instance property - */ - getPattern: function() { - return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); - }, - - /** - * Sets brush styles - */ - _setBrushStyles: function() { - this.callSuper('_setBrushStyles'); - this.canvas.contextTop.strokeStyle = this.getPattern(); - }, - - /** - * Creates path - */ - createPath: function(pathData) { - var path = this.callSuper('createPath', pathData); - path.stroke = new fabric.Pattern({ - source: this.source || this.getPatternSrcFunction() - }); - return path; - } -}); - - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately - * @param {Object} [options] Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} - * @example Generate jpeg dataURL with lower quality - * var dataURL = canvas.toDataURL({ - * format: 'jpeg', - * quality: 0.8 - * }); - * @example Generate cropped png dataURL (clipping of canvas) - * var dataURL = canvas.toDataURL({ - * format: 'png', - * left: 100, - * top: 100, - * width: 200, - * height: 200 - * }); - * @example Generate double scaled png dataURL - * var dataURL = canvas.toDataURL({ - * format: 'png', - * multiplier: 2 - * }); - */ - toDataURL: function (options) { - options || (options = { }); - - var format = options.format || 'png', - quality = options.quality || 1, - multiplier = options.multiplier || 1, - cropping = { - left: options.left, - top: options.top, - width: options.width, - height: options.height - }; - - if (multiplier !== 1) { - return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); - } - else { - return this.__toDataURL(format, quality, cropping); - } - }, - - /** - * @private - */ - __toDataURL: function(format, quality, cropping) { - - this.renderAll(true); - - var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, - croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); - - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (format === 'jpg') { - format = 'jpeg'; - } - - var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) - ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) - : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - - if (croppedCanvasEl) { - croppedCanvasEl = null; - } - - return data; - }, - - /** - * @private - */ - __getCroppedCanvas: function(canvasEl, cropping) { - - var croppedCanvasEl, - croppedCtx, - shouldCrop = 'left' in cropping || - 'top' in cropping || - 'width' in cropping || - 'height' in cropping; - - if (shouldCrop) { - - croppedCanvasEl = fabric.util.createCanvasElement(); - croppedCtx = croppedCanvasEl.getContext('2d'); - - croppedCanvasEl.width = cropping.width || this.width; - croppedCanvasEl.height = cropping.height || this.height; - - croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); - } - - return croppedCanvasEl; - }, - - /** - * @private - */ - __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { - - var origWidth = this.getWidth(), - origHeight = this.getHeight(), - scaledWidth = origWidth * multiplier, - scaledHeight = origHeight * multiplier, - activeObject = this.getActiveObject(), - activeGroup = this.getActiveGroup(), - - ctx = this.contextTop || this.contextContainer; - - if (multiplier > 1) { - this.setWidth(scaledWidth).setHeight(scaledHeight); - } - ctx.scale(multiplier, multiplier); - - if (cropping.left) { - cropping.left *= multiplier; - } - if (cropping.top) { - cropping.top *= multiplier; - } - if (cropping.width) { - cropping.width *= multiplier; - } - else if (multiplier < 1) { - cropping.width = scaledWidth; - } - if (cropping.height) { - cropping.height *= multiplier; - } - else if (multiplier < 1) { - cropping.height = scaledHeight; - } - - if (activeGroup) { - // not removing group due to complications with restoring it with correct state afterwords - this._tempRemoveBordersControlsFromGroup(activeGroup); - } - else if (activeObject && this.deactivateAll) { - this.deactivateAll(); - } - - this.renderAll(true); - - var data = this.__toDataURL(format, quality, cropping); - - // restoring width, height for `renderAll` to draw - // background properly (while context is scaled) - this.width = origWidth; - this.height = origHeight; - - ctx.scale(1 / multiplier, 1 / multiplier); - this.setWidth(origWidth).setHeight(origHeight); - - if (activeGroup) { - this._restoreBordersControlsOnGroup(activeGroup); - } - else if (activeObject && this.setActiveObject) { - this.setActiveObject(activeObject); - } - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - - return data; - }, - - /** - * Exports canvas element to a dataurl image (allowing to change image size via multiplier). - * @deprecated since 1.0.13 - * @param {String} format (png|jpeg) - * @param {Number} multiplier - * @param {Number} quality (0..1) - * @return {String} - */ - toDataURLWithMultiplier: function (format, multiplier, quality) { - return this.toDataURL({ - format: format, - multiplier: multiplier, - quality: quality - }); - }, - - /** - * @private - */ - _tempRemoveBordersControlsFromGroup: function(group) { - group.origHasControls = group.hasControls; - group.origBorderColor = group.borderColor; - - group.hasControls = true; - group.borderColor = 'rgba(0,0,0,0)'; - - group.forEachObject(function(o) { - o.origBorderColor = o.borderColor; - o.borderColor = 'rgba(0,0,0,0)'; - }); - }, - - /** - * @private - */ - _restoreBordersControlsOnGroup: function(group) { - group.hideControls = group.origHideControls; - group.borderColor = group.origBorderColor; - - group.forEachObject(function(o) { - o.borderColor = o.origBorderColor; - delete o.origBorderColor; - }); - } -}); - - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Populates canvas with data from the specified dataless JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} - * @deprecated since 1.2.2 - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} - */ - loadFromDatalessJSON: function (json, callback, reviver) { - return this.loadFromJSON(json, callback, reviver); - }, - - /** - * Populates canvas with data from the specified JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toJSON} - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} - * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} - * @example loadFromJSON - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); - * @example loadFromJSON with reviver - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { - * // `o` = json object - * // `object` = fabric.Object instance - * // ... do some stuff ... - * }); - */ - loadFromJSON: function (json, callback, reviver) { - if (!json) { - return; - } - - // serialize if it wasn't already - var serialized = (typeof json === 'string') - ? JSON.parse(json) - : json; - - this.clear(); - - var _this = this; - this._enlivenObjects(serialized.objects, function () { - _this._setBgOverlay(serialized, callback); - }, reviver); - - return this; - }, - - /** - * @private - * @param {Object} serialized Object with background and overlay information - * @param {Function} callback Invoked after all background and overlay images/patterns loaded - */ - _setBgOverlay: function(serialized, callback) { - var _this = this, - loaded = { - backgroundColor: false, - overlayColor: false, - backgroundImage: false, - overlayImage: false - }; - - if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { - callback && callback(); - return; - } - - var cbIfLoaded = function () { - if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { - _this.renderAll(); - callback && callback(); - } - }; - - this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); - this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); - this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); - this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); - - cbIfLoaded(); - }, - - /** - * @private - * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) - * @param {(Object|String)} value Value to set - * @param {Object} loaded Set loaded property to true if property is set - * @param {Object} callback Callback function to invoke after property is set - */ - __setBgOverlay: function(property, value, loaded, callback) { - var _this = this; - - if (!value) { - loaded[property] = true; - return; - } - - if (property === 'backgroundImage' || property === 'overlayImage') { - fabric.Image.fromObject(value, function(img) { - _this[property] = img; - loaded[property] = true; - callback && callback(); - }); - } - else { - this['set' + fabric.util.string.capitalize(property, true)](value, function() { - loaded[property] = true; - callback && callback(); - }); - } - }, - - /** - * @private - * @param {Array} objects - * @param {Function} callback - * @param {Function} [reviver] - */ - _enlivenObjects: function (objects, callback, reviver) { - var _this = this; - - if (!objects || objects.length === 0) { - callback && callback(); - return; - } - - var renderOnAddRemove = this.renderOnAddRemove; - this.renderOnAddRemove = false; - - fabric.util.enlivenObjects(objects, function(enlivenedObjects) { - enlivenedObjects.forEach(function(obj, index) { - _this.insertAt(obj, index, true); - }); - - _this.renderOnAddRemove = renderOnAddRemove; - callback && callback(); - }, null, reviver); - }, - - /** - * @private - * @param {String} format - * @param {Function} callback - */ - _toDataURL: function (format, callback) { - this.clone(function (clone) { - callback(clone.toDataURL(format)); - }); - }, - - /** - * @private - * @param {String} format - * @param {Number} multiplier - * @param {Function} callback - */ - _toDataURLWithMultiplier: function (format, multiplier, callback) { - this.clone(function (clone) { - callback(clone.toDataURLWithMultiplier(format, multiplier)); - }); - }, - - /** - * Clones canvas instance - * @param {Object} [callback] Receives cloned instance as a first argument - * @param {Array} [properties] Array of properties to include in the cloned canvas and children - */ - clone: function (callback, properties) { - var data = JSON.stringify(this.toJSON(properties)); - this.cloneWithoutData(function(clone) { - clone.loadFromJSON(data, function() { - callback && callback(clone); - }); - }); - }, - - /** - * Clones canvas instance without cloning existing data. - * This essentially copies canvas dimensions, clipping properties, etc. - * but leaves data empty (so that you can populate it with your own) - * @param {Object} [callback] Receives cloned instance as a first argument - */ - cloneWithoutData: function(callback) { - var el = fabric.document.createElement('canvas'); - - el.width = this.getWidth(); - el.height = this.getHeight(); - - var clone = new fabric.Canvas(el); - clone.clipTo = this.clipTo; - if (this.backgroundImage) { - clone.setBackgroundImage(this.backgroundImage.src, function() { - clone.renderAll(); - callback && callback(clone); - }); - clone.backgroundImageOpacity = this.backgroundImageOpacity; - clone.backgroundImageStretch = this.backgroundImageStretch; - } - else { - callback && callback(clone); - } - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - toFixed = fabric.util.toFixed, - capitalize = fabric.util.string.capitalize, - degreesToRadians = fabric.util.degreesToRadians, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Object) { - return; - } - - /** - * Root object class from which all 2d shape classes inherit from - * @class fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} - * @see {@link fabric.Object#initialize} for constructor definition - * - * @fires added - * @fires removed - * - * @fires selected - * @fires modified - * @fires rotating - * @fires scaling - * @fires moving - * - * @fires mousedown - * @fires mouseup - */ - fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { - - /** - * Retrieves object's {@link fabric.Object#clipTo|clipping function} - * @method getClipTo - * @memberOf fabric.Object.prototype - * @return {Function} - */ - - /** - * Sets object's {@link fabric.Object#clipTo|clipping function} - * @method setClipTo - * @memberOf fabric.Object.prototype - * @param {Function} clipTo Clipping function - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method getTransformMatrix - * @memberOf fabric.Object.prototype - * @return {Array} transformMatrix - */ - - /** - * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method setTransformMatrix - * @memberOf fabric.Object.prototype - * @param {Array} transformMatrix - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#visible|visible} state - * @method getVisible - * @memberOf fabric.Object.prototype - * @return {Boolean} True if visible - */ - - /** - * Sets object's {@link fabric.Object#visible|visible} state - * @method setVisible - * @memberOf fabric.Object.prototype - * @param {Boolean} value visible value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#shadow|shadow} - * @method getShadow - * @memberOf fabric.Object.prototype - * @return {Object} Shadow instance - */ - - /** - * Retrieves object's {@link fabric.Object#stroke|stroke} - * @method getStroke - * @memberOf fabric.Object.prototype - * @return {String} stroke value - */ - - /** - * Sets object's {@link fabric.Object#stroke|stroke} - * @method setStroke - * @memberOf fabric.Object.prototype - * @param {String} value stroke value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method getStrokeWidth - * @memberOf fabric.Object.prototype - * @return {Number} strokeWidth value - */ - - /** - * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method setStrokeWidth - * @memberOf fabric.Object.prototype - * @param {Number} value strokeWidth value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originX|originX} - * @method getOriginX - * @memberOf fabric.Object.prototype - * @return {String} originX value - */ - - /** - * Sets object's {@link fabric.Object#originX|originX} - * @method setOriginX - * @memberOf fabric.Object.prototype - * @param {String} value originX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originY|originY} - * @method getOriginY - * @memberOf fabric.Object.prototype - * @return {String} originY value - */ - - /** - * Sets object's {@link fabric.Object#originY|originY} - * @method setOriginY - * @memberOf fabric.Object.prototype - * @param {String} value originY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#fill|fill} - * @method getFill - * @memberOf fabric.Object.prototype - * @return {String} Fill value - */ - - /** - * Sets object's {@link fabric.Object#fill|fill} - * @method setFill - * @memberOf fabric.Object.prototype - * @param {String} value Fill value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#opacity|opacity} - * @method getOpacity - * @memberOf fabric.Object.prototype - * @return {Number} Opacity value (0-1) - */ - - /** - * Sets object's {@link fabric.Object#opacity|opacity} - * @method setOpacity - * @memberOf fabric.Object.prototype - * @param {Number} value Opacity value (0-1) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) - * @method getAngle - * @memberOf fabric.Object.prototype - * @return {Number} - */ - - /** - * Sets object's {@link fabric.Object#angle|angle} - * @method setAngle - * @memberOf fabric.Object.prototype - * @param {Number} value Angle value (in degrees) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#top|top position} - * @method getTop - * @memberOf fabric.Object.prototype - * @return {Number} Top value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#top|top position} - * @method setTop - * @memberOf fabric.Object.prototype - * @param {Number} value Top value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#left|left position} - * @method getLeft - * @memberOf fabric.Object.prototype - * @return {Number} Left value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#left|left position} - * @method setLeft - * @memberOf fabric.Object.prototype - * @param {Number} value Left value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleX|scaleX} value - * @method getScaleX - * @memberOf fabric.Object.prototype - * @return {Number} scaleX value - */ - - /** - * Sets object's {@link fabric.Object#scaleX|scaleX} value - * @method setScaleX - * @memberOf fabric.Object.prototype - * @param {Number} value scaleX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleY|scaleY} value - * @method getScaleY - * @memberOf fabric.Object.prototype - * @return {Number} scaleY value - */ - - /** - * Sets object's {@link fabric.Object#scaleY|scaleY} value - * @method setScaleY - * @memberOf fabric.Object.prototype - * @param {Number} value scaleY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipX|flipX} value - * @method getFlipX - * @memberOf fabric.Object.prototype - * @return {Boolean} flipX value - */ - - /** - * Sets object's {@link fabric.Object#flipX|flipX} value - * @method setFlipX - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipY|flipY} value - * @method getFlipY - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value - */ - - /** - * Sets object's {@link fabric.Object#flipY|flipY} value - * @method setFlipY - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Type of an object (rect, circle, path, etc.) - * @type String - * @default - */ - type: 'object', - - /** - * Horizontal origin of transformation of an object (one of "left", "right", "center") - * @type String - * @default - */ - originX: 'left', - - /** - * Vertical origin of transformation of an object (one of "top", "bottom", "center") - * @type String - * @default - */ - originY: 'top', - - /** - * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} - * @type Number - * @default - */ - top: 0, - - /** - * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} - * @type Number - * @default - */ - left: 0, - - /** - * Object width - * @type Number - * @default - */ - width: 0, - - /** - * Object height - * @type Number - * @default - */ - height: 0, - - /** - * Object scale factor (horizontal) - * @type Number - * @default - */ - scaleX: 1, - - /** - * Object scale factor (vertical) - * @type Number - * @default - */ - scaleY: 1, - - /** - * When true, an object is rendered as flipped horizontally - * @type Boolean - * @default - */ - flipX: false, - - /** - * When true, an object is rendered as flipped vertically - * @type Boolean - * @default - */ - flipY: false, - - /** - * Opacity of an object - * @type Number - * @default - */ - opacity: 1, - - /** - * Angle of rotation of an object (in degrees) - * @type Number - * @default - */ - angle: 0, - - /** - * Size of object's controlling corners (in pixels) - * @type Number - * @default - */ - cornerSize: 12, - - /** - * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) - * @type Boolean - * @default - */ - transparentCorners: true, - - /** - * Default cursor value used when hovering over this object on canvas - * @type String - * @default - */ - hoverCursor: null, - - /** - * Padding between object and its controlling borders (in pixels) - * @type Number - * @default - */ - padding: 0, - - /** - * Color of controlling borders of an object (when it's active) - * @type String - * @default - */ - borderColor: 'rgba(102,153,255,0.75)', - - /** - * Color of controlling corners of an object (when it's active) - * @type String - * @default - */ - cornerColor: 'rgba(102,153,255,0.5)', - - /** - * When true, this object will use center point as the origin of transformation - * when being scaled via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredScaling: false, - - /** - * When true, this object will use center point as the origin of transformation - * when being rotated via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredRotation: true, - - /** - * Color of object's fill - * @type String - * @default - */ - fill: 'rgb(0,0,0)', - - /** - * Fill rule used to fill an object - * @type String - * @default - */ - fillRule: 'source-over', - - /** - * Background color of an object. Only works with text objects at the moment. - * @type String - * @default - */ - backgroundColor: '', - - /** - * When defined, an object is rendered via stroke and this property specifies its color - * @type String - * @default - */ - stroke: null, - - /** - * Width of a stroke used to render this object - * @type Number - * @default - */ - strokeWidth: 1, - - /** - * Array specifying dash pattern of an object's stroke (stroke must be defined) - * @type Array - */ - strokeDashArray: null, - - /** - * Line endings style of an object's stroke (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'butt', - - /** - * Corner style of an object's stroke (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'miter', - - /** - * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke - * @type Number - * @default - */ - strokeMiterLimit: 10, - - /** - * Shadow object representing shadow of this shape - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Opacity of object's controlling borders when object is active and moving - * @type Number - * @default - */ - borderOpacityWhenMoving: 0.4, - - /** - * Scale factor of object's controlling borders - * @type Number - * @default - */ - borderScaleFactor: 1, - - /** - * Transform matrix (similar to SVG's transform matrix) - * @type Array - */ - transformMatrix: null, - - /** - * Minimum allowed scale value of an object - * @type Number - * @default - */ - minScaleLimit: 0.01, - - /** - * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). - * But events still fire on it. - * @type Boolean - * @default - */ - selectable: true, - - /** - * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 - * @type Boolean - * @default - */ - evented: true, - - /** - * When set to `false`, an object is not rendered on canvas - * @type Boolean - * @default - */ - visible: true, - - /** - * When set to `false`, object's controls are not displayed and can not be used to manipulate object - * @type Boolean - * @default - */ - hasControls: true, - - /** - * When set to `false`, object's controlling borders are not rendered - * @type Boolean - * @default - */ - hasBorders: true, - - /** - * When set to `false`, object's controlling rotating point will not be visible or selectable - * @type Boolean - * @default - */ - hasRotatingPoint: true, - - /** - * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) - * @type Number - * @default - */ - rotatingPointOffset: 40, - - /** - * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box - * @type Boolean - * @default - */ - perPixelTargetFind: false, - - /** - * When `false`, default object's values are not included in its serialization - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Function that determines clipping of an object (context is passed as a first argument) - * Note that context origin is at the object's center point (not left/top corner) - * @type Function - */ - clipTo: null, - - /** - * When `true`, object horizontal movement is locked - * @type Boolean - * @default - */ - lockMovementX: false, - - /** - * When `true`, object vertical movement is locked - * @type Boolean - * @default - */ - lockMovementY: false, - - /** - * When `true`, object rotation is locked - * @type Boolean - * @default - */ - lockRotation: false, - - /** - * When `true`, object horizontal scaling is locked - * @type Boolean - * @default - */ - lockScalingX: false, - - /** - * When `true`, object vertical scaling is locked - * @type Boolean - * @default - */ - lockScalingY: false, - - /** - * When `true`, object non-uniform scaling is locked - * @type Boolean - * @default - */ - lockUniScaling: false, - - /** - * When `true`, object cannot be flipped by scaling into negative values - * @type Boolean - * @default - */ - - lockScalingFlip: false, - /** - * List of properties to consider when checking if state - * of an object is changed (fabric.Object#hasStateChanged) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: ( - 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + - 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + - 'angle opacity fill fillRule shadow clipTo visible backgroundColor' - ).split(' '), - - /** - * Constructor - * @param {Object} [options] Options object - */ - initialize: function(options) { - if (options) { - this.setOptions(options); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initGradient: function(options) { - if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { - this.set('fill', new fabric.Gradient(options.fill)); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initPattern: function(options) { - if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { - this.set('fill', new fabric.Pattern(options.fill)); - } - if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { - this.set('stroke', new fabric.Pattern(options.stroke)); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initClipping: function(options) { - if (!options.clipTo || typeof options.clipTo !== 'string') { - return; - } - - var functionBody = fabric.util.getFunctionBody(options.clipTo); - if (typeof functionBody !== 'undefined') { - this.clipTo = new Function('ctx', functionBody); - } - }, - - /** - * Sets object's properties from options - * @param {Object} [options] Options object - */ - setOptions: function(options) { - for (var prop in options) { - this.set(prop, options[prop]); - } - this._initGradient(options); - this._initPattern(options); - this._initClipping(options); - }, - - /** - * Transforms context when rendering an object - * @param {CanvasRenderingContext2D} ctx Context - * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node - */ - transform: function(ctx, fromLeft) { - if (this.group) { - this.group.transform(ctx, fromLeft); - } - ctx.globalAlpha = this.opacity; - - var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - ctx.scale( - this.scaleX * (this.flipX ? -1 : 1), - this.scaleY * (this.flipY ? -1 : 1) - ); - }, - - /** - * Returns an object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - object = { - type: this.type, - originX: this.originX, - originY: this.originY, - left: toFixed(this.left, NUM_FRACTION_DIGITS), - top: toFixed(this.top, NUM_FRACTION_DIGITS), - width: toFixed(this.width, NUM_FRACTION_DIGITS), - height: toFixed(this.height, NUM_FRACTION_DIGITS), - fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, - stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), - strokeDashArray: this.strokeDashArray, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), - scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), - scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), - angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), - flipX: this.flipX, - flipY: this.flipY, - opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), - shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, - visible: this.visible, - clipTo: this.clipTo && String(this.clipTo), - backgroundColor: this.backgroundColor - }; - - if (!this.includeDefaultValues) { - object = this._removeDefaultValues(object); - } - - fabric.util.populateWithProperties(this, object, propertiesToInclude); - - return object; - }, - - /** - * Returns (dataless) object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - // will be overwritten by subclasses - return this.toObject(propertiesToInclude); - }, - - /** - * @private - * @param {Object} object - */ - _removeDefaultValues: function(object) { - var prototype = fabric.util.getKlass(object.type).prototype, - stateProperties = prototype.stateProperties; - - stateProperties.forEach(function(prop) { - if (object[prop] === prototype[prop]) { - delete object[prop]; - } - }); - - return object; - }, - - /** - * Returns a string representation of an instance - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Basic getter - * @param {String} property Property name - * @return {Any} value of a property - */ - get: function(property) { - return this[property]; - }, - - /** - * @private - */ - _setObject: function(obj) { - for (var prop in obj) { - this._set(prop, obj[prop]); - } - }, - - /** - * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. - * @param {String|Object} key Property name or object (if object, iterate over the object properties) - * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) - * @return {fabric.Object} thisArg - * @chainable - */ - set: function(key, value) { - if (typeof key === 'object') { - this._setObject(key); - } - else { - if (typeof value === 'function' && key !== 'clipTo') { - this._set(key, value(this.get(key))); - } - else { - this._set(key, value); - } - } - return this; - }, - - /** - * @private - * @param {String} key - * @param {Any} value - * @return {fabric.Object} thisArg - */ - _set: function(key, value) { - var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); - - if (shouldConstrainValue) { - value = this._constrainScale(value); - } - if (key === 'scaleX' && value < 0) { - this.flipX = !this.flipX; - value *= -1; - } - else if (key === 'scaleY' && value < 0) { - this.flipY = !this.flipY; - value *= -1; - } - else if (key === 'width' || key === 'height') { - this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); - } - else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { - value = new fabric.Shadow(value); - } - - this[key] = value; - - return this; - }, - - /** - * Toggles specified property from `true` to `false` or from `false` to `true` - * @param {String} property Property to toggle - * @return {fabric.Object} thisArg - * @chainable - */ - toggle: function(property) { - var value = this.get(property); - if (typeof value === 'boolean') { - this.set(property, !value); - } - return this; - }, - - /** - * Sets sourcePath of an object - * @param {String} value Value to set sourcePath to - * @return {fabric.Object} thisArg - * @chainable - */ - setSourcePath: function(value) { - this.sourcePath = value; - return this; - }, - - /** - * Retrieves viewportTransform from Object's canvas if possible - * @method getViewportTransform - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value // TODO - */ - getViewportTransform: function() { - if (this.canvas && this.canvas.viewportTransform) { - return this.canvas.viewportTransform; - } - return [1, 0, 0, 1, 0, 0]; - }, - - /** - * Renders an object on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if width/height are zeros or object is not visible - if (this.width === 0 || this.height === 0 || !this.visible) { - return; - } - - ctx.save(); - - //setup fill rule for current object - this._setupFillRule(ctx); - - this._transform(ctx, noTransform); - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - - if (this.group && this.group.type === 'path-group') { - ctx.translate(-this.group.width/2, -this.group.height/2); - var m = this.transformMatrix; - if (m) { - ctx.transform.apply(ctx, m); - } - } - ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity; - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - this._render(ctx, noTransform); - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - this._restoreFillRule(ctx); - - ctx.restore(); - }, - - _transform: function(ctx, noTransform) { - var m = this.transformMatrix; - - if (m && !this.group) { - ctx.setTransform.apply(ctx, m); - } - if (!noTransform) { - this.transform(ctx); - } - }, - - _setStrokeStyles: function(ctx) { - if (this.stroke) { - ctx.lineWidth = this.strokeWidth; - ctx.lineCap = this.strokeLineCap; - ctx.lineJoin = this.strokeLineJoin; - ctx.miterLimit = this.strokeMiterLimit; - ctx.strokeStyle = this.stroke.toLive - ? this.stroke.toLive(ctx) - : this.stroke; - } - }, - - _setFillStyles: function(ctx) { - if (this.fill) { - ctx.fillStyle = this.fill.toLive - ? this.fill.toLive(ctx) - : this.fill; - } - }, - - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - var vpt = this.getViewportTransform(); - - ctx.save(); - if (this.active && !noTransform) { - var center; - if (this.group) { - center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.group.angle)); - } - center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group); - if (this.group) { - center.x *= this.group.scaleX; - center.y *= this.group.scaleY; - } - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setShadow: function(ctx) { - if (!this.shadow) { - return; - } - - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur; - ctx.shadowOffsetX = this.shadow.offsetX; - ctx.shadowOffsetY = this.shadow.offsetY; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _removeShadow: function(ctx) { - if (!this.shadow) { - return; - } - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderFill: function(ctx) { - if (!this.fill) { - return; - } - - ctx.save(); - if (this.fill.toLive) { - ctx.translate( - -this.width / 2 + this.fill.offsetX || 0, - -this.height / 2 + this.fill.offsetY || 0); - } - if (this.fill.gradientTransform) { - var g = this.fill.gradientTransform; - ctx.transform.apply(ctx, g); - } - if (this.fillRule === 'destination-over') { - ctx.fill('evenodd'); - } - else { - ctx.fill(); - } - ctx.restore(); - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderStroke: function(ctx) { - if (!this.stroke || this.strokeWidth === 0) { - return; - } - - ctx.save(); - if (this.strokeDashArray) { - // Spec requires the concatenation of two copies the dash list when the number of elements is odd - if (1 & this.strokeDashArray.length) { - this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); - } - - if (supportsLineDash) { - ctx.setLineDash(this.strokeDashArray); - this._stroke && this._stroke(ctx); - } - else { - this._renderDashedStroke && this._renderDashedStroke(ctx); - } - ctx.stroke(); - } - else { - if (this.stroke.gradientTransform) { - var g = this.stroke.gradientTransform; - ctx.transform.apply(ctx, g); - } - this._stroke ? this._stroke(ctx) : ctx.stroke(); - } - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * Clones an instance - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {fabric.Object} clone of an instance - */ - clone: function(callback, propertiesToInclude) { - if (this.constructor.fromObject) { - return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); - } - return new fabric.Object(this.toObject(propertiesToInclude)); - }, - - /** - * Creates an instance of fabric.Image out of an object - * @param {Function} callback callback, invoked with an instance as a first argument - * @return {fabric.Object} thisArg - */ - cloneAsImage: function(callback) { - var dataUrl = this.toDataURL(); - fabric.util.loadImage(dataUrl, function(img) { - if (callback) { - callback(new fabric.Image(img)); - } - }); - return this; - }, - - /** - * Converts an object into a data-url-like string - * @param {Object} options Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - */ - toDataURL: function(options) { - options || (options = { }); - - var el = fabric.util.createCanvasElement(), - boundingRect = this.getBoundingRect(); - - el.width = boundingRect.width; - el.height = boundingRect.height; - - fabric.util.wrapElement(el, 'div'); - var canvas = new fabric.Canvas(el); - - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (options.format === 'jpg') { - options.format = 'jpeg'; - } - - if (options.format === 'jpeg') { - canvas.backgroundColor = '#fff'; - } - - var origParams = { - active: this.get('active'), - left: this.getLeft(), - top: this.getTop() - }; - - this.set('active', false); - this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); - - var originalCanvas = this.canvas; - canvas.add(this); - var data = canvas.toDataURL(options); - - this.set(origParams).setCoords(); - this.canvas = originalCanvas; - - canvas.dispose(); - canvas = null; - - return data; - }, - - /** - * Returns true if specified type is identical to the type of an instance - * @param {String} type Type to check against - * @return {Boolean} - */ - isType: function(type) { - return this.type === type; - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 0; - }, - - /** - * Returns a JSON representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} JSON - */ - toJSON: function(propertiesToInclude) { - // delegate, not alias - return this.toObject(propertiesToInclude); - }, - - /** - * Sets gradient (fill or stroke) of an object - * Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0 - * @param {String} property Property name 'stroke' or 'fill' - * @param {Object} [options] Options object - * @param {String} [options.type] Type of gradient 'radial' or 'linear' - * @param {Number} [options.x1=0] x-coordinate of start point - * @param {Number} [options.y1=0] y-coordinate of start point - * @param {Number} [options.x2=0] x-coordinate of end point - * @param {Number} [options.y2=0] y-coordinate of end point - * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) - * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) - * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} - * @example Set linear gradient - * object.setGradient('fill', { - * type: 'linear', - * x1: -object.width / 2, - * y1: 0, - * x2: object.width / 2, - * y2: 0, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - * @example Set radial gradient - * object.setGradient('fill', { - * type: 'radial', - * x1: 0, - * y1: 0, - * x2: 0, - * y2: 0, - * r1: object.width / 2, - * r2: 10, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - */ - setGradient: function(property, options) { - options || (options = { }); - - var gradient = { colorStops: [] }; - - gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); - gradient.coords = { - x1: options.x1, - y1: options.y1, - x2: options.x2, - y2: options.y2 - }; - - if (options.r1 || options.r2) { - gradient.coords.r1 = options.r1; - gradient.coords.r2 = options.r2; - } - - for (var position in options.colorStops) { - var color = new fabric.Color(options.colorStops[position]); - gradient.colorStops.push({ - offset: position, - color: color.toRgb(), - opacity: color.getAlpha() - }); - } - - return this.set(property, fabric.Gradient.forObject(this, gradient)); - }, - - /** - * Sets pattern fill of an object - * @param {Object} options Options object - * @param {(String|HTMLImageElement)} options.source Pattern source - * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner - * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} - * @example Set pattern - * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { - * object.setPatternFill({ - * source: img, - * repeat: 'repeat' - * }); - * canvas.renderAll(); - * }); - */ - setPatternFill: function(options) { - return this.set('fill', new fabric.Pattern(options)); - }, - - /** - * Sets {@link fabric.Object#shadow|shadow} of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @param {String} [options.color=rgb(0,0,0)] Shadow color - * @param {Number} [options.blur=0] Shadow blur - * @param {Number} [options.offsetX=0] Shadow horizontal offset - * @param {Number} [options.offsetY=0] Shadow vertical offset - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} - * @example Set shadow with string notation - * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); - * canvas.renderAll(); - * @example Set shadow with object notation - * object.setShadow({ - * color: 'red', - * blur: 10, - * offsetX: 20, - * offsetY: 20 - * }); - * canvas.renderAll(); - */ - setShadow: function(options) { - return this.set('shadow', options ? new fabric.Shadow(options) : null); - }, - - /** - * Sets "color" of an instance (alias of `set('fill', …)`) - * @param {String} color Color value - * @return {fabric.Object} thisArg - * @chainable - */ - setColor: function(color) { - this.set('fill', color); - return this; - }, - - /** - * Sets "angle" of an instance - * @param {Number} angle Angle value - * @return {fabric.Object} thisArg - * @chainable - */ - setAngle: function(angle) { - var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; - - if (shouldCenterOrigin) { - this._setOriginToCenter(); - } - - this.set('angle', angle); - - if (shouldCenterOrigin) { - this._resetOrigin(); - } - - return this; - }, - - /** - * Centers object horizontally on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerH: function () { - this.canvas.centerObjectH(this); - return this; - }, - - /** - * Centers object vertically on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerV: function () { - this.canvas.centerObjectV(this); - return this; - }, - - /** - * Centers object vertically and horizontally on canvas to which is was added last - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - center: function () { - this.canvas.centerObject(this); - return this; - }, - - /** - * Removes object from canvas to which it was added last - * @return {fabric.Object} thisArg - * @chainable - */ - remove: function() { - this.canvas.remove(this); - return this; - }, - - /** - * Returns coordinates of a pointer relative to an object - * @param {Event} e Event to operate upon - * @param {Object} [pointer] Pointer to operate upon (instead of event) - * @return {Object} Coordinates of a pointer (x, y) - */ - getLocalPointer: function(e, pointer) { - pointer = pointer || this.canvas.getPointer(e); - var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); - return { - x: pointer.x - objectLeftTop.x, - y: pointer.y - objectLeftTop.y - }; - }, - - /** - * Sets canvas globalCompositeOperation for specific object - * custom composition operation for the particular object can be specifed using fillRule property - * @param {CanvasRenderingContext2D} ctx Rendering canvas context - */ - _setupFillRule: function (ctx) { - if (this.fillRule) { - this._prevFillRule = ctx.globalCompositeOperation; - ctx.globalCompositeOperation = this.fillRule; - } - }, - - /** - * Restores previously saved canvas globalCompositeOperation after obeject rendering - * @param {CanvasRenderingContext2D} ctx Rendering canvas context - */ - _restoreFillRule: function (ctx) { - if (this.fillRule && this._prevFillRule) { - ctx.globalCompositeOperation = this._prevFillRule; - } - } - }); - - fabric.util.createAccessors(fabric.Object); - - /** - * Alias for {@link fabric.Object.prototype.setAngle} - * @alias rotate -> setAngle - * @memberof fabric.Object - */ - fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; - - extend(fabric.Object.prototype, fabric.Observable); - - /** - * Defines the number of fraction digits to use when serializing object values. - * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. - * @static - * @memberof fabric.Object - * @constant - * @type Number - */ - fabric.Object.NUM_FRACTION_DIGITS = 2; - - /** - * Unique id used internally when creating SVG elements - * @static - * @memberof fabric.Object - * @type Number - */ - fabric.Object.__uid = 0; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Translates the coordinates from origin to center coordinates (based on the object's dimensions) - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToCenterPoint: function(point, originX, originY) { - var cx = point.x, - cy = point.y, - strokeWidth = this.stroke ? this.strokeWidth : 0; - - if (originX === 'left') { - cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - - if (originY === 'top') { - cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - - // Apply the reverse rotation to the point (it's already scaled properly) - return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); - }, - - /** - * Translates the coordinates from center to origin coordinates (based on the object's dimensions) - * @param {fabric.Point} center The point which corresponds to center of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToOriginPoint: function(center, originX, originY) { - var x = center.x, - y = center.y, - strokeWidth = this.stroke ? this.strokeWidth : 0; - - // Get the point coordinates - if (originX === 'left') { - x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - if (originY === 'top') { - y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - - // Apply the rotation to the point (it's already scaled properly) - return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); - }, - - /** - * Returns the real center coordinates of the object - * @return {fabric.Point} - */ - getCenterPoint: function() { - var leftTop = new fabric.Point(this.left, this.top); - return this.translateToCenterPoint(leftTop, this.originX, this.originY); - }, - - /** - * Returns the coordinates of the object based on center coordinates - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @return {fabric.Point} - */ - // getOriginPoint: function(center) { - // return this.translateToOriginPoint(center, this.originX, this.originY); - // }, - - /** - * Returns the coordinates of the object as if it has a different origin - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - getPointByOrigin: function(originX, originY) { - var center = this.getCenterPoint(); - return this.translateToOriginPoint(center, originX, originY); - }, - - /** - * Returns the point in local coordinates - * @param {fabric.Point} point The point relative to the global coordinate system - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - toLocalPoint: function(point, originX, originY) { - var center = this.getCenterPoint(), - strokeWidth = this.stroke ? this.strokeWidth : 0, - x, y; - - if (originX && originY) { - if (originX === 'left') { - x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else { - x = center.x; - } - - if (originY === 'top') { - y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else { - y = center.y; - } - } - else { - x = this.left; - y = this.top; - } - - return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)) - .subtractEquals(new fabric.Point(x, y)); - }, - - /** - * Returns the point in global coordinates - * @param {fabric.Point} The point relative to the local coordinate system - * @return {fabric.Point} - */ - // toGlobalPoint: function(point) { - // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); - // }, - - /** - * Sets the position of the object taking into consideration the object's origin - * @param {fabric.Point} pos The new position of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {void} - */ - setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY), - position = this.translateToOriginPoint(center, this.originX, this.originY); - - this.set('left', position.x); - this.set('top', position.y); - }, - - /** - * @param {String} to One of 'left', 'center', 'right' - */ - adjustPosition: function(to) { - var angle = degreesToRadians(this.angle), - hypotHalf = this.getWidth() / 2, - xHalf = Math.cos(angle) * hypotHalf, - yHalf = Math.sin(angle) * hypotHalf, - hypotFull = this.getWidth(), - xFull = Math.cos(angle) * hypotFull, - yFull = Math.sin(angle) * hypotFull; - - if (this.originX === 'center' && to === 'left' || - this.originX === 'right' && to === 'center') { - // move half left - this.left -= xHalf; - this.top -= yHalf; - } - else if (this.originX === 'left' && to === 'center' || - this.originX === 'center' && to === 'right') { - // move half right - this.left += xHalf; - this.top += yHalf; - } - else if (this.originX === 'left' && to === 'right') { - // move full right - this.left += xFull; - this.top += yFull; - } - else if (this.originX === 'right' && to === 'left') { - // move full left - this.left -= xFull; - this.top -= yFull; - } - - this.setCoords(); - this.originX = to; - }, - - /** - * Sets the origin/position of the object to it's center point - * @private - * @return {void} - */ - _setOriginToCenter: function() { - this._originalOriginX = this.originX; - this._originalOriginY = this.originY; - - var center = this.getCenterPoint(); - - this.originX = 'center'; - this.originY = 'center'; - - this.left = center.x; - this.top = center.y; - }, - - /** - * Resets the origin/position of the object to it's original origin - * @private - * @return {void} - */ - _resetOrigin: function() { - var originPoint = this.translateToOriginPoint( - this.getCenterPoint(), - this._originalOriginX, - this._originalOriginY); - - this.originX = this._originalOriginX; - this.originY = this._originalOriginY; - - this.left = originPoint.x; - this.top = originPoint.y; - - this._originalOriginX = null; - this._originalOriginY = null; - }, - - /** - * @private - */ - _getLeftTopCoords: function() { - return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center'); - } - }); - -})(); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Object containing coordinates of object's controls - * @type Object - * @default - */ - oCoords: null, - - /** - * Checks if object intersects with an area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object intersects with an area formed by 2 points - */ - intersectsWithRect: function(pointTL, pointBR) { - var oCoords = this.oCoords, - tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), - tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), - bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br = new fabric.Point(oCoords.br.x, oCoords.br.y), - intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - pointTL, - pointBR - ); - return intersection.status === 'Intersection'; - }, - - /** - * Checks if object intersects with another object - * @param {Object} other Object to test - * @return {Boolean} true if object intersects with another object - */ - intersectsWithObject: function(other) { - // extracts coords - function getCoords(oCoords) { - return { - tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), - tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), - bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br: new fabric.Point(oCoords.br.x, oCoords.br.y) - }; - } - var thisCoords = getCoords(this.oCoords), - otherCoords = getCoords(other.oCoords), - intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); - - return intersection.status === 'Intersection'; - }, - - /** - * Checks if object is fully contained within area of another object - * @param {Object} other Object to test - * @return {Boolean} true if object is fully contained within area of another object - */ - isContainedWithinObject: function(other) { - var boundingRect = other.getBoundingRect(), - point1 = new fabric.Point(boundingRect.left, boundingRect.top), - point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); - - return this.isContainedWithinRect(point1, point2); - }, - - /** - * Checks if object is fully contained within area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object is fully contained within area formed by 2 points - */ - isContainedWithinRect: function(pointTL, pointBR) { - var boundingRect = this.getBoundingRect(); - - return ( - boundingRect.left >= pointTL.x && - boundingRect.left + boundingRect.width <= pointBR.x && - boundingRect.top >= pointTL.y && - boundingRect.top + boundingRect.height <= pointBR.y - ); - }, - - /** - * Checks if point is inside the object - * @param {fabric.Point} point Point to check against - * @return {Boolean} true if point is inside the object - */ - containsPoint: function(point) { - var lines = this._getImageLines(this.oCoords), - xPoints = this._findCrossPoints(point, lines); - - // if xPoints is odd then point is inside the object - return (xPoints !== 0 && xPoints % 2 === 1); - }, - - /** - * Method that returns an object with the object edges in it, given the coordinates of the corners - * @private - * @param {Object} oCoords Coordinates of the object corners - */ - _getImageLines: function(oCoords) { - return { - topline: { - o: oCoords.tl, - d: oCoords.tr - }, - rightline: { - o: oCoords.tr, - d: oCoords.br - }, - bottomline: { - o: oCoords.br, - d: oCoords.bl - }, - leftline: { - o: oCoords.bl, - d: oCoords.tl - } - }; - }, - - /** - * Helper method to determine how many cross points are between the 4 object edges - * and the horizontal line determined by a point on canvas - * @private - * @param {fabric.Point} point Point to check - * @param {Object} oCoords Coordinates of the object being evaluated - */ - _findCrossPoints: function(point, oCoords) { - var b1, b2, a1, a2, xi, yi, - xcount = 0, - iLine; - - for (var lineKey in oCoords) { - iLine = oCoords[lineKey]; - // optimisation 1: line below point. no cross - if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { - continue; - } - // optimisation 2: line above point. no cross - if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { - continue; - } - // optimisation 3: vertical line case - if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { - xi = iLine.o.x; - yi = point.y; - } - // calculate the intersection point - else { - b1 = 0; - b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); - a1 = point.y - b1 * point.x; - a2 = iLine.o.y - b2 * iLine.o.x; - - xi = - (a1 - a2) / (b1 - b2); - yi = a1 + b1 * xi; - } - // dont count xi < point.x cases - if (xi >= point.x) { - xcount += 1; - } - // optimisation 4: specific for square images - if (xcount === 2) { - break; - } - } - return xcount; - }, - - /** - * Returns width of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} width value - */ - getBoundingRectWidth: function() { - return this.getBoundingRect().width; - }, - - /** - * Returns height of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} height value - */ - getBoundingRectHeight: function() { - return this.getBoundingRect().height; - }, - - /** - * Returns coordinates of object's bounding rectangle (left, top, width, height) - * @return {Object} Object with left, top, width, height properties - */ - getBoundingRect: function() { - this.oCoords || this.setCoords(); - - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], - minX = fabric.util.array.min(xCoords), - maxX = fabric.util.array.max(xCoords), - width = Math.abs(minX - maxX), - - yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], - minY = fabric.util.array.min(yCoords), - maxY = fabric.util.array.max(yCoords), - height = Math.abs(minY - maxY); - - return { - left: minX, - top: minY, - width: width, - height: height - }; - }, - - /** - * Returns width of an object - * @return {Number} width value - */ - getWidth: function() { - return this.width * this.scaleX; - }, - - /** - * Returns height of an object - * @return {Number} height value - */ - getHeight: function() { - return this.height * this.scaleY; - }, - - /** - * Makes sure the scale is valid and modifies it if necessary - * @private - * @param {Number} value - * @return {Number} - */ - _constrainScale: function(value) { - if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) { - return -this.minScaleLimit; - } - else { - return this.minScaleLimit; - } - } - return value; - }, - - /** - * Scales an object (equally by x and y) - * @param {Number} value Scale factor - * @return {fabric.Object} thisArg - * @chainable - */ - scale: function(value) { - value = this._constrainScale(value); - - if (value < 0) { - this.flipX = !this.flipX; - this.flipY = !this.flipY; - value *= -1; - } - - this.scaleX = value; - this.scaleY = value; - this.setCoords(); - return this; - }, - - /** - * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) - * @param {Number} value New width value - * @return {fabric.Object} thisArg - * @chainable - */ - scaleToWidth: function(value) { - // adjust to bounding rect factor so that rotated shapes would fit as well - var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); - return this.scale(value / this.width / boundingRectFactor); - }, - - /** - * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) - * @param {Number} value New height value - * @return {fabric.Object} thisArg - * @chainable - */ - scaleToHeight: function(value) { - // adjust to bounding rect factor so that rotated shapes would fit as well - var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); - return this.scale(value / this.height / boundingRectFactor); - }, - - /** - * Sets corner position coordinates based on current angle, width and height - * @return {fabric.Object} thisArg - * @chainable - */ - setCoords: function() { - var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, - theta = degreesToRadians(this.angle), - vpt = this.getViewportTransform(), - f = function (p) { - return fabric.util.transformPoint(p, vpt); - }, - w = this.width, - h = this.height, - capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', - vLine = this.type === 'line' && this.width === 1, - hLine = this.type === 'line' && this.height === 1, - strokeW = (capped && hLine) || this.type !== 'line', - strokeH = (capped && vLine) || this.type !== 'line'; - - if (vLine) { - w = strokeWidth; - } - else if (hLine) { - h = strokeWidth; - } - if (strokeW) { - w += strokeWidth; - } - if (strokeH) { - h += strokeWidth; - } - this.currentWidth = w * this.scaleX; - this.currentHeight = h * this.scaleY; - - // If width is negative, make postive. Fixes path selection issue - if (this.currentWidth < 0) { - this.currentWidth = Math.abs(this.currentWidth); - } - - var _hypotenuse = Math.sqrt( - Math.pow(this.currentWidth / 2, 2) + - Math.pow(this.currentHeight / 2, 2)), - - _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), - - // offset added for rotate and scale actions - offsetX = Math.cos(_angle + theta) * _hypotenuse, - offsetY = Math.sin(_angle + theta) * _hypotenuse, - sinTh = Math.sin(theta), - cosTh = Math.cos(theta), - coords = this.getCenterPoint(), - wh = new fabric.Point(this.currentWidth, this.currentHeight), - _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), - _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), - _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), - _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), - tl = f(_tl), - tr = f(_tr), - br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), - bl = f(_bl), - ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), - mt = f(_mt), - mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), - mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), - mtr = f(new fabric.Point(_mt.x, _mt.y)), - - // padding - padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), - padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); - - tl = tl.add(new fabric.Point(-padX, -padY)); - tr = tr.add(new fabric.Point(padY, -padX)); - br = br.add(new fabric.Point(padX, padY)); - bl = bl.add(new fabric.Point(-padY, padX)); - ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); - mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); - mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); - mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); - mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); - - // debugging - - // setTimeout(function() { - // canvas.contextTop.fillStyle = 'green'; - // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); - // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); - // canvas.contextTop.fillRect(br.x, br.y, 3, 3); - // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); - // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); - // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); - // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); - // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); - // }, 50); - - this.oCoords = { - // corners - tl: tl, tr: tr, br: br, bl: bl, - // middle - ml: ml, mt: mt, mr: mr, mb: mb, - // rotating point - mtr: mtr - }; - - // set coordinates of the draggable boxes in the corners used to scale/rotate the image - this._setCornerCoords && this._setCornerCoords(); - - return this; - } - }); -})(); - - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Moves an object to the bottom of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - sendToBack: function() { - if (this.group) { - fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); - } - else { - this.canvas.sendToBack(this); - } - return this; - }, - - /** - * Moves an object to the top of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - bringToFront: function() { - if (this.group) { - fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); - } - else { - this.canvas.bringToFront(this); - } - return this; - }, - - /** - * Moves an object down in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - sendBackwards: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); - } - else { - this.canvas.sendBackwards(this, intersecting); - } - return this; - }, - - /** - * Moves an object up in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - bringForward: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); - } - else { - this.canvas.bringForward(this, intersecting); - } - return this; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {Number} index New position of object - * @return {fabric.Object} thisArg - * @chainable - */ - moveTo: function(index) { - if (this.group) { - fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); - } - else { - this.canvas.moveTo(this, index); - } - return this; - } -}); - - -/* _TO_SVG_START_ */ -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Returns styles-string for svg-export - * @return {String} - */ - getSvgStyles: function() { - - var fill = this.fill - ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) - : 'none', - fillRule = (this.fillRule === 'destination-over' ? 'evenodd' : this.fillRule), - stroke = this.stroke - ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) - : 'none', - - strokeWidth = this.strokeWidth ? this.strokeWidth : '0', - strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', - strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', - strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', - strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', - opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', - - visibility = this.visible ? '' : ' visibility: hidden;', - filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; - - return [ - 'stroke: ', stroke, '; ', - 'stroke-width: ', strokeWidth, '; ', - 'stroke-dasharray: ', strokeDashArray, '; ', - 'stroke-linecap: ', strokeLineCap, '; ', - 'stroke-linejoin: ', strokeLineJoin, '; ', - 'stroke-miterlimit: ', strokeMiterLimit, '; ', - 'fill: ', fill, '; ', - 'fill-rule: ', fillRule, '; ', - 'opacity: ', opacity, ';', - filter, - visibility - ].join(''); - }, - - /** - * Returns transform-string for svg-export - * @return {String} - */ - getSvgTransform: function() { - if (this.group) { - return ''; - } - var toFixed = fabric.util.toFixed, - angle = this.getAngle(), - vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0], - center = fabric.util.transformPoint(this.getCenterPoint(), vpt), - - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - translatePart = this.type === 'path-group' ? '' : 'translate(' + - toFixed(center.x, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(center.y, NUM_FRACTION_DIGITS) + - ')', - - anglePart = angle !== 0 - ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') - : '', - - scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1) - ? '' : - (' scale(' + - toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + - ' ' + - toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + - ')'), - - addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0, - - flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', - - addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0, - - flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; - - return [ - translatePart, anglePart, scalePart, flipXPart, flipYPart - ].join(''); - }, - - /** - * Returns transform-string for svg-export from the transform matrix of single elements - * @return {String} - */ - getSvgTransformMatrix: function() { - return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : ''; - }, - - /** - * @private - */ - _createBaseSVGMarkup: function() { - var markup = [ ]; - - if (this.fill && this.fill.toLive) { - markup.push(this.fill.toSVG(this, false)); - } - if (this.stroke && this.stroke.toLive) { - markup.push(this.stroke.toSVG(this, false)); - } - if (this.shadow) { - markup.push(this.shadow.toSVG(this)); - } - return markup; - } -}); -/* _TO_SVG_END_ */ - - -/* - Depends on `stateProperties` -*/ -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Returns true if object state (one of its state properties) was changed - * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called - */ - hasStateChanged: function() { - return this.stateProperties.some(function(prop) { - return this.get(prop) !== this.originalState[prop]; - }, this); - }, - - /** - * Saves state of an object - * @param {Object} [options] Object with additional `stateProperties` array to include when saving state - * @return {fabric.Object} thisArg - */ - saveState: function(options) { - this.stateProperties.forEach(function(prop) { - this.originalState[prop] = this.get(prop); - }, this); - - if (options && options.stateProperties) { - options.stateProperties.forEach(function(prop) { - this.originalState[prop] = this.get(prop); - }, this); - } - - return this; - }, - - /** - * Setups state of an object - * @return {fabric.Object} thisArg - */ - setupState: function() { - this.originalState = { }; - this.saveState(); - - return this; - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Line) { - fabric.warn('fabric.Line is already defined'); - return; - } - - /** - * Line class - * @class fabric.Line - * @extends fabric.Object - * @see {@link fabric.Line#initialize} for constructor definition - */ - fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'line', - - /** - * x value or first line edge - * @type Number - * @default - */ - x1: 0, - - /** - * y value or first line edge - * @type Number - * @default - */ - y1: 0, - - /** - * x value or second line edge - * @type Number - * @default - */ - x2: 0, - - /** - * y value or second line edge - * @type Number - * @default - */ - y2: 0, - - /** - * Constructor - * @param {Array} [points] Array of points - * @param {Object} [options] Options object - * @return {fabric.Line} thisArg - */ - initialize: function(points, options) { - options = options || { }; - - if (!points) { - points = [0, 0, 0, 0]; - } - - this.callSuper('initialize', options); - - this.set('x1', points[0]); - this.set('y1', points[1]); - this.set('x2', points[2]); - this.set('y2', points[3]); - - this._setWidthHeight(options); - }, - - /** - * @private - * @param {Object} [options] Options - */ - _setWidthHeight: function(options) { - options || (options = { }); - - this.width = Math.abs(this.x2 - this.x1) || 1; - this.height = Math.abs(this.y2 - this.y1) || 1; - - this.left = 'left' in options - ? options.left - : this._getLeftToOriginX(); - - this.top = 'top' in options - ? options.top - : this._getTopToOriginY(); - }, - - /** - * @private - * @param {String} key - * @param {Any} value - */ - _set: function(key, value) { - this[key] = value; - if (typeof coordProps[key] !== 'undefined') { - this._setWidthHeight(); - } - return this; - }, - - /** - * @private - * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. - */ - _getLeftToOriginX: makeEdgeToOriginGetter( - { // property names - origin: 'originX', - axis1: 'x1', - axis2: 'x2', - dimension: 'width' - }, - { // possible values of origin - nearest: 'left', - center: 'center', - farthest: 'right' - } - ), - - /** - * @private - * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. - */ - _getTopToOriginY: makeEdgeToOriginGetter( - { // property names - origin: 'originY', - axis1: 'y1', - axis2: 'y2', - dimension: 'height' - }, - { // possible values of origin - nearest: 'top', - center: 'center', - farthest: 'bottom' - } - ), - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - - if (noTransform) { - // Line coords are distances from left-top of canvas to origin of line. - // - // To render line in a path-group, we need to translate them to - // distances from center of path-group to center of line. - var cp = this.getCenterPoint(); - ctx.translate( - cp.x, - cp.y - ); - } - - if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { - - // move from center (of virtual box) to its left/top corner - // we can't assume x1, y1 is top left and x2, y2 is bottom right - var xMult = this.x1 <= this.x2 ? -1 : 1, - yMult = this.y1 <= this.y2 ? -1 : 1; - - ctx.moveTo( - this.width === 1 ? 0 : (xMult * this.width / 2), - this.height === 1 ? 0 : (yMult * this.height / 2)); - - ctx.lineTo( - this.width === 1 ? 0 : (xMult * -1 * this.width / 2), - this.height === 1 ? 0 : (yMult * -1 * this.height / 2)); - } - - ctx.lineWidth = this.strokeWidth; - - // TODO: test this - // make sure setting "fill" changes color of a line - // (by copying fillStyle to strokeStyle, since line is stroked, not filled) - var origStrokeStyle = ctx.strokeStyle; - ctx.strokeStyle = this.stroke || ctx.fillStyle; - this.stroke && this._renderStroke(ctx); - ctx.strokeStyle = origStrokeStyle; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var - xMult = this.x1 <= this.x2 ? -1 : 1, - yMult = this.y1 <= this.y2 ? -1 : 1, - x = this.width === 1 ? 0 : xMult * this.width / 2, - y = this.height === 1 ? 0 : yMult * this.height / 2; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray); - ctx.closePath(); - }, - - /** - * Returns object representation of an instance - * @methd toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - x1: this.get('x1'), - y1: this.get('y1'), - x2: this.get('x2'), - y2: this.get('y2') - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), addTranslate = ''; - if (!this.group) { - var x = - this.width / 2 - (this.x1 > this.x2 ? this.x2 : this.x1), - y = - this.height / 2 - (this.y1 > this.y2 ? this.y2 : this.y1); - addTranslate = 'translate(' + x + ', ' + y + ') '; - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) - * @static - * @memberOf fabric.Line - * @see http://www.w3.org/TR/SVG/shapes.html#LineElement - */ - fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); - - /** - * Returns fabric.Line instance from an SVG element - * @static - * @memberOf fabric.Line - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromElement = function(element, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), - points = [ - parsedAttributes.x1 || 0, - parsedAttributes.y1 || 0, - parsedAttributes.x2 || 0, - parsedAttributes.y2 || 0 - ]; - return new fabric.Line(points, extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Line instance from an object representation - * @static - * @memberOf fabric.Line - * @param {Object} object Object to create an instance from - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromObject = function(object) { - var points = [object.x1, object.y1, object.x2, object.y2]; - return new fabric.Line(points, object); - }; - - /** - * Produces a function that calculates distance from canvas edge to Line origin. - */ - function makeEdgeToOriginGetter(propertyNames, originValues) { - var origin = propertyNames.origin, - axis1 = propertyNames.axis1, - axis2 = propertyNames.axis2, - dimension = propertyNames.dimension, - nearest = originValues.nearest, - center = originValues.center, - farthest = originValues.farthest; - - return function() { - switch (this.get(origin)) { - case nearest: - return Math.min(this.get(axis1), this.get(axis2)); - case center: - return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); - case farthest: - return Math.max(this.get(axis1), this.get(axis2)); - } - }; - - } - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - piBy2 = Math.PI * 2, - extend = fabric.util.object.extend; - - if (fabric.Circle) { - fabric.warn('fabric.Circle is already defined.'); - return; - } - - /** - * Circle class - * @class fabric.Circle - * @extends fabric.Object - * @see {@link fabric.Circle#initialize} for constructor definition - */ - fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'circle', - - /** - * Radius of this circle - * @type Number - * @default - */ - radius: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Circle} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - this.set('radius', options.radius || 0); - }, - - /** - * @private - * @param {String} key - * @param {Any} value - * @return {fabric.Circle} thisArg - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - - if (key === 'radius') { - this.setRadius(value); - } - - return this; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - radius: this.get('radius') - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = 0, y = 0; - if (this.group) { - x = this.left + this.radius; - y = this.top + this.radius; - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - ctx.arc(noTransform ? this.left + this.radius : 0, noTransform ? this.top + this.radius : 0, this.radius, 0, piBy2, false); - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns horizontal radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusX: function() { - return this.get('radius') * this.get('scaleX'); - }, - - /** - * Returns vertical radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusY: function() { - return this.get('radius') * this.get('scaleY'); - }, - - /** - * Sets radius of an object (and updates width accordingly) - * @return {Number} - */ - setRadius: function(value) { - this.radius = value; - this.set('width', value * 2).set('height', value * 2); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) - * @static - * @memberOf fabric.Circle - * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement - */ - fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); - - /** - * Returns {@link fabric.Circle} instance from an SVG element - * @static - * @memberOf fabric.Circle - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @throws {Error} If value of `r` attribute is missing or invalid - * @return {fabric.Circle} Instance of fabric.Circle - */ - fabric.Circle.fromElement = function(element, options) { - options || (options = { }); - - var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); - - if (!isValidRadius(parsedAttributes)) { - throw new Error('value of `r` attribute is required and can not be negative'); - } - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - - var obj = new fabric.Circle(extend(parsedAttributes, options)); - - obj.left -= obj.radius; - obj.top -= obj.radius; - return obj; - }; - - /** - * @private - */ - function isValidRadius(attributes) { - return (('radius' in attributes) && (attributes.radius > 0)); - } - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Circle} instance from an object representation - * @static - * @memberOf fabric.Circle - * @param {Object} object Object to create an instance from - * @return {Object} Instance of fabric.Circle - */ - fabric.Circle.fromObject = function(object) { - return new fabric.Circle(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Triangle) { - fabric.warn('fabric.Triangle is already defined'); - return; - } - - /** - * Triangle class - * @class fabric.Triangle - * @extends fabric.Object - * @return {fabric.Triangle} thisArg - * @see {@link fabric.Triangle#initialize} for constructor definition - */ - fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'triangle', - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('width', options.width || 100) - .set('height', options.height || 100); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - var widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - ctx.beginPath(); - ctx.moveTo(-widthBy2, heightBy2); - ctx.lineTo(0, -heightBy2); - ctx.lineTo(widthBy2, heightBy2); - ctx.closePath(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); - ctx.closePath(); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - widthBy2 = this.width / 2, - heightBy2 = this.height / 2, - points = [ - -widthBy2 + ' ' + heightBy2, - '0 ' + -heightBy2, - widthBy2 + ' ' + heightBy2 - ] - .join(','); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /** - * Returns fabric.Triangle instance from an object representation - * @static - * @memberOf fabric.Triangle - * @param {Object} object Object to create an instance from - * @return {Object} instance of Canvas.Triangle - */ - fabric.Triangle.fromObject = function(object) { - return new fabric.Triangle(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global){ - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - piBy2 = Math.PI * 2, - extend = fabric.util.object.extend; - - if (fabric.Ellipse) { - fabric.warn('fabric.Ellipse is already defined.'); - return; - } - - /** - * Ellipse class - * @class fabric.Ellipse - * @extends fabric.Object - * @return {fabric.Ellipse} thisArg - * @see {@link fabric.Ellipse#initialize} for constructor definition - */ - fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'ellipse', - - /** - * Horizontal radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical radius - * @type Number - * @default - */ - ry: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Ellipse} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('rx', options.rx || 0); - this.set('ry', options.ry || 0); - - this.set('width', this.get('rx') * 2); - this.set('height', this.get('ry') * 2); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - rx: this.get('rx'), - ry: this.get('ry') - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = 0, y = 0; - if (this.group) { - x = this.left + this.rx; - y = this.top + this.ry; - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - ctx.save(); - ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); - ctx.arc(noTransform ? this.left + this.rx : 0, noTransform ? (this.top + this.ry) * this.rx/this.ry : 0, this.rx, 0, piBy2, false); - ctx.restore(); - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) - * @static - * @memberOf fabric.Ellipse - * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement - */ - fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); - - /** - * Returns {@link fabric.Ellipse} instance from an SVG element - * @static - * @memberOf fabric.Ellipse - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromElement = function(element, options) { - options || (options = { }); - - var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - - var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); - - ellipse.top -= ellipse.ry; - ellipse.left -= ellipse.rx; - return ellipse; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Ellipse} instance from an object representation - * @static - * @memberOf fabric.Ellipse - * @param {Object} object Object to create an instance from - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromObject = function(object) { - return new fabric.Ellipse(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - if (fabric.Rect) { - console.warn('fabric.Rect is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push('rx', 'ry', 'x', 'y'); - - /** - * Rectangle class - * @class fabric.Rect - * @extends fabric.Object - * @return {fabric.Rect} thisArg - * @see {@link fabric.Rect#initialize} for constructor definition - */ - fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { - - /** - * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * Type of an object - * @type String - * @default - */ - type: 'rect', - - /** - * Horizontal border radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical border radius - * @type Number - * @default - */ - ry: 0, - - /** - * Used to specify dash pattern for stroke on this object - * @type Array - */ - strokeDashArray: null, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - this._initRxRy(); - - }, - - /** - * Initializes rx/ry attributes - * @private - */ - _initRxRy: function() { - if (this.rx && !this.ry) { - this.ry = this.rx; - } - else if (this.ry && !this.rx) { - this.rx = this.ry; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx, noTransform) { - - // optimize 1x1 case (used in spray brush) - if (this.width === 1 && this.height === 1) { - ctx.fillRect(0, 0, 1, 1); - return; - } - - var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, - ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, - w = this.width, - h = this.height, - x = noTransform ? this.left : -this.width / 2, - y = noTransform ? this.top : -this.height / 2, - isRounded = rx !== 0 || ry !== 0, - k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; - - ctx.beginPath(); - - ctx.moveTo(x + rx, y); - - ctx.lineTo(x + w - rx, y); - isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); - - ctx.lineTo(x + w, y + h - ry); - isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); - - ctx.lineTo(x + rx, y + h); - isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); - - ctx.lineTo(x, y + ry); - isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); - - ctx.closePath(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var x = -this.width / 2, - y = -this.height / 2, - w = this.width, - h = this.height; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); - ctx.closePath(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var object = extend(this.callSuper('toObject', propertiesToInclude), { - rx: this.get('rx') || 0, - ry: this.get('ry') || 0 - }); - if (!this.includeDefaultValues) { - this._removeDefaultValues(object); - } - return object; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; - if (!this.group) { - x = -this.width / 2; - y = -this.height / 2; - } - markup.push( - '\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) - * @static - * @memberOf fabric.Rect - * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement - */ - fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); - - /** - * Returns {@link fabric.Rect} instance from an SVG element - * @static - * @memberOf fabric.Rect - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Rect} Instance of fabric.Rect - */ - fabric.Rect.fromElement = function(element, options) { - if (!element) { - return null; - } - options = options || { }; - - var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - - return new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Rect} instance from an object representation - * @static - * @memberOf fabric.Rect - * @param {Object} object Object to create an instance from - * @return {Object} instance of fabric.Rect - */ - fabric.Rect.fromObject = function(object) { - return new fabric.Rect(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - toFixed = fabric.util.toFixed; - - if (fabric.Polyline) { - fabric.warn('fabric.Polyline is already defined'); - return; - } - - /** - * Polyline class - * @class fabric.Polyline - * @extends fabric.Object - * @see {@link fabric.Polyline#initialize} for constructor definition - */ - fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polyline', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Constructor - * @param {Array} points Array of points (where each point is an object with x and y) - * @param {Object} [options] Options object - * @param {Boolean} [skipOffset] Whether points offsetting should be skipped - * @return {fabric.Polyline} thisArg - * @example - * var poly = new fabric.Polyline([ - * { x: 10, y: 10 }, - * { x: 50, y: 30 }, - * { x: 40, y: 70 }, - * { x: 60, y: 50 }, - * { x: 100, y: 150 }, - * { x: 40, y: 100 } - * ], { - * stroke: 'red', - * left: 100, - * top: 100 - * }); - */ - initialize: function(points, options) { - options = options || { }; - this.set('points', points); - this.callSuper('initialize', options); - this._calcDimensions(); - }, - - /** - * @private - */ - _calcDimensions: function() { - return fabric.Polygon.prototype._calcDimensions.call(this); - }, - - /** - * @private - */ - _applyPointOffset: function() { - return fabric.Polygon.prototype._applyPointOffset.call(this); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var points = [], - markup = this._createBaseSVGMarkup(); - - for (var i = 0, len = this.points.length; i < len; i++) { - points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); - } - - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - var point; - ctx.beginPath(); - - if (this._applyPointOffset) { - if (!(this.group && this.group.type === 'path-group')) { - this._applyPointOffset(); - } - this._applyPointOffset = null; - } - - ctx.moveTo(this.points[0].x, this.points[0].y); - for (var i = 0, len = this.points.length; i < len; i++) { - point = this.points[i]; - ctx.lineTo(point.x, point.y); - } - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var p1, p2; - - ctx.beginPath(); - for (var i = 0, len = this.points.length; i < len; i++) { - p1 = this.points[i]; - p2 = this.points[i + 1] || p1; - fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.get('points').length; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) - * @static - * @memberOf fabric.Polyline - * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement - */ - fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); - - /** - * Returns fabric.Polyline instance from an SVG element - * @static - * @memberOf fabric.Polyline - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromElement = function(element, options) { - if (!element) { - return null; - } - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); - - if (points === null) { - return null; - } - - return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polyline instance from an object representation - * @static - * @memberOf fabric.Polyline - * @param {Object} object Object to create an instance from - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromObject = function(object) { - var points = object.points; - return new fabric.Polyline(points, object, true); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - toFixed = fabric.util.toFixed; - - if (fabric.Polygon) { - fabric.warn('fabric.Polygon is already defined'); - return; - } - - /** - * Polygon class - * @class fabric.Polygon - * @extends fabric.Object - * @see {@link fabric.Polygon#initialize} for constructor definition - */ - fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polygon', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Constructor - * @param {Array} points Array of points - * @param {Object} [options] Options object - * @return {fabric.Polygon} thisArg - */ - initialize: function(points, options) { - options = options || { }; - this.points = points; - this.callSuper('initialize', options); - this._calcDimensions(); - }, - - /** - * @private - */ - _calcDimensions: function() { - - var points = this.points, - minX = min(points, 'x'), - minY = min(points, 'y'), - maxX = max(points, 'x'), - maxY = max(points, 'y'); - - this.width = (maxX - minX) || 1; - this.height = (maxY - minY) || 1; - - this.left = minX, - this.top = minY; - }, - - /** - * @private - */ - _applyPointOffset: function() { - // change points to offset polygon into a bounding box - // executed one time - this.points.forEach(function(p) { - p.x -= (this.left + this.width / 2); - p.y -= (this.top + this.height / 2); - }, this); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - points: this.points.concat() - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var points = [], - markup = this._createBaseSVGMarkup(); - - for (var i = 0, len = this.points.length; i < len; i++) { - points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); - } - - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - var point; - ctx.beginPath(); - - if (this._applyPointOffset) { - if (!(this.group && this.group.type === 'path-group')) { - this._applyPointOffset(); - } - this._applyPointOffset = null; - } - - ctx.moveTo(this.points[0].x, this.points[0].y); - for (var i = 0, len = this.points.length; i < len; i++) { - point = this.points[i]; - ctx.lineTo(point.x, point.y); - } - this._renderFill(ctx); - if (this.stroke || this.strokeDashArray) { - ctx.closePath(); - this._renderStroke(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var p1, p2; - - ctx.beginPath(); - for (var i = 0, len = this.points.length; i < len; i++) { - p1 = this.points[i]; - p2 = this.points[i + 1] || this.points[0]; - fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); - } - ctx.closePath(); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.points.length; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) - * @static - * @memberOf fabric.Polygon - * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement - */ - fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); - - /** - * Returns {@link fabric.Polygon} instance from an SVG element - * @static - * @memberOf fabric.Polygon - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromElement = function(element, options) { - if (!element) { - return null; - } - - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); - - if (points === null) { - return null; - } - - return new fabric.Polygon(points, extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polygon instance from an object representation - * @static - * @memberOf fabric.Polygon - * @param {Object} object Object to create an instance from - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromObject = function(object) { - return new fabric.Polygon(object.points, object, true); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - min = fabric.util.array.min, - max = fabric.util.array.max, - extend = fabric.util.object.extend, - _toString = Object.prototype.toString, - drawArc = fabric.util.drawArc, - commandLengths = { - m: 2, - l: 2, - h: 1, - v: 1, - c: 6, - s: 4, - q: 4, - t: 2, - a: 7 - }, - repeatedCommands = { - m: 'l', - M: 'L' - }; - - if (fabric.Path) { - fabric.warn('fabric.Path is already defined'); - return; - } - - /** - * @private - */ - function getX(item) { - if (item[0] === 'H') { - return item[1]; - } - return item[item.length - 2]; - } - - /** - * @private - */ - function getY(item) { - if (item[0] === 'V') { - return item[1]; - } - return item[item.length - 1]; - } - - /** - * Path class - * @class fabric.Path - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} - * @see {@link fabric.Path#initialize} for constructor definition - */ - fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path', - - /** - * Array of path points - * @type Array - * @default - */ - path: null, - - /** - * Constructor - * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) - * @param {Object} [options] Options object - * @return {fabric.Path} thisArg - */ - initialize: function(path, options) { - options = options || { }; - - this.setOptions(options); - - if (!path) { - throw new Error('`path` argument is required'); - } - - var fromArray = _toString.call(path) === '[object Array]'; - - this.path = fromArray - ? path - // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) - : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); - - if (!this.path) { - return; - } - - if (!fromArray) { - this.path = this._parsePath(); - } - this._initializePath(options); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initializePath: function (options) { - var isWidthSet = 'width' in options && options.width != null, - isHeightSet = 'height' in options && options.width != null, - isLeftSet = 'left' in options, - isTopSet = 'top' in options, - origLeft = isLeftSet ? this.left : 0, - origTop = isTopSet ? this.top : 0; - - if (!isWidthSet || !isHeightSet) { - extend(this, this._parseDimensions()); - if (isWidthSet) { - this.width = options.width; - } - if (isHeightSet) { - this.height = options.height; - } - } - else { //Set center location relative to given height/width if not specified - if (!isTopSet) { - this.top = this.height / 2; - } - if (!isLeftSet) { - this.left = this.width / 2; - } - } - this.pathOffset = this.pathOffset || - // Save top-left coords as offset - this._calculatePathOffset(origLeft, origTop); - }, - - /** - * @private - * @param {Number} origLeft Original left position - * @param {Number} origTop Original top position - */ - _calculatePathOffset: function (origLeft, origTop) { - return { - x: this.left - origLeft - (this.width / 2), - y: this.top - origTop - (this.height / 2) - }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render path on - */ - _render: function(ctx, noTransform) { - var current, // current instruction - previous = null, - subpathStartX = 0, - subpathStartY = 0, - x = 0, // current x - y = 0, // current y - controlX = 0, // current control point x - controlY = 0, // current control point y - tempX, - tempY, - tempControlX, - tempControlY, - l = -((this.width / 2) + this.pathOffset.x), - t = -((this.height / 2) + this.pathOffset.y); - - if (noTransform) { - l += this.width / 2; - t += this.height / 2; - } - - for (var i = 0, len = this.path.length; i < len; ++i) { - - current = this.path[i]; - - switch (current[0]) { // first letter - - case 'l': // lineto, relative - x += current[1]; - y += current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'L': // lineto, absolute - x = current[1]; - y = current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'h': // horizontal lineto, relative - x += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'H': // horizontal lineto, absolute - x = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'v': // vertical lineto, relative - y += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'V': // verical lineto, absolute - y = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'm': // moveTo, relative - x += current[1]; - y += current[2]; - subpathStartX = x; - subpathStartY = y; - ctx.moveTo(x + l, y + t); - break; - - case 'M': // moveTo, absolute - x = current[1]; - y = current[2]; - subpathStartX = x; - subpathStartY = y; - ctx.moveTo(x + l, y + t); - break; - - case 'c': // bezierCurveTo, relative - tempX = x + current[5]; - tempY = y + current[6]; - controlX = x + current[3]; - controlY = y + current[4]; - ctx.bezierCurveTo( - x + current[1] + l, // x1 - y + current[2] + t, // y1 - controlX + l, // x2 - controlY + t, // y2 - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'C': // bezierCurveTo, absolute - x = current[5]; - y = current[6]; - controlX = current[3]; - controlY = current[4]; - ctx.bezierCurveTo( - current[1] + l, - current[2] + t, - controlX + l, - controlY + t, - x + l, - y + t - ); - break; - - case 's': // shorthand cubic bezierCurveTo, relative - - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - // calculate reflection of previous control points - controlX = controlX ? (2 * x - controlX) : x; - controlY = controlY ? (2 * y - controlY) : y; - - ctx.bezierCurveTo( - controlX + l, - controlY + t, - x + current[1] + l, - y + current[2] + t, - tempX + l, - tempY + t - ); - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = x + current[1]; - controlY = y + current[2]; - - x = tempX; - y = tempY; - break; - - case 'S': // shorthand cubic bezierCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - ctx.bezierCurveTo( - controlX + l, - controlY + t, - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = current[1]; - controlY = current[2]; - - break; - - case 'q': // quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - controlX = x + current[1]; - controlY = y + current[2]; - - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'Q': // quadraticCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - - ctx.quadraticCurveTo( - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - controlX = current[1]; - controlY = current[2]; - break; - - case 't': // shorthand quadraticCurveTo, relative - - // transform to absolute x,y - tempX = x + current[1]; - tempY = y + current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else if (previous[0] === 't') { - // calculate reflection of previous control points for t - controlX = 2 * x - tempControlX; - controlY = 2 * y - tempControlY; - } - else if (previous[0] === 'q') { - // calculate reflection of previous control points for q - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - tempControlX = controlX; - tempControlY = controlY; - - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - controlX = x + current[1]; - controlY = y + current[2]; - break; - - case 'T': - tempX = current[1]; - tempY = current[2]; - - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'a': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + x + l, - current[7] + y + t - ]); - x += current[6]; - y += current[7]; - break; - - case 'A': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + l, - current[7] + t - ]); - x = current[6]; - y = current[7]; - break; - - case 'z': - case 'Z': - x = subpathStartX; - y = subpathStartY; - ctx.closePath(); - break; - } - previous = current; - } - }, - - /** - * Renders path on a specified context - * @param {CanvasRenderingContext2D} ctx context to render path on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - if (noTransform) { - ctx.translate(-this.width/2, -this.height/2); - } - var m = this.transformMatrix; - - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - if (!noTransform) { - this.transform(ctx); - } - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - ctx.beginPath(); - ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity; - this._render(ctx, noTransform); - this._renderFill(ctx); - this._renderStroke(ctx); - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * Returns string representation of an instance - * @return {String} string representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(this.callSuper('toObject', propertiesToInclude), { - path: this.path.map(function(item) { return item.slice() }), - pathOffset: this.pathOffset - }); - if (this.sourcePath) { - o.sourcePath = this.sourcePath; - } - if (this.transformMatrix) { - o.transformMatrix = this.transformMatrix; - } - return o; - }, - - /** - * Returns dataless object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.path = this.sourcePath; - } - delete o.sourcePath; - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var chunks = [], - markup = this._createBaseSVGMarkup(); - - for (var i = 0, len = this.path.length; i < len; i++) { - chunks.push(this.path[i].join(' ')); - } - var path = chunks.join(' '); - - markup.push( - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns number representation of an instance complexity - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.path.length; - }, - - /** - * @private - */ - _parsePath: function() { - var result = [ ], - coords = [ ], - currentPath, - parsed, - re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, - match, - coordsStr; - - for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { - currentPath = this.path[i]; - - coordsStr = currentPath.slice(1).trim(); - coords.length = 0; - - while ((match = re.exec(coordsStr))) { - coords.push(match[0]); - } - - coordsParsed = [ currentPath.charAt(0) ]; - - for (var j = 0, jlen = coords.length; j < jlen; j++) { - parsed = parseFloat(coords[j]); - if (!isNaN(parsed)) { - coordsParsed.push(parsed); - } - } - - var command = coordsParsed[0], - commandLength = commandLengths[command.toLowerCase()], - repeatedCommand = repeatedCommands[command] || command; - - if (coordsParsed.length - 1 > commandLength) { - for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { - result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); - command = repeatedCommand; - } - } - else { - result.push(coordsParsed); - } - } - - return result; - }, - - /** - * @private - */ - _parseDimensions: function() { - var aX = [], - aY = [], - previous = { }; - - this.path.forEach(function(item, i) { - this._getCoordsFromCommand(item, i, aX, aY, previous); - }, this); - - var minX = min(aX), - minY = min(aY), - maxX = max(aX), - maxY = max(aY), - deltaX = maxX - minX, - deltaY = maxY - minY, - - o = { - left: this.left + (minX + deltaX / 2), - top: this.top + (minY + deltaY / 2), - width: deltaX, - height: deltaY - }; - - return o; - }, - - _getCoordsFromCommand: function(item, i, aX, aY, previous) { - var isLowerCase = false; - - if (item[0] !== 'H') { - previous.x = (i === 0) ? getX(item) : getX(this.path[i - 1]); - } - if (item[0] !== 'V') { - previous.y = (i === 0) ? getY(item) : getY(this.path[i - 1]); - } - - // lowercased letter denotes relative position; - // transform to absolute - if (item[0] === item[0].toLowerCase()) { - isLowerCase = true; - } - - var xy = this._getXY(item, isLowerCase, previous), - val; - - val = parseInt(xy.x, 10); - if (!isNaN(val)) { - aX.push(val); - } - - val = parseInt(xy.y, 10); - if (!isNaN(val)) { - aY.push(val); - } - }, - - _getXY: function(item, isLowerCase, previous) { - - // last 2 items in an array of coordinates are the actualy x/y (except H/V), collect them - // TODO (kangax): support relative h/v commands - - var x = isLowerCase - ? previous.x + getX(item) - : item[0] === 'V' - ? previous.x - : getX(item), - - y = isLowerCase - ? previous.y + getY(item) - : item[0] === 'H' - ? previous.y - : getY(item); - - return { x: x, y: y }; - } - }); - - /** - * Creates an instance of fabric.Path from an object - * @static - * @memberOf fabric.Path - * @param {Object} object - * @param {Function} callback Callback to invoke when an fabric.Path instance is created - */ - fabric.Path.fromObject = function(object, callback) { - if (typeof object.path === 'string') { - fabric.loadSVGFromURL(object.path, function (elements) { - var path = elements[0], - pathUrl = object.path; - - delete object.path; - - fabric.util.object.extend(path, object); - path.setSourcePath(pathUrl); - - callback(path); - }); - } - else { - callback(new fabric.Path(object.path, object)); - } - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) - * @static - * @memberOf fabric.Path - * @see http://www.w3.org/TR/SVG/paths.html#PathElement - */ - fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); - - /** - * Creates an instance of fabric.Path from an SVG element - * @static - * @memberOf fabric.Path - * @param {SVGElement} element to parse - * @param {Function} callback Callback to invoke when an fabric.Path instance is created - * @param {Object} [options] Options object - */ - fabric.Path.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); - callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Path - * @type Boolean - * @default - */ - fabric.Path.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - invoke = fabric.util.array.invoke, - parentToObject = fabric.Object.prototype.toObject; - - if (fabric.PathGroup) { - fabric.warn('fabric.PathGroup is already defined'); - return; - } - - /** - * Path group class - * @class fabric.PathGroup - * @extends fabric.Path - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} - * @see {@link fabric.PathGroup#initialize} for constructor definition - */ - fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path-group', - - /** - * Fill value - * @type String - * @default - */ - fill: '', - - /** - * Constructor - * @param {Array} paths - * @param {Object} [options] Options object - * @return {fabric.PathGroup} thisArg - */ - initialize: function(paths, options) { - - options = options || { }; - this.paths = paths || [ ]; - - for (var i = this.paths.length; i--; ) { - this.paths[i].group = this; - } - - this.setOptions(options); - - if (options.widthAttr) { - this.scaleX = options.widthAttr / options.width; - } - if (options.heightAttr) { - this.scaleY = options.heightAttr / options.height; - } - - this.setCoords(); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * Renders this group on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render this instance on - */ - render: function(ctx) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - - var m = this.transformMatrix; - - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - this.transform(ctx); - - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - for (var i = 0, l = this.paths.length; i < l; ++i) { - this.paths[i].render(ctx, true); - } - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * Sets certain property to a certain value - * @param {String} prop - * @param {Any} value - * @return {fabric.PathGroup} thisArg - */ - _set: function(prop, value) { - - if (prop === 'fill' && value && this.isSameColor()) { - var i = this.paths.length; - while (i--) { - this.paths[i]._set(prop, value); - } - } - - return this.callSuper('_set', prop, value); - }, - - /** - * Returns object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(parentToObject.call(this, propertiesToInclude), { - paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) - }); - if (this.sourcePath) { - o.sourcePath = this.sourcePath; - } - return o; - }, - - /** - * Returns dataless object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} dataless object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.paths = this.sourcePath; - } - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var objects = this.getObjects(), - translatePart = 'translate(' + this.left + ' ' + this.top + ')', - markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - - for (var i = 0, len = objects.length; i < len; i++) { - markup.push(objects[i].toSVG(reviver)); - } - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns a string representation of this path group - * @return {String} string representation of an object - */ - toString: function() { - return '#'; - }, - - /** - * Returns true if all paths in this group are of same color - * @return {Boolean} true if all paths are of the same color (`fill`) - */ - isSameColor: function() { - var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); - return this.getObjects().every(function(path) { - return (path.get('fill') || '').toLowerCase() === firstPathFill; - }); - }, - - /** - * Returns number representation of object's complexity - * @return {Number} complexity - */ - complexity: function() { - return this.paths.reduce(function(total, path) { - return total + ((path && path.complexity) ? path.complexity() : 0); - }, 0); - }, - - /** - * Returns all paths in this path group - * @return {Array} array of path objects included in this path group - */ - getObjects: function() { - return this.paths; - } - }); - - /** - * Creates fabric.PathGroup instance from an object representation - * @static - * @memberOf fabric.PathGroup - * @param {Object} object Object to create an instance from - * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created - */ - fabric.PathGroup.fromObject = function(object, callback) { - if (typeof object.paths === 'string') { - fabric.loadSVGFromURL(object.paths, function (elements) { - - var pathUrl = object.paths; - delete object.paths; - - var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); - - callback(pathGroup); - }); - } - else { - fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { - delete object.paths; - callback(new fabric.PathGroup(enlivenedObjects, object)); - }); - } - }; - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.PathGroup - * @type Boolean - * @default - */ - fabric.PathGroup.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global){ - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - invoke = fabric.util.array.invoke; - - if (fabric.Group) { - return; - } - - // lock-related properties, for use in fabric.Group#get - // to enable locking behavior on group - // when one of its objects has lock-related properties set - var _lockProperties = { - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - lockUniScaling: true - }; - - /** - * Group class - * @class fabric.Group - * @extends fabric.Object - * @mixes fabric.Collection - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} - * @see {@link fabric.Group#initialize} for constructor definition - */ - fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'group', - - /** - * Constructor - * @param {Object} objects Group objects - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(objects, options) { - options = options || { }; - - this._objects = objects || []; - for (var i = this._objects.length; i--; ) { - this._objects[i].group = this; - } - - this.originalState = { }; - this.callSuper('initialize'); - - this._calcBounds(); - this._updateObjectsCoords(); - - if (options) { - extend(this, options); - } - this._setOpacityIfSame(); - - this.setCoords(); - this.saveCoords(); - }, - - /** - * @private - */ - _updateObjectsCoords: function() { - this.forEachObject(this._updateObjectCoords, this); - }, - - /** - * @private - */ - _updateObjectCoords: function(object) { - var objectLeft = object.getLeft(), - objectTop = object.getTop(); - - object.set({ - originalLeft: objectLeft, - originalTop: objectTop, - left: objectLeft - this.left, - top: objectTop - this.top - }); - - object.setCoords(); - - // do not display corners of objects enclosed in a group - object.__origHasControls = object.hasControls; - object.hasControls = false; - }, - - /** - * Returns string represenation of a group - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Adds an object to a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - addWithUpdate: function(object) { - this._restoreObjectsState(); - if (object) { - this._objects.push(object); - object.group = this; - } - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - this._calcBounds(); - this._updateObjectsCoords(); - return this; - }, - - /** - * @private - */ - _setObjectActive: function(object) { - object.set('active', true); - object.group = this; - }, - - /** - * Removes an object from a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - removeWithUpdate: function(object) { - this._moveFlippedObject(object); - this._restoreObjectsState(); - - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - - this.remove(object); - this._calcBounds(); - this._updateObjectsCoords(); - - return this; - }, - - /** - * @private - */ - _onObjectAdded: function(object) { - object.group = this; - }, - - /** - * @private - */ - _onObjectRemoved: function(object) { - delete object.group; - object.set('active', false); - }, - - /** - * Properties that are delegated to group objects when reading/writing - * @param {Object} delegatedProperties - */ - delegatedProperties: { - fill: true, - opacity: true, - fontFamily: true, - fontWeight: true, - fontSize: true, - fontStyle: true, - lineHeight: true, - textDecoration: true, - textAlign: true, - backgroundColor: true - }, - - /** - * @private - */ - _set: function(key, value) { - if (key in this.delegatedProperties) { - var i = this._objects.length; - this[key] = value; - while (i--) { - this._objects[i].set(key, value); - } - } - else { - this[key] = value; - } - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - objects: invoke(this._objects, 'toObject', propertiesToInclude) - }); - }, - - /** - * Renders instance on a given context - * @param {CanvasRenderingContext2D} ctx context to render instance on - */ - render: function(ctx) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - this.clipTo && fabric.util.clipContext(this, ctx); - - // the array is now sorted in order of highest first, so start from end - for (var i = 0, len = this._objects.length; i < len; i++) { - this._renderObject(this._objects[i], ctx); - } - - this.clipTo && ctx.restore(); - - ctx.restore(); - }, - - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - this.callSuper('_renderControls', ctx, noTransform); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i]._renderControls(ctx); - } - }, - - /** - * @private - */ - _renderObject: function(object, ctx) { - var originalHasRotatingPoint = object.hasRotatingPoint; - - // do not render if object is not visible - if (!object.visible) { - return; - } - - object.hasRotatingPoint = false; - - object.render(ctx); - - object.hasRotatingPoint = originalHasRotatingPoint; - }, - - /** - * Retores original state of each of group objects (original state is that which was before group was created). - * @private - * @return {fabric.Group} thisArg - * @chainable - */ - _restoreObjectsState: function() { - this._objects.forEach(this._restoreObjectState, this); - return this; - }, - - /** - * Moves a flipped object to the position where it's displayed - * @private - * @param {fabric.Object} object - * @return {fabric.Group} thisArg - */ - _moveFlippedObject: function(object) { - var oldOriginX = object.get('originX'), - oldOriginY = object.get('originY'), - center = object.getCenterPoint(); - - object.set({ - originX: 'center', - originY: 'center', - left: center.x, - top: center.y - }); - - this._toggleFlipping(object); - - var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); - - object.set({ - originX: oldOriginX, - originY: oldOriginY, - left: newOrigin.x, - top: newOrigin.y - }); - - return this; - }, - - /** - * @private - */ - _toggleFlipping: function(object) { - if (this.flipX) { - object.toggle('flipX'); - object.set('left', -object.get('left')); - object.setAngle(-object.getAngle()); - } - if (this.flipY) { - object.toggle('flipY'); - object.set('top', -object.get('top')); - object.setAngle(-object.getAngle()); - } - }, - - /** - * Restores original state of a specified object in group - * @private - * @param {fabric.Object} object - * @return {fabric.Group} thisArg - */ - _restoreObjectState: function(object) { - this._setObjectPosition(object); - - object.setCoords(); - object.hasControls = object.__origHasControls; - delete object.__origHasControls; - object.set('active', false); - object.setCoords(); - delete object.group; - - return this; - }, - - /** - * @private - */ - _setObjectPosition: function(object) { - var groupLeft = this.getLeft(), - groupTop = this.getTop(), - rotated = this._getRotatedLeftTop(object); - - object.set({ - angle: object.getAngle() + this.getAngle(), - left: groupLeft + rotated.left, - top: groupTop + rotated.top, - scaleX: object.get('scaleX') * this.get('scaleX'), - scaleY: object.get('scaleY') * this.get('scaleY') - }); - }, - - /** - * @private - */ - _getRotatedLeftTop: function(object) { - var groupAngle = this.getAngle() * (Math.PI / 180); - return { - left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') + - Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')), - - top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') + - Math.sin(groupAngle) * object.getLeft() * this.get('scaleX')) - }; - }, - - /** - * Destroys a group (restoring state of its objects) - * @return {fabric.Group} thisArg - * @chainable - */ - destroy: function() { - this._objects.forEach(this._moveFlippedObject, this); - return this._restoreObjectsState(); - }, - - /** - * Saves coordinates of this instance (to be used together with `hasMoved`) - * @saveCoords - * @return {fabric.Group} thisArg - * @chainable - */ - saveCoords: function() { - this._originalLeft = this.get('left'); - this._originalTop = this.get('top'); - return this; - }, - - /** - * Checks whether this group was moved (since `saveCoords` was called last) - * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) - */ - hasMoved: function() { - return this._originalLeft !== this.get('left') || - this._originalTop !== this.get('top'); - }, - - /** - * Sets coordinates of all group objects - * @return {fabric.Group} thisArg - * @chainable - */ - setObjectsCoords: function() { - this.forEachObject(function(object) { - object.setCoords(); - }); - return this; - }, - - /** - * @private - */ - _setOpacityIfSame: function() { - var objects = this.getObjects(), - firstValue = objects[0] ? objects[0].get('opacity') : 1, - isSameOpacity = objects.every(function(o) { - return o.get('opacity') === firstValue; - }); - - if (isSameOpacity) { - this.opacity = firstValue; - } - }, - - /** - * @private - */ - _calcBounds: function(onlyWidthHeight) { - var aX = [], - aY = [], - o; - - for (var i = 0, len = this._objects.length; i < len; ++i) { - o = this._objects[i]; - o.setCoords(); - for (var prop in o.oCoords) { - aX.push(o.oCoords[prop].x); - aY.push(o.oCoords[prop].y); - } - } - - this.set(this._getBounds(aX, aY, onlyWidthHeight)); - }, - - /** - * @private - */ - _getBounds: function(aX, aY, onlyWidthHeight) { - var ivt = fabric.util.invertTransform(this.getViewportTransform()), - minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), - maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), - obj = { - width: (maxXY.x - minXY.x) || 0, - height: (maxXY.y - minXY.y) || 0 - }; - - if (!onlyWidthHeight) { - obj.left = (minXY.x + maxXY.x) / 2 || 0; - obj.top = (minXY.y + maxXY.y) / 2 || 0; - } - return obj; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - - for (var i = 0, len = this._objects.length; i < len; i++) { - markup.push(this._objects[i].toSVG(reviver)); - } - - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns requested property - * @param {String} prop Property to get - * @return {Any} - */ - get: function(prop) { - if (prop in _lockProperties) { - if (this[prop]) { - return this[prop]; - } - else { - for (var i = 0, len = this._objects.length; i < len; i++) { - if (this._objects[i][prop]) { - return true; - } - } - return false; - } - } - else { - if (prop in this.delegatedProperties) { - return this._objects[0] && this._objects[0].get(prop); - } - return this[prop]; - } - } - }); - - /** - * Returns {@link fabric.Group} instance from an object representation - * @static - * @memberOf fabric.Group - * @param {Object} object Object to create a group from - * @param {Function} [callback] Callback to invoke when an group instance is created - * @return {fabric.Group} An instance of fabric.Group - */ - fabric.Group.fromObject = function(object, callback) { - fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { - delete object.objects; - callback && callback(new fabric.Group(enlivenedObjects, object)); - }); - }; - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Group - * @type Boolean - * @default - */ - fabric.Group.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var extend = fabric.util.object.extend; - - if (!global.fabric) { - global.fabric = { }; - } - - if (global.fabric.Image) { - fabric.warn('fabric.Image is already defined.'); - return; - } - - /** - * Image class - * @class fabric.Image - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} - * @see {@link fabric.Image#initialize} for constructor definition - */ - fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'image', - - /** - * crossOrigin value (one of "", "anonymous", "allow-credentials") - * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes - * @type String - * @default - */ - crossOrigin: '', - - /** - * Constructor - * @param {HTMLImageElement | String} element Image element - * @param {Object} [options] Options object - * @return {fabric.Image} thisArg - */ - initialize: function(element, options) { - options || (options = { }); - - this.filters = [ ]; - - this.callSuper('initialize', options); - - this._initElement(element, options); - this._initConfig(options); - - if (options.filters) { - this.filters = options.filters; - this.applyFilters(); - } - }, - - /** - * Returns image element which this instance if based on - * @return {HTMLImageElement} Image element - */ - getElement: function() { - return this._element; - }, - - /** - * Sets image element for this instance to a specified one. - * If filters defined they are applied to new image. - * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. - * @param {HTMLImageElement} element - * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated - * @return {fabric.Image} thisArg - * @chainable - */ - setElement: function(element, callback) { - this._element = element; - this._originalElement = element; - this._initConfig(); - - if (this.filters.length !== 0) { - this.applyFilters(callback); - } - - return this; - }, - - /** - * Sets crossOrigin value (on an instance and corresponding image element) - * @return {fabric.Image} thisArg - * @chainable - */ - setCrossOrigin: function(value) { - this.crossOrigin = value; - this._element.crossOrigin = value; - - return this; - }, - - /** - * Returns original size of an image - * @return {Object} Object with "width" and "height" properties - */ - getOriginalSize: function() { - var element = this.getElement(); - return { - width: element.width, - height: element.height - }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _stroke: function(ctx) { - ctx.save(); - this._setStrokeStyles(ctx); - ctx.beginPath(); - ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); - ctx.closePath(); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var x = -this.width / 2, - y = -this.height / 2, - w = this.width, - h = this.height; - - ctx.save(); - this._setStrokeStyles(ctx); - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); - ctx.closePath(); - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - src: this._originalElement.src || this._originalElement._src, - filters: this.filters.map(function(filterObj) { - return filterObj && filterObj.toObject(); - }), - crossOrigin: this.crossOrigin - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = [], x = -this.width / 2, y = -this.height / 2; - if (this.group) { - x = this.left; - y = this.top; - } - markup.push( - '\n', - '\n' - ); - - if (this.stroke || this.strokeDashArray) { - var origFill = this.fill; - this.fill = null; - markup.push( - '\n' - ); - this.fill = origFill; - } - - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns source of an image - * @return {String} Source of an image - */ - getSrc: function() { - if (this.getElement()) { - return this.getElement().src || this.getElement()._src; - } - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Returns a clone of an instance - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - */ - clone: function(callback, propertiesToInclude) { - this.constructor.fromObject(this.toObject(propertiesToInclude), callback); - }, - - /** - * Applies filters assigned to this image (from "filters" array) - * @mthod applyFilters - * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated - * @return {fabric.Image} thisArg - * @chainable - */ - applyFilters: function(callback) { - - if (!this._originalElement) { - return; - } - - if (this.filters.length === 0) { - this._element = this._originalElement; - callback && callback(); - return; - } - - var imgEl = this._originalElement, - canvasEl = fabric.util.createCanvasElement(), - replacement = fabric.util.createImage(), - _this = this; - - canvasEl.width = imgEl.width; - canvasEl.height = imgEl.height; - - canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); - - this.filters.forEach(function(filter) { - filter && filter.applyTo(canvasEl); - }); - - /** @ignore */ - - replacement.width = imgEl.width; - replacement.height = imgEl.height; - - if (fabric.isLikelyNode) { - replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); - - // onload doesn't fire in some node versions, so we invoke callback manually - _this._element = replacement; - callback && callback(); - } - else { - replacement.onload = function() { - _this._element = replacement; - callback && callback(); - replacement.onload = canvasEl = imgEl = null; - }; - replacement.src = canvasEl.toDataURL('image/png'); - } - - return this; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx, noTransform) { - this._element && - ctx.drawImage( - this._element, - noTransform ? this.left : -this.width/2, - noTransform ? this.top : -this.height/2, - this.width, - this.height - ); - this._renderStroke(ctx); - }, - - /** - * @private - */ - _resetWidthHeight: function() { - var element = this.getElement(); - - this.set('width', element.width); - this.set('height', element.height); - }, - - /** - * The Image class's initialization method. This method is automatically - * called by the constructor. - * @private - * @param {HTMLImageElement|String} element The element representing the image - */ - _initElement: function(element) { - this.setElement(fabric.util.getById(element)); - fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initConfig: function(options) { - options || (options = { }); - this.setOptions(options); - this._setWidthHeight(options); - if (this._element && this.crossOrigin) { - this._element.crossOrigin = this.crossOrigin; - } - }, - - /** - * @private - * @param {Object} object Object with filters property - * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created - */ - _initFilters: function(object, callback) { - if (object.filters && object.filters.length) { - fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { - callback && callback(enlivenedObjects); - }, 'fabric.Image.filters'); - } - else { - callback && callback(); - } - }, - - /** - * @private - * @param {Object} [options] Object with width/height properties - */ - _setWidthHeight: function(options) { - this.width = 'width' in options - ? options.width - : (this.getElement() - ? this.getElement().width || 0 - : 0); - - this.height = 'height' in options - ? options.height - : (this.getElement() - ? this.getElement().height || 0 - : 0); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /** - * Default CSS class name for canvas - * @static - * @type String - * @default - */ - fabric.Image.CSS_CANVAS = 'canvas-img'; - - /** - * Alias for getSrc - * @static - */ - fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; - - /** - * Creates an instance of fabric.Image from its object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an image instance is created - */ - fabric.Image.fromObject = function(object, callback) { - fabric.util.loadImage(object.src, function(img) { - fabric.Image.prototype._initFilters.call(object, object, function(filters) { - object.filters = filters || [ ]; - var instance = new fabric.Image(img, object); - callback && callback(instance); - }); - }, null, object.crossOrigin); - }; - - /** - * Creates an instance of fabric.Image from an URL string - * @static - * @param {String} url URL to create an image from - * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) - * @param {Object} [imgOptions] Options object - */ - fabric.Image.fromURL = function(url, callback, imgOptions) { - fabric.util.loadImage(url, function(img) { - callback(new fabric.Image(img, imgOptions)); - }, null, imgOptions && imgOptions.crossOrigin); - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) - * @static - * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} - */ - fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height xlink:href'.split(' ')); - - /** - * Returns {@link fabric.Image} instance from an SVG element - * @static - * @param {SVGElement} element Element to parse - * @param {Function} callback Callback to execute when fabric.Image object is created - * @param {Object} [options] Options object - * @return {fabric.Image} Instance of fabric.Image - */ - fabric.Image.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); - - fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, - extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.async = true; - - /** - * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 - * @static - * @type Number - * @default - */ - fabric.Image.pngCompression = 1; - -})(typeof exports !== 'undefined' ? exports : this); - - -/** - * @namespace fabric.Image.filters - * @memberOf fabric.Image - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - */ -fabric.Image.filters = fabric.Image.filters || { }; - -/** - * Root filter class from which all filter classes inherit from - * @class fabric.Image.filters.BaseFilter - * @memberOf fabric.Image.filters - */ -fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'BaseFilter', - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { type: this.type }; - }, - - /** - * Returns a JSON representation of an instance - * @return {Object} JSON - */ - toJSON: function() { - // delegate, not alias - return this.toObject(); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Brightness filter class - * @class fabric.Image.filters.Brightness - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Brightness({ - * brightness: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Brightness', - - /** - * Constructor - * @memberOf fabric.Image.filters.Brightness.prototype - * @param {Object} [options] Options object - * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) - */ - initialize: function(options) { - options = options || { }; - this.brightness = options.brightness || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - brightness = this.brightness; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i] += brightness; - data[i + 1] += brightness; - data[i + 2] += brightness; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - brightness: this.brightness - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness - */ - fabric.Image.filters.Brightness.fromObject = function(object) { - return new fabric.Image.filters.Brightness(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Adapted from html5rocks article - * @class fabric.Image.filters.Convolute - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example Sharpen filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 0, -1, 0, - * -1, 5, -1, - * 0, -1, 0 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Blur filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter with opaqueness - * var filter = new fabric.Image.filters.Convolute({ - * opaque: true, - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Convolute', - - /** - * Constructor - * @memberOf fabric.Image.filters.Convolute.prototype - * @param {Object} [options] Options object - * @param {Boolean} [options.opaque=false] Opaque value (true/false) - * @param {Array} [options.matrix] Filter matrix - */ - initialize: function(options) { - options = options || { }; - - this.opaque = options.opaque; - this.matrix = options.matrix || [ - 0, 0, 0, - 0, 1, 0, - 0, 0, 0 - ]; - - var canvasEl = fabric.util.createCanvasElement(); - this.tmpCtx = canvasEl.getContext('2d'); - }, - - /** - * @private - */ - _createImageData: function(w, h) { - return this.tmpCtx.createImageData(w, h); - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - - var weights = this.matrix, - context = canvasEl.getContext('2d'), - pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - - side = Math.round(Math.sqrt(weights.length)), - halfSide = Math.floor(side/2), - src = pixels.data, - sw = pixels.width, - sh = pixels.height, - - // pad output by the convolution matrix - w = sw, - h = sh, - output = this._createImageData(w, h), - - dst = output.data, - - // go through the destination image pixels - alphaFac = this.opaque ? 1 : 0; - - for (var y = 0; y < h; y++) { - for (var x = 0; x < w; x++) { - var sy = y, - sx = x, - dstOff = (y * w + x) * 4, - // calculate the weighed sum of the source image pixels that - // fall under the convolution matrix - r = 0, g = 0, b = 0, a = 0; - - for (var cy = 0; cy < side; cy++) { - for (var cx = 0; cx < side; cx++) { - - var scy = sy + cy - halfSide, - scx = sx + cx - halfSide; - - /* jshint maxdepth:5 */ - if (scy < 0 || scy > sh || scx < 0 || scx > sw) { - continue; - } - - var srcOff = (scy * sw + scx) * 4, - wt = weights[cy * side + cx]; - - r += src[srcOff] * wt; - g += src[srcOff + 1] * wt; - b += src[srcOff + 2] * wt; - a += src[srcOff + 3] * wt; - } - } - dst[dstOff] = r; - dst[dstOff + 1] = g; - dst[dstOff + 2] = b; - dst[dstOff + 3] = a + alphaFac * (255 - a); - } - } - - context.putImageData(output, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - opaque: this.opaque, - matrix: this.matrix - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute - */ - fabric.Image.filters.Convolute.fromObject = function(object) { - return new fabric.Image.filters.Convolute(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * GradientTransparency filter class - * @class fabric.Image.filters.GradientTransparency - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.GradientTransparency({ - * threshold: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'GradientTransparency', - - /** - * Constructor - * @memberOf fabric.Image.filters.GradientTransparency.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=100] Threshold value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 100; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - total = data.length; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i + 3] = threshold + 255 * (total - i) / total; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency - */ - fabric.Image.filters.GradientTransparency.fromObject = function(object) { - return new fabric.Image.filters.GradientTransparency(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Grayscale image filter class - * @class fabric.Image.filters.Grayscale - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Grayscale(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Grayscale', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Grayscale.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - len = imageData.width * imageData.height * 4, - index = 0, - average; - - while (index < len) { - average = (data[index] + data[index + 1] + data[index + 2]) / 3; - data[index] = average; - data[index + 1] = average; - data[index + 2] = average; - index += 4; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale - */ - fabric.Image.filters.Grayscale.fromObject = function() { - return new fabric.Image.filters.Grayscale(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Invert filter class - * @class fabric.Image.filters.Invert - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Invert(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Invert', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Invert.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i; - - for (i = 0; i < iLen; i+=4) { - data[i] = 255 - data[i]; - data[i + 1] = 255 - data[i + 1]; - data[i + 2] = 255 - data[i + 2]; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert - */ - fabric.Image.filters.Invert.fromObject = function() { - return new fabric.Image.filters.Invert(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Mask filter class - * See http://resources.aleph-1.com/mask/ - * @class fabric.Image.filters.Mask - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition - */ - fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Mask', - - /** - * Constructor - * @memberOf fabric.Image.filters.Mask.prototype - * @param {Object} [options] Options object - * @param {fabric.Image} [options.mask] Mask image object - * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) - */ - initialize: function(options) { - options = options || { }; - - this.mask = options.mask; - this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - if (!this.mask) { - return; - } - - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - maskEl = this.mask.getElement(), - maskCanvasEl = fabric.util.createCanvasElement(), - channel = this.channel, - i, - iLen = imageData.width * imageData.height * 4; - - maskCanvasEl.width = maskEl.width; - maskCanvasEl.height = maskEl.height; - - maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); - - var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), - maskData = maskImageData.data; - - for (i = 0; i < iLen; i += 4) { - data[i + 3] = maskData[i + channel]; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - mask: this.mask.toObject(), - channel: this.channel - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when a mask filter instance is created - */ - fabric.Image.filters.Mask.fromObject = function(object, callback) { - fabric.util.loadImage(object.mask.src, function(img) { - object.mask = new fabric.Image(img, object.mask); - callback && callback(new fabric.Image.filters.Mask(object)); - }); - }; - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.filters.Mask.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Noise filter class - * @class fabric.Image.filters.Noise - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Noise({ - * noise: 700 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Noise', - - /** - * Constructor - * @memberOf fabric.Image.filters.Noise.prototype - * @param {Object} [options] Options object - * @param {Number} [options.noise=0] Noise value - */ - initialize: function(options) { - options = options || { }; - this.noise = options.noise || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - noise = this.noise, rand; - - for (var i = 0, len = data.length; i < len; i += 4) { - - rand = (0.5 - Math.random()) * noise; - - data[i] += rand; - data[i + 1] += rand; - data[i + 2] += rand; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - noise: this.noise - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise - */ - fabric.Image.filters.Noise.fromObject = function(object) { - return new fabric.Image.filters.Noise(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Pixelate filter class - * @class fabric.Image.filters.Pixelate - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Pixelate({ - * blocksize: 8 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Pixelate', - - /** - * Constructor - * @memberOf fabric.Image.filters.Pixelate.prototype - * @param {Object} [options] Options object - * @param {Number} [options.blocksize=4] Blocksize for pixelate - */ - initialize: function(options) { - options = options || { }; - this.blocksize = options.blocksize || 4; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = imageData.height, - jLen = imageData.width, - index, i, j, r, g, b, a; - - for (i = 0; i < iLen; i += this.blocksize) { - for (j = 0; j < jLen; j += this.blocksize) { - - index = (i * 4) * jLen + (j * 4); - - r = data[index]; - g = data[index + 1]; - b = data[index + 2]; - a = data[index + 3]; - - /* - blocksize: 4 - - [1,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [1,1,1,1,1] - */ - - for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { - for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { - index = (_i * 4) * jLen + (_j * 4); - data[index] = r; - data[index + 1] = g; - data[index + 2] = b; - data[index + 3] = a; - } - } - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - blocksize: this.blocksize - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate - */ - fabric.Image.filters.Pixelate.fromObject = function(object) { - return new fabric.Image.filters.Pixelate(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Remove white filter class - * @class fabric.Image.filters.RemoveWhite - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.RemoveWhite({ - * threshold: 40, - * distance: 140 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'RemoveWhite', - - /** - * Constructor - * @memberOf fabric.Image.filters.RemoveWhite.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=30] Threshold value - * @param {Number} [options.distance=20] Distance value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 30; - this.distance = options.distance || 20; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - distance = this.distance, - limit = 255 - threshold, - abs = Math.abs, - r, g, b; - - for (var i = 0, len = data.length; i < len; i += 4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - if (r > limit && - g > limit && - b > limit && - abs(r - g) < distance && - abs(r - b) < distance && - abs(g - b) < distance - ) { - data[i + 3] = 1; - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold, - distance: this.distance - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite - */ - fabric.Image.filters.RemoveWhite.fromObject = function(object) { - return new fabric.Image.filters.RemoveWhite(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Sepia filter class - * @class fabric.Image.filters.Sepia - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, avg; - - for (i = 0; i < iLen; i+=4) { - avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; - data[i] = avg + 100; - data[i + 1] = avg + 50; - data[i + 2] = avg + 255; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia - */ - fabric.Image.filters.Sepia.fromObject = function() { - return new fabric.Image.filters.Sepia(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Sepia2 filter class - * @class fabric.Image.filters.Sepia2 - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia2(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia2', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, r, g, b; - - for (i = 0; i < iLen; i+=4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; - data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; - data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 - */ - fabric.Image.filters.Sepia2.fromObject = function() { - return new fabric.Image.filters.Sepia2(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Tint filter class - * Adapted from https://github.com/mezzoblue/PaintbrushJS - * @class fabric.Image.filters.Tint - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example Tint filter with hex color and opacity - * var filter = new fabric.Image.filters.Tint({ - * color: '#3513B0', - * opacity: 0.5 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Tint filter with rgba color - * var filter = new fabric.Image.filters.Tint({ - * color: 'rgba(53, 21, 176, 0.5)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Tint', - - /** - * Constructor - * @memberOf fabric.Image.filters.Tint.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to tint the image with - * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - this.opacity = typeof options.opacity !== 'undefined' - ? options.opacity - : new fabric.Color(this.color).getAlpha(); - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - tintR, tintG, tintB, - r, g, b, alpha1, - source; - - source = new fabric.Color(this.color).getSource(); - - tintR = source[0] * this.opacity; - tintG = source[1] * this.opacity; - tintB = source[2] * this.opacity; - - alpha1 = 1 - this.opacity; - - for (i = 0; i < iLen; i+=4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - // alpha compositing - data[i] = tintR + r * alpha1; - data[i + 1] = tintG + g * alpha1; - data[i + 2] = tintB + b * alpha1; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color, - opacity: this.opacity - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint - */ - fabric.Image.filters.Tint.fromObject = function(object) { - return new fabric.Image.filters.Tint(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Multiply filter class - * Adapted from http://www.laurenscorijn.com/articles/colormath-basics - * @class fabric.Image.filters.Multiply - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @example Multiply filter with hex color - * var filter = new fabric.Image.filters.Multiply({ - * color: '#F0F' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Multiply filter with rgb color - * var filter = new fabric.Image.filters.Multiply({ - * color: 'rgb(53, 21, 176)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Multiply', - - /** - * Constructor - * @memberOf fabric.Image.filters.Multiply.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to multiply the image pixels with - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - source; - - source = new fabric.Color(this.color).getSource(); - - for (i = 0; i < iLen; i+=4) { - data[i] *= source[0] / 255; - data[i + 1] *= source[1] / 255; - data[i + 2] *= source[2] / 255; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply - */ - fabric.Image.filters.Multiply.fromObject = function(object) { - return new fabric.Image.filters.Multiply(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global){ - 'use strict'; - - var fabric = global.fabric; - - /** - * Color Blend filter class - * @class fabric.Image.filter.Blend - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @example - * var filter = new fabric.Image.filters.Blend({ - * color: '#000', - * mode: 'multiply' - * }); - * - * var filter = new fabric.Image.filters.Blend({ - * image: fabricImageObject, - * mode: 'multiply', - * alpha: 0.5 - * }); - - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Blend = fabric.util.createClass({ - type: 'Blend', - - initialize: function(options){ - options = options || {}; - this.color = options.color || '#000'; - this.image = options.image || false; - this.mode = options.mode || 'multiply'; - this.alpha = options.alpha || 1; - }, - - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - tr, tg, tb, - r, g, b, - source, - isImage = false; - - if (this.image) { - // Blend images - isImage = true; - - var _el = fabric.util.createCanvasElement(); - _el.width = this.image.width; - _el.height = this.image.height; - - var tmpCanvas = new fabric.StaticCanvas(_el); - tmpCanvas.add(this.image); - var context2 = tmpCanvas.getContext('2d'); - source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; - } - else { - // Blend color - source = new fabric.Color(this.color).getSource(); - - tr = source[0] * this.alpha; - tg = source[1] * this.alpha; - tb = source[2] * this.alpha; - } - - for (var i = 0, len = data.length; i < len; i += 4) { - - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - if (isImage) { - tr = source[i] * this.alpha; - tg = source[i + 1] * this.alpha; - tb = source[i + 2] * this.alpha; - } - - switch (this.mode) { - case 'multiply': - data[i] = r * tr / 255; - data[i + 1] = g * tg / 255; - data[i + 2] = b * tb / 255; - break; - case 'screen': - data[i] = 1 - (1 - r) * (1 - tr); - data[i + 1] = 1 - (1 - g) * (1 - tg); - data[i + 2] = 1 - (1 - b) * (1 - tb); - break; - case 'add': - data[i] = Math.min(255, r + tr); - data[i + 1] = Math.min(255, g + tg); - data[i + 2] = Math.min(255, b + tb); - break; - case 'diff': - case 'difference': - data[i] = Math.abs(r - tr); - data[i + 1] = Math.abs(g - tg); - data[i + 2] = Math.abs(b - tb); - break; - case 'subtract': - var _r = r - tr, - _g = g - tg, - _b = b - tb; - - data[i] = (_r < 0) ? 0 : _r; - data[i + 1] = (_g < 0) ? 0 : _g; - data[i + 2] = (_b < 0) ? 0 : _b; - break; - case 'darken': - data[i] = Math.min(r, tr); - data[i + 1] = Math.min(g, tg); - data[i + 2] = Math.min(b, tb); - break; - case 'lighten': - data[i] = Math.max(r, tr); - data[i + 1] = Math.max(g, tg); - data[i + 2] = Math.max(b, tb); - break; - } - } - - context.putImageData(imageData, 0, 0); - } - }); - - fabric.Image.filters.Blend.fromObject = function(object) { - return new fabric.Image.filters.Blend(object); - }; -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Text) { - fabric.warn('fabric.Text is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push( - 'fontFamily', - 'fontWeight', - 'fontSize', - 'text', - 'textDecoration', - 'textAlign', - 'fontStyle', - 'lineHeight', - 'textBackgroundColor', - 'useNative', - 'path' - ); - - /** - * Text class - * @class fabric.Text - * @extends fabric.Object - * @return {fabric.Text} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} - * @see {@link fabric.Text#initialize} for constructor definition - */ - fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { - - /** - * Properties which when set cause object to change dimensions - * @type Object - * @private - */ - _dimensionAffectingProps: { - fontSize: true, - fontWeight: true, - fontFamily: true, - textDecoration: true, - fontStyle: true, - lineHeight: true, - stroke: true, - strokeWidth: true, - text: true - }, - - /** - * @private - */ - _reNewline: /\r?\n/, - - /** - * Retrieves object's fontSize - * @method getFontSize - * @memberOf fabric.Text.prototype - * @return {String} Font size (in pixels) - */ - - /** - * Sets object's fontSize - * @method setFontSize - * @memberOf fabric.Text.prototype - * @param {Number} fontSize Font size (in pixels) - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontWeight - * @method getFontWeight - * @memberOf fabric.Text.prototype - * @return {(String|Number)} Font weight - */ - - /** - * Sets object's fontWeight - * @method setFontWeight - * @memberOf fabric.Text.prototype - * @param {(Number|String)} fontWeight Font weight - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontFamily - * @method getFontFamily - * @memberOf fabric.Text.prototype - * @return {String} Font family - */ - - /** - * Sets object's fontFamily - * @method setFontFamily - * @memberOf fabric.Text.prototype - * @param {String} fontFamily Font family - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's text - * @method getText - * @memberOf fabric.Text.prototype - * @return {String} text - */ - - /** - * Sets object's text - * @method setText - * @memberOf fabric.Text.prototype - * @param {String} text Text - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textDecoration - * @method getTextDecoration - * @memberOf fabric.Text.prototype - * @return {String} Text decoration - */ - - /** - * Sets object's textDecoration - * @method setTextDecoration - * @memberOf fabric.Text.prototype - * @param {String} textDecoration Text decoration - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontStyle - * @method getFontStyle - * @memberOf fabric.Text.prototype - * @return {String} Font style - */ - - /** - * Sets object's fontStyle - * @method setFontStyle - * @memberOf fabric.Text.prototype - * @param {String} fontStyle Font style - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's lineHeight - * @method getLineHeight - * @memberOf fabric.Text.prototype - * @return {Number} Line height - */ - - /** - * Sets object's lineHeight - * @method setLineHeight - * @memberOf fabric.Text.prototype - * @param {Number} lineHeight Line height - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textAlign - * @method getTextAlign - * @memberOf fabric.Text.prototype - * @return {String} Text alignment - */ - - /** - * Sets object's textAlign - * @method setTextAlign - * @memberOf fabric.Text.prototype - * @param {String} textAlign Text alignment - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textBackgroundColor - * @method getTextBackgroundColor - * @memberOf fabric.Text.prototype - * @return {String} Text background color - */ - - /** - * Sets object's textBackgroundColor - * @method setTextBackgroundColor - * @memberOf fabric.Text.prototype - * @param {String} textBackgroundColor Text background color - * @return {fabric.Text} - * @chainable - */ - - /** - * Type of an object - * @type String - * @default - */ - type: 'text', - - /** - * Font size (in pixels) - * @type Number - * @default - */ - fontSize: 40, - - /** - * Font weight (e.g. bold, normal, 400, 600, 800) - * @type {(Number|String)} - * @default - */ - fontWeight: 'normal', - - /** - * Font family - * @type String - * @default - */ - fontFamily: 'Times New Roman', - - /** - * Text decoration Possible values: "", "underline", "overline" or "line-through". - * @type String - * @default - */ - textDecoration: '', - - /** - * Text alignment. Possible values: "left", "center", or "right". - * @type String - * @default - */ - textAlign: 'left', - - /** - * Font style . Possible values: "", "normal", "italic" or "oblique". - * @type String - * @default - */ - fontStyle: '', - - /** - * Line height - * @type Number - * @default - */ - lineHeight: 1.3, - - /** - * Background color of text lines - * @type String - * @default - */ - textBackgroundColor: '', - - /** - * URL of a font file, when using Cufon - * @type String | null - * @default - */ - path: null, - - /** - * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used) - * @type Boolean - * @default - */ - useNative: true, - - /** - * List of properties to consider when checking if - * state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * When defined, an object is rendered via stroke and this property specifies its color. - * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 - * @type String - * @default - */ - stroke: null, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Constructor - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.Text} thisArg - */ - initialize: function(text, options) { - options = options || { }; - - this.text = text; - this.__skipDimension = true; - this.setOptions(options); - this.__skipDimension = false; - this._initDimensions(); - }, - - /** - * Renders text object on offscreen canvas, so that it would get dimensions - * @private - */ - _initDimensions: function() { - if (this.__skipDimension) { - return; - } - var canvasEl = fabric.util.createCanvasElement(); - this._render(canvasEl.getContext('2d')); - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of text object - */ - toString: function() { - return '#'; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - - if (typeof Cufon === 'undefined' || this.useNative === true) { - this._renderViaNative(ctx); - } - else { - this._renderViaCufon(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderViaNative: function(ctx) { - var textLines = this.text.split(this._reNewline); - - this._setTextStyles(ctx); - - this.width = this._getTextWidth(ctx, textLines); - this.height = this._getTextHeight(ctx, textLines); - - this.clipTo && fabric.util.clipContext(this, ctx); - - this._renderTextBackground(ctx, textLines); - this._translateForTextAlign(ctx); - this._renderText(ctx, textLines); - - if (this.textAlign !== 'left' && this.textAlign !== 'justify') { - ctx.restore(); - } - - this._renderTextDecoration(ctx, textLines); - this.clipTo && ctx.restore(); - - this._setBoundaries(ctx, textLines); - this._totalLineHeight = 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderText: function(ctx, textLines) { - ctx.save(); - this._setShadow(ctx); - this._setupFillRule(ctx); - this._renderTextFill(ctx, textLines); - this._renderTextStroke(ctx, textLines); - this._restoreFillRule(ctx); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _translateForTextAlign: function(ctx) { - if (this.textAlign !== 'left' && this.textAlign !== 'justify') { - ctx.save(); - ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _setBoundaries: function(ctx, textLines) { - this._boundaries = [ ]; - - for (var i = 0, len = textLines.length; i < len; i++) { - - var lineWidth = this._getLineWidth(ctx, textLines[i]), - lineLeftOffset = this._getLineLeftOffset(lineWidth); - - this._boundaries.push({ - height: this.fontSize * this.lineHeight, - width: lineWidth, - left: lineLeftOffset - }); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setTextStyles: function(ctx) { - this._setFillStyles(ctx); - this._setStrokeStyles(ctx); - ctx.textBaseline = 'alphabetic'; - if (!this.skipTextAlign) { - ctx.textAlign = this.textAlign; - } - ctx.font = this._getFontDeclaration(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - * @return {Number} Height of fabric.Text object - */ - _getTextHeight: function(ctx, textLines) { - return this.fontSize * textLines.length * this.lineHeight; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - * @return {Number} Maximum width of fabric.Text object - */ - _getTextWidth: function(ctx, textLines) { - var maxWidth = ctx.measureText(textLines[0] || '|').width; - - for (var i = 1, len = textLines.length; i < len; i++) { - var currentLineWidth = ctx.measureText(textLines[i]).width; - if (currentLineWidth > maxWidth) { - maxWidth = currentLineWidth; - } - } - return maxWidth; - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} chars Chars to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - */ - _renderChars: function(method, ctx, chars, left, top) { - ctx[method](chars, left, top); - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Text to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - * @param {Number} lineIndex Index of a line in a text - */ - _renderTextLine: function(method, ctx, line, left, top, lineIndex) { - // lift the line by quarter of fontSize - top -= this.fontSize / 4; - - // short-circuit - if (this.textAlign !== 'justify') { - this._renderChars(method, ctx, line, left, top, lineIndex); - return; - } - - var lineWidth = ctx.measureText(line).width, - totalWidth = this.width; - - if (totalWidth > lineWidth) { - // stretch the line - var words = line.split(/\s+/), - wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, - widthDiff = totalWidth - wordsWidth, - numSpaces = words.length - 1, - spaceWidth = widthDiff / numSpaces, - leftOffset = 0; - - for (var i = 0, len = words.length; i < len; i++) { - this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); - leftOffset += ctx.measureText(words[i]).width + spaceWidth; - } - } - else { - this._renderChars(method, ctx, line, left, top, lineIndex); - } - }, - - /** - * @private - * @return {Number} Left offset - */ - _getLeftOffset: function() { - if (fabric.isLikelyNode) { - return 0; - } - return -this.width / 2; - }, - - /** - * @private - * @return {Number} Top offset - */ - _getTopOffset: function() { - return -this.height / 2; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextFill: function(ctx, textLines) { - if (!this.fill && !this._skipFillStrokeCheck) { - return; - } - - this._boundaries = [ ]; - var lineHeights = 0; - - for (var i = 0, len = textLines.length; i < len; i++) { - var heightOfLine = this._getHeightOfLine(ctx, i, textLines); - lineHeights += heightOfLine; - - this._renderTextLine( - 'fillText', - ctx, - textLines[i], - this._getLeftOffset(), - this._getTopOffset() + lineHeights, - i - ); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextStroke: function(ctx, textLines) { - if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) { - return; - } - - var lineHeights = 0; - - ctx.save(); - if (this.strokeDashArray) { - // Spec requires the concatenation of two copies the dash list when the number of elements is odd - if (1 & this.strokeDashArray.length) { - this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); - } - supportsLineDash && ctx.setLineDash(this.strokeDashArray); - } - - ctx.beginPath(); - for (var i = 0, len = textLines.length; i < len; i++) { - var heightOfLine = this._getHeightOfLine(ctx, i, textLines); - lineHeights += heightOfLine; - - this._renderTextLine( - 'strokeText', - ctx, - textLines[i], - this._getLeftOffset(), - this._getTopOffset() + lineHeights, - i - ); - } - ctx.closePath(); - ctx.restore(); - }, - - _getHeightOfLine: function() { - return this.fontSize * this.lineHeight; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextBackground: function(ctx, textLines) { - this._renderTextBoxBackground(ctx); - this._renderTextLinesBackground(ctx, textLines); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextBoxBackground: function(ctx) { - if (!this.backgroundColor) { - return; - } - - ctx.save(); - ctx.fillStyle = this.backgroundColor; - - ctx.fillRect( - this._getLeftOffset(), - this._getTopOffset(), - this.width, - this.height - ); - - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextLinesBackground: function(ctx, textLines) { - if (!this.textBackgroundColor) { - return; - } - - ctx.save(); - ctx.fillStyle = this.textBackgroundColor; - - for (var i = 0, len = textLines.length; i < len; i++) { - - if (textLines[i] !== '') { - - var lineWidth = this._getLineWidth(ctx, textLines[i]), - lineLeftOffset = this._getLineLeftOffset(lineWidth); - - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset, - this._getTopOffset() + (i * this.fontSize * this.lineHeight), - lineWidth, - this.fontSize * this.lineHeight - ); - } - } - ctx.restore(); - }, - - /** - * @private - * @param {Number} lineWidth Width of text line - * @return {Number} Line left offset - */ - _getLineLeftOffset: function(lineWidth) { - if (this.textAlign === 'center') { - return (this.width - lineWidth) / 2; - } - if (this.textAlign === 'right') { - return this.width - lineWidth; - } - return 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Text line - * @return {Number} Line width - */ - _getLineWidth: function(ctx, line) { - return this.textAlign === 'justify' - ? this.width - : ctx.measureText(line).width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextDecoration: function(ctx, textLines) { - if (!this.textDecoration) { - return; - } - - // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; - var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, - _this = this; - - /** @ignore */ - function renderLinesAtOffset(offset) { - for (var i = 0, len = textLines.length; i < len; i++) { - - var lineWidth = _this._getLineWidth(ctx, textLines[i]), - lineLeftOffset = _this._getLineLeftOffset(lineWidth); - - ctx.fillRect( - _this._getLeftOffset() + lineLeftOffset, - ~~((offset + (i * _this._getHeightOfLine(ctx, i, textLines))) - halfOfVerticalBox), - lineWidth, - 1); - } - } - - if (this.textDecoration.indexOf('underline') > -1) { - renderLinesAtOffset(this.fontSize * this.lineHeight); - } - if (this.textDecoration.indexOf('line-through') > -1) { - renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize / 2); - } - if (this.textDecoration.indexOf('overline') > -1) { - renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize); - } - }, - - /** - * @private - */ - _getFontDeclaration: function() { - return [ - // node-canvas needs "weight style", while browsers need "style weight" - (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), - (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), - this.fontSize + 'px', - (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) - ].join(' '); - }, - - /** - * Renders text instance on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - this._transform(ctx, noTransform); - - var m = this.transformMatrix, - isInPathGroup = this.group && this.group.type === 'path-group'; - - if (isInPathGroup) { - ctx.translate(-this.group.width/2, -this.group.height/2); - } - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - if (isInPathGroup) { - ctx.translate(this.left, this.top); - } - this._render(ctx); - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var object = extend(this.callSuper('toObject', propertiesToInclude), { - text: this.text, - fontSize: this.fontSize, - fontWeight: this.fontWeight, - fontFamily: this.fontFamily, - fontStyle: this.fontStyle, - lineHeight: this.lineHeight, - textDecoration: this.textDecoration, - textAlign: this.textAlign, - path: this.path, - textBackgroundColor: this.textBackgroundColor, - useNative: this.useNative - }); - if (!this.includeDefaultValues) { - this._removeDefaultValues(object); - } - return object; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = [ ], - textLines = this.text.split(this._reNewline), - offsets = this._getSVGLeftTopOffsets(textLines), - textAndBg = this._getSVGTextAndBg(offsets.lineTop, offsets.textLeft, textLines), - shadowSpans = this._getSVGShadows(offsets.lineTop, textLines); - - // move top offset by an ascent - offsets.textTop += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0); - - this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - - /** - * @private - */ - _getSVGLeftTopOffsets: function(textLines) { - var lineTop = this.useNative - ? this.fontSize * this.lineHeight - : (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)), - - textLeft = -(this.width/2), - textTop = this.useNative - ? this.fontSize - 1 - : (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight; - - return { - textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), - textTop: textTop + (this.group && this.group.type === 'path-group' ? this.top : 0), - lineTop: lineTop - }; - }, - - /** - * @private - */ - _wrapSVGTextAndBg: function(markup, textAndBg, shadowSpans, offsets) { - markup.push( - '\n', - textAndBg.textBgRects.join(''), - '', - shadowSpans.join(''), - textAndBg.textSpans.join(''), - '\n', - '\n' - ); - }, - - /** - * @private - * @param {Number} lineHeight - * @param {Array} textLines Array of all text lines - * @return {Array} - */ - _getSVGShadows: function(lineHeight, textLines) { - var shadowSpans = [], - i, len, - lineTopOffsetMultiplier = 1; - - if (!this.shadow || !this._boundaries) { - return shadowSpans; - } - - for (i = 0, len = textLines.length; i < len; i++) { - if (textLines[i] !== '') { - var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0; - shadowSpans.push( - '', - fabric.util.string.escapeXml(textLines[i]), - ''); - lineTopOffsetMultiplier = 1; - } - else { - // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier - // prevents empty tspans - lineTopOffsetMultiplier++; - } - } - - return shadowSpans; - }, - - /** - * @private - * @param {Number} lineHeight - * @param {Number} textLeftOffset Text left offset - * @param {Array} textLines Array of all text lines - * @return {Object} - */ - _getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) { - var textSpans = [ ], - textBgRects = [ ], - lineTopOffsetMultiplier = 1; - - // bounding-box background - this._setSVGBg(textBgRects); - - // text and text-background - for (var i = 0, len = textLines.length; i < len; i++) { - if (textLines[i] !== '') { - this._setSVGTextLineText(textLines[i], i, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); - lineTopOffsetMultiplier = 1; - } - else { - // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier - // prevents empty tspans - lineTopOffsetMultiplier++; - } - - if (!this.textBackgroundColor || !this._boundaries) { - continue; - } - - this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight); - } - - return { - textSpans: textSpans, - textBgRects: textBgRects - }; - }, - - _setSVGTextLineText: function(textLine, i, textSpans, lineHeight, lineTopOffsetMultiplier) { - var lineLeftOffset = (this._boundaries && this._boundaries[i]) - ? toFixed(this._boundaries[i].left, 2) - : 0; - - textSpans.push( - ' elements since setting opacity - // on containing one doesn't work in Illustrator - this._getFillAttributes(this.fill), '>', - fabric.util.string.escapeXml(textLine), - '' - ); - }, - - _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, lineHeight) { - textBgRects.push( - '\n'); - }, - - _setSVGBg: function(textBgRects) { - if (this.backgroundColor && this._boundaries) { - textBgRects.push( - ''); - } - }, - - /** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * - * @private - * @param {Any} value - * @return {String} - */ - _getFillAttributes: function(value) { - var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; - if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { - return 'fill="' + value + '"'; - } - return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; - }, - /* _TO_SVG_END_ */ - - /** - * Sets specified property to a specified value - * @param {String} key - * @param {Any} value - * @return {fabric.Text} thisArg - * @chainable - */ - _set: function(key, value) { - if (key === 'fontFamily' && this.path) { - this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3'); - } - this.callSuper('_set', key, value); - - if (key in this._dimensionAffectingProps) { - this._initDimensions(); - this.setCoords(); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) - * @static - * @memberOf fabric.Text - * @see: http://www.w3.org/TR/SVG/text.html#TextElement - */ - fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( - 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); - - /** - * Default SVG font size - * @static - * @memberOf fabric.Text - */ - fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; - - /** - * Returns fabric.Text instance from an SVG element (not yet implemented) - * @static - * @memberOf fabric.Text - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromElement = function(element, options) { - if (!element) { - return null; - } - - var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); - options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); - - if ('dx' in parsedAttributes) { - options.left += parsedAttributes.dx; - } - if ('dy' in parsedAttributes) { - options.top += parsedAttributes.dy; - } - if (!('fontSize' in options)) { - options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; - } - - if (!options.originX) { - options.originX = 'left'; - } - - var text = new fabric.Text(element.textContent, options), - /* - Adjust positioning: - x/y attributes in SVG correspond to the bottom-left corner of text bounding box - top/left properties in Fabric correspond to center point of text bounding box - */ - offX = 0; - - if (text.originX === 'left') { - offX = text.getWidth() / 2; - } - if (text.originX === 'right') { - offX = -text.getWidth() / 2; - } - text.set({ - left: text.getLeft() + offX, - top: text.getTop() - text.getHeight() / 2 - }); - - return text; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Text instance from an object representation - * @static - * @memberOf fabric.Text - * @param {Object} object Object to create an instance from - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromObject = function(object) { - return new fabric.Text(object.text, clone(object)); - }; - - fabric.util.createAccessors(fabric.Text); - -})(typeof exports !== 'undefined' ? exports : this); - }).call({}, window, document, html2canvas); \ No newline at end of file diff --git a/dist/html2canvas.svg.min.js b/dist/html2canvas.svg.min.js index 38bd965..07c61db 100644 --- a/dist/html2canvas.svg.min.js +++ b/dist/html2canvas.svg.min.js @@ -4,9 +4,4 @@ Released under MIT License */ -(function(window,document,exports,undefined){var fabric=fabric||{version:"1.4.11"};"undefined"!=typeof exports&&(exports.fabric=fabric),"undefined"!=typeof document&&"undefined"!=typeof window?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom(""),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode="undefined"!=typeof Buffer&&"undefined"==typeof window,fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"],fabric.DPI=96;var Cufon=function(){function a(a){var b=this.face=a.face;this.glyphs=a.glyphs,this.w=a.w,this.baseSize=parseInt(b["units-per-em"],10),this.family=b["font-family"].toLowerCase(),this.weight=b["font-weight"],this.style=b["font-style"]||"normal",this.viewBox=function(){var a=b.bbox.split(/\s+/),c={minX:parseInt(a[0],10),minY:parseInt(a[1],10),maxX:parseInt(a[2],10),maxY:parseInt(a[3],10)};return c.width=c.maxX-c.minX,c.height=c.maxY-c.minY,c.toString=function(){return[this.minX,this.minY,this.width,this.height].join(" ")},c}(),this.ascent=-parseInt(b.ascent,10),this.descent=-parseInt(b.descent,10),this.height=-this.ascent+this.descent}function b(){var a={},b={oblique:"italic",italic:"oblique"};this.add=function(b){(a[b.style]||(a[b.style]={}))[b.weight]=b},this.get=function(c,d){var e=a[c]||a[b[c]]||a.normal||a.italic||a.oblique;if(!e)return null;if(d={normal:400,bold:700}[d]||parseInt(d,10),e[d])return e[d];var f,g,h={1:1,99:0}[d%100],i=[];h===undefined&&(h=d>400),500==d&&(d=400);for(var j in e)j=parseInt(j,10),(!f||f>j)&&(f=j),(!g||j>g)&&(g=j),i.push(j);return f>d&&(d=f),d>g&&(d=g),i.sort(function(a,b){return(h?a>d&&b>d?b>a:a>b:d>a&&d>b?a>b:b>a)?-1:1}),e[i[0]]}}function c(){function a(a,b){return a.contains?a.contains(b):16&a.compareDocumentPosition(b)}function b(b){var c=b.relatedTarget;c&&!a(this,c)&&d(this)}function c(){d(this)}function d(a){setTimeout(function(){n.replace(a,r.get(a).options,!0)},10)}this.attach=function(a){a.onmouseenter===undefined?(f(a,"mouseover",b),f(a,"mouseout",b)):(f(a,"mouseenter",c),f(a,"mouseleave",c))}}function d(){function a(a){return a.cufid||(a.cufid=++c)}var b={},c=0;this.get=function(c){var d=a(c);return b[d]||(b[d]={})}}function e(a){var b={},c={};this.get=function(c){return b[c]!=undefined?b[c]:a[c]},this.getSize=function(a,b){return c[a]||(c[a]=new p.Size(this.get(a),b))},this.extend=function(a){for(var c in a)b[c]=a[c];return this}}function f(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,function(){return c.call(a,fabric.window.event)})}function g(a,b){var c=r.get(a);return c.options?a:(b.hover&&b.hoverables[a.nodeName.toLowerCase()]&&s.attach(a),c.options=b,a)}function h(a){var b={};return function(c){return b.hasOwnProperty(c)||(b[c]=a.apply(null,arguments)),b[c]}}function i(a,b){b||(b=p.getStyle(a));for(var c,d=p.quotedList(b.get("fontFamily").toLowerCase()),e=0,f=d.length;f>e;++e)if(c=d[e],v[c])return v[c].get(b.get("fontStyle"),b.get("fontWeight"));return null}function j(a){return fabric.document.getElementsByTagName(a)}function k(){for(var a,b={},c=0,d=arguments.length;d>c;++c)for(a in arguments[c])b[a]=arguments[c][a];return b}function l(a,b,c,d,e,f){var g=d.separate;if("none"==g)return u[d.engine].apply(null,arguments);var h,i=fabric.document.createDocumentFragment(),j=b.split(x[g]),k="words"==g;k&&q&&(/^\s/.test(b)&&j.unshift(""),/\s$/.test(b)&&j.push(""));for(var l=0,m=j.length;m>l;++l)h=u[d.engine](a,k?p.textAlign(j[l],c,l,m):j[l],c,d,e,f,m-1>l),h&&i.appendChild(h);return i}function m(a,b){for(var c,d,e,f,h=g(a,b).firstChild;h;h=e){if(e=h.nextSibling,f=!1,1==h.nodeType){if(!h.firstChild)continue;if(!/cufon/.test(h.className)){arguments.callee(h,b);continue}f=!0}if(d||(d=p.getStyle(a).extend(b)),c||(c=i(a,d)),c)if(f)u[b.engine](c,null,d,b,h,a);else{var j=h.data;if("undefined"!=typeof G_vmlCanvasManager&&(j=j.replace(/\r/g,"\n")),""!==j){var k=l(c,j,d,b,h,a);k?h.parentNode.replaceChild(k,h):h.parentNode.removeChild(h)}}}}var n=function(){return n.replace.apply(null,arguments)},o=n.DOM={ready:function(){var a=!1,b={loaded:1,complete:1},c=[],d=function(){if(!a){a=!0;for(var b;b=c.shift();b());}};return fabric.document.addEventListener&&(fabric.document.addEventListener("DOMContentLoaded",d,!1),fabric.window.addEventListener("pageshow",d,!1)),!fabric.window.opera&&fabric.document.readyState&&function(){b[fabric.document.readyState]?d():setTimeout(arguments.callee,10)}(),fabric.document.readyState&&fabric.document.createStyleSheet&&function(){try{fabric.document.body.doScroll("left"),d()}catch(a){setTimeout(arguments.callee,1)}}(),f(fabric.window,"load",d),function(b){arguments.length?a?b():c.push(b):d()}}()},p=n.CSS={Size:function(a,b){this.value=parseFloat(a),this.unit=String(a).match(/[a-z%]*$/)[0]||"px",this.convert=function(a){return a/b*this.value},this.convertFrom=function(a){return a/this.value*b},this.toString=function(){return this.value+this.unit}},getStyle:function(a){return new e(a.style)},quotedList:h(function(a){for(var b,c=[],d=/\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g;b=d.exec(a);)c.push(b[3]||b[1]);return c}),ready:function(){var a=!1,b=[],c=function(){a=!0;for(var c;c=b.shift();c());},d=Object.prototype.propertyIsEnumerable?j("style"):{length:0},e=j("link");return o.ready(function(){for(var a,b=0,f=0,g=e.length;a=e[f],g>f;++f)a.disabled||"stylesheet"!=a.rel.toLowerCase()||++b;fabric.document.styleSheets.length>=d.length+b?c():setTimeout(arguments.callee,10)}),function(c){a?c():b.push(c)}}(),supports:function(a,b){var c=fabric.document.createElement("span").style;return c[a]===undefined?!1:(c[a]=b,c[a]===b)},textAlign:function(a,b,c,d){return"right"==b.get("textAlign")?c>0&&(a=" "+a):d-1>c&&(a+=" "),a},textDecoration:function(a,b){b||(b=this.getStyle(a));for(var c={underline:null,overline:null,"line-through":null},d=a;d.parentNode&&1==d.parentNode.nodeType;){var e=!0;for(var f in c)c[f]||(-1!=b.get("textDecoration").indexOf(f)&&(c[f]=b.get("color")),e=!1);if(e)break;b=this.getStyle(d=d.parentNode)}return c},textShadow:h(function(a){if("none"==a)return null;for(var b,c=[],d={},e=0,f=/(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/gi;b=f.exec(a);)","==b[0]?(c.push(d),d={},e=0):b[1]?d.color=b[1]:d[["offX","offY","blur"][e++]]=b[2];return c.push(d),c}),color:h(function(a){var b={};return b.color=a.replace(/^rgba\((.*?),\s*([\d.]+)\)/,function(a,c,d){return b.opacity=parseFloat(d),"rgb("+c+")"}),b}),textTransform:function(a,b){return a[{uppercase:"toUpperCase",lowercase:"toLowerCase"}[b.get("textTransform")]||"toString"]()}},q=0==" ".split(/\s+/).length,r=new d,s=new c,t=[],u={},v={},w={engine:null,hover:!1,hoverables:{a:!0},printable:!0,selector:fabric.window.Sizzle||fabric.window.jQuery&&function(a){return jQuery(a)}||fabric.window.dojo&&dojo.query||fabric.window.$$&&function(a){return $$(a)}||fabric.window.$&&function(a){return $(a)}||fabric.document.querySelectorAll&&function(a){return fabric.document.querySelectorAll(a)}||j,separate:"words",textShadow:"none"},x={words:/\s+/,characters:""};return n.now=function(){return o.ready(),n},n.refresh=function(){for(var a=t.splice(0,t.length),b=0,c=a.length;c>b;++b)n.replace.apply(null,a[b]);return n},n.registerEngine=function(a,b){return b?(u[a]=b,n.set("engine",a)):n},n.registerFont=function(c){var d=new a(c),e=d.family;return v[e]||(v[e]=new b),v[e].add(d),n.set("fontFamily",'"'+e+'"')},n.replace=function(a,b,c){return b=k(w,b),b.engine?("string"==typeof b.textShadow&&b.textShadow&&(b.textShadow=p.textShadow(b.textShadow)),c||t.push(arguments),(a.nodeType||"string"==typeof a)&&(a=[a]),p.ready(function(){for(var c=0,d=a.length;d>c;++c){var e=a[c];"string"==typeof e?n.replace(b.selector(e),b,!0):m(e,b)}}),n):n},n.replaceElement=function(a,b){return b=k(w,b),"string"==typeof b.textShadow&&b.textShadow&&(b.textShadow=p.textShadow(b.textShadow)),m(a,b)},n.engines=u,n.fonts=v,n.getOptions=function(){return k(w)},n.set=function(a,b){return w[a]=b,n},n}();Cufon.registerEngine("canvas",function(){function a(a,b){var c,d=0,e=0,f=[],g=/([mrvxe])([^a-z]*)/g;a:for(var h=0;c=g.exec(a);++h){var i=c[2].split(",");switch(c[1]){case"v":f[h]={m:"bezierCurveTo",a:[d+~~i[0],e+~~i[1],d+~~i[2],e+~~i[3],d+=~~i[4],e+=~~i[5]]};break;case"r":f[h]={m:"lineTo",a:[d+=~~i[0],e+=~~i[1]]};break;case"m":f[h]={m:"moveTo",a:[d=~~i[0],e=~~i[1]]};break;case"x":f[h]={m:"closePath",a:[]};break;case"e":break a}b[f[h].m].apply(b,f[h].a)}return f}function b(a,b){for(var c=0,d=a.length;d>c;++c){var e=a[c];b[e.m].apply(b,e.a)}}var c=Cufon.CSS.supports("display","inline-block"),d=!c&&("BackCompat"==fabric.document.compatMode||/frameset|transitional/i.test(fabric.document.doctype.publicId)),e=fabric.document.createElement("style");e.type="text/css";var f=fabric.document.createTextNode(".cufon-canvas{text-indent:0}@media screen,projection{.cufon-canvas{display:inline;display:inline-block;position:relative;vertical-align:middle"+(d?"":";font-size:1px;line-height:1px")+"}.cufon-canvas .cufon-alt{display:-moz-inline-box;display:inline-block;width:0;height:0;overflow:hidden}"+(c?".cufon-canvas canvas{position:relative}":".cufon-canvas canvas{position:absolute}")+"}@media print{.cufon-canvas{padding:0 !important}.cufon-canvas canvas{display:none}.cufon-canvas .cufon-alt{display:inline}}");try{e.appendChild(f)}catch(g){e.setAttribute("type","text/css"),e.styleSheet.cssText=f.data}return fabric.document.getElementsByTagName("head")[0].appendChild(e),function(d,e,f,g,h){function i(){T.save();var a=0,b=0,c=[{left:0}];g.backgroundColor&&(T.save(),T.fillStyle=g.backgroundColor,T.translate(0,d.ascent),T.fillRect(0,0,A+10,(-d.ascent+d.descent)*D),T.restore()),"right"===g.textAlign?(T.translate(G[b],0),c[0].left=G[b]*U):"center"===g.textAlign&&(T.translate(G[b]/2,0),c[0].left=G[b]/2*U);for(var e=0,f=z.length;f>e;++e)if("\n"!==z[e]){var h=d.glyphs[z[e]]||d.missingGlyph;if(h){var i=Number(h.w||d.w)+n;g.textBackgroundColor&&(T.save(),T.fillStyle=g.textBackgroundColor,T.translate(0,d.ascent),T.fillRect(0,0,i+10,-d.ascent+d.descent),T.restore()),T.translate(i,0),a+=i,e==f-1&&(c[c.length-1].width=a*U,c[c.length-1].height=(-d.ascent+d.descent)*U)}}else{b++;var j=-d.ascent-d.ascent/5*g.lineHeight,k=c[c.length-1],l={left:0};k.width=a*U,k.height=(-d.ascent+d.descent)*U,"right"===g.textAlign?(T.translate(-A,j),T.translate(G[b],0),l.left=G[b]*U):"center"===g.textAlign?(T.translate(-a-G[b-1]/2,j),T.translate(G[b]/2,0),l.left=G[b]/2*U):T.translate(-a,j),c.push(l),a=0}T.restore(),Cufon.textOptions.boundaries=c}function j(c){T.fillStyle=c||Cufon.textOptions.color||f.get("color");var e=0,h=0;"right"===g.textAlign?T.translate(G[h],0):"center"===g.textAlign&&T.translate(G[h]/2,0);for(var i=0,j=z.length;j>i;++i)if("\n"!==z[i]){var k=d.glyphs[z[i]]||d.missingGlyph;if(k){var l=Number(k.w||d.w)+n;W&&(T.save(),T.strokeStyle=T.fillStyle,T.lineWidth+=T.lineWidth,T.beginPath(),W.underline&&(T.moveTo(0,-d.face["underline-position"]+.5),T.lineTo(l,-d.face["underline-position"]+.5)),W.overline&&(T.moveTo(0,d.ascent+.5),T.lineTo(l,d.ascent+.5)),W["line-through"]&&(T.moveTo(0,-d.descent+.5),T.lineTo(l,-d.descent+.5)),T.stroke(),T.restore()),X&&(T.save(),T.transform(1,0,-.25,1,0,0)),T.beginPath(),k.d&&(k.code?b(k.code,T):k.code=a("m"+k.d,T)),T.fill(),g.strokeStyle&&(T.closePath(),T.save(),T.lineWidth=g.strokeWidth,T.strokeStyle=g.strokeStyle,T.stroke(),T.restore()),X&&T.restore(),T.translate(l,0),e+=l}}else{h++;var m=-d.ascent-d.ascent/5*g.lineHeight;"right"===g.textAlign?(T.translate(-A,m),T.translate(G[h],0)):"center"===g.textAlign?(T.translate(-e-G[h-1]/2,m),T.translate(G[h]/2,0)):T.translate(-e,m),e=0}}var k=null===e,l=d.viewBox,m=f.getSize("fontSize",d.baseSize),n=f.get("letterSpacing");n="normal"==n?0:m.convertFrom(parseInt(n,10));var o=0,p=0,q=0,r=0,s=g.textShadow,t=[];if(Cufon.textOptions.shadowOffsets=[],Cufon.textOptions.shadows=null,s){Cufon.textOptions.shadows=s;for(var u=0,v=s.length;v>u;++u){var w=s[u],x=m.convertFrom(parseFloat(w.offX)),y=m.convertFrom(parseFloat(w.offY));t[u]=[x,y]}}for(var z=Cufon.CSS.textTransform(k?h.alt:e,f).split(""),A=0,B=null,C=0,D=1,E=[],u=0,v=z.length;v>u;++u)if("\n"!==z[u]){var F=d.glyphs[z[u]]||d.missingGlyph;F&&(A+=B=Number(F.w||d.w)+n)}else D++,A>C&&(C=A),E.push(A),A=0;E.push(A),A=Math.max(C,A);for(var G=[],u=E.length;u--;)G[u]=A-E[u];if(null===B)return null;p+=l.width-B,r+=l.minX;var H,I;if(k)H=h,I=h.firstChild;else if(H=fabric.document.createElement("span"),H.className="cufon cufon-canvas",H.alt=e,I=fabric.document.createElement("canvas"),H.appendChild(I),g.printable){var J=fabric.document.createElement("span");J.className="cufon-alt",J.appendChild(fabric.document.createTextNode(e)),H.appendChild(J)}var K=H.style,L=I.style||{},M=m.convert(l.height-o+q),N=Math.ceil(M),O=N/M;I.width=Math.ceil(m.convert(A+p-r)*O),I.height=N,o+=l.minY,L.top=Math.round(m.convert(o-d.ascent))+"px",L.left=Math.round(m.convert(r))+"px";var P=Math.ceil(m.convert(A*O)),Q=P+"px",R=m.convert(d.height),S=(g.lineHeight-1)*m.convert(-d.ascent/5)*(D-1);Cufon.textOptions.width=P,Cufon.textOptions.height=R*D+S,Cufon.textOptions.lines=D,Cufon.textOptions.totalLineHeight=S,c?(K.width=Q,K.height=R+"px"):(K.paddingLeft=Q,K.paddingBottom=R-1+"px");var T=Cufon.textOptions.context||I.getContext("2d"),U=N/l.height;Cufon.textOptions.fontAscent=d.ascent*U,Cufon.textOptions.boundaries=null;for(var V=Cufon.textOptions.shadowOffsets,u=t.length;u--;)V[u]=[t[u][0]*U,t[u][1]*U];T.save(),T.scale(U,U),T.translate(-r-1/U*I.width/2+(Cufon.fonts[d.family].offsetLeft||0),-o-Cufon.textOptions.height/U/2+(Cufon.fonts[d.family].offsetTop||0)),T.lineWidth=d.face["underline-thickness"],T.save();var W=Cufon.getTextDecoration(g),X="italic"===g.fontStyle;if(T.save(),i(),s)for(var u=0,v=s.length;v>u;++u){var w=s[u];T.save(),T.translate.apply(T,t[u]),j(w.color),T.restore()}return j(),T.restore(),T.restore(),T.restore(),H}}()),Cufon.registerEngine("vml",function(){function a(a,c){return b(a,/(?:em|ex|%)$/i.test(c)?"1em":c)}function b(a,b){if(/px$/i.test(b))return parseFloat(b);var c=a.style.left,d=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left,a.style.left=b;var e=a.style.pixelLeft;return a.style.left=c,a.runtimeStyle.left=d,e}if(fabric.document.namespaces){var c=fabric.document.createElement("canvas");if(!(c&&c.getContext&&c.getContext.apply)){null==fabric.document.namespaces.cvml&&fabric.document.namespaces.add("cvml","urn:schemas-microsoft-com:vml");var d=fabric.document.createElement("cvml:shape");if(d.style.behavior="url(#default#VML)",d.coordsize)return d=null,fabric.document.write(''),function(c,d,e,f,g,h,i){var j=null===d;j&&(d=g.alt);var k=c.viewBox,l=e.computedFontSize||(e.computedFontSize=new Cufon.CSS.Size(a(h,e.get("fontSize"))+"px",c.baseSize)),m=e.computedLSpacing;m==undefined&&(m=e.get("letterSpacing"),e.computedLSpacing=m="normal"==m?0:~~l.convertFrom(b(h,m)));var n,o;if(j)n=g,o=g.firstChild;else{if(n=fabric.document.createElement("span"),n.className="cufon cufon-vml",n.alt=d,o=fabric.document.createElement("span"),o.className="cufon-vml-canvas",n.appendChild(o),f.printable){var p=fabric.document.createElement("span");p.className="cufon-alt",p.appendChild(fabric.document.createTextNode(d)),n.appendChild(p)}i||n.appendChild(fabric.document.createElement("cvml:shape"))}var q=n.style,r=o.style,s=l.convert(k.height),t=Math.ceil(s),u=t/s,v=k.minX,w=k.minY;r.height=t,r.top=Math.round(l.convert(w-c.ascent)),r.left=Math.round(l.convert(v)),q.height=l.convert(c.height)+"px";for(var x,y,z=(Cufon.getTextDecoration(f),e.get("color")),A=Cufon.CSS.textTransform(d,e).split(""),B=0,C=0,D=null,E=f.textShadow,F=0,G=0,H=A.length;H>F;++F)x=c.glyphs[A[F]]||c.missingGlyph,x&&(B+=D=~~(x.w||c.w)+m);if(null===D)return null;var I,J=-v+B+(k.width-D),K=l.convert(J*u),L=Math.round(K),M=J+","+k.height,N="r"+M+"nsnf";for(F=0;H>F;++F)if(x=c.glyphs[A[F]]||c.missingGlyph){j?(y=o.childNodes[G],y.firstChild&&y.removeChild(y.firstChild)):(y=fabric.document.createElement("cvml:shape"),o.appendChild(y)),y.stroked="f",y.coordsize=M,y.coordorigin=I=v-C+","+w,y.path=(x.d?"m"+x.d+"xe":"")+"m"+I+N,y.fillcolor=z;var O=y.style;if(O.width=L,O.height=t,E){var P,Q=E[0],R=E[1],S=Cufon.CSS.color(Q.color),T=fabric.document.createElement("cvml:shadow");T.on="t",T.color=S.color,T.offset=Q.offX+","+Q.offY,R&&(P=Cufon.CSS.color(R.color),T.type="double",T.color2=P.color,T.offset2=R.offX+","+R.offY),T.opacity=S.opacity||P&&P.opacity||1,y.appendChild(T)}C+=~~(x.w||c.w)+m,++G}return q.width=Math.max(Math.ceil(l.convert(B*u)),0),n}}}}()),Cufon.getTextDecoration=function(a){return{underline:"underline"===a.textDecoration,overline:"overline"===a.textDecoration,"line-through":"line-through"===a.textDecoration}},"undefined"!=typeof exports&&(exports.Cufon=Cufon),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){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(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\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,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),function(){function a(a,b){this.__eventListeners[a]&&(b?fabric.util.removeFromArray(this.__eventListeners[a],b):this.__eventListeners[a].length=0)}function b(a,b){if(this.__eventListeners||(this.__eventListeners={}),1===arguments.length)for(var c in a)this.on(c,a[c]);else this.__eventListeners[a]||(this.__eventListeners[a]=[]),this.__eventListeners[a].push(b);return this}function c(b,c){if(this.__eventListeners){if(0===arguments.length)this.__eventListeners={};else if(1===arguments.length&&"object"==typeof arguments[0])for(var d in b)a.call(this,d,b[d]);else a.call(this,b,c);return this}}function d(a,b){if(this.__eventListeners){var c=this.__eventListeners[a];if(c){for(var d=0,e=c.length;e>d;d++)c[d].call(this,b||{});return this}}}fabric.Observable={observe:b,stopObserving:c,fire:d,on:b,off:c,trigger:d}}(),fabric.Collection={add:function(){this._objects.push.apply(this._objects,arguments);for(var a=0,b=arguments.length;b>a;a++)this._onObjectAdded(arguments[a]);return this.renderOnAddRemove&&this.renderAll(),this},insertAt:function(a,b,c){var d=this.getObjects();return c?d[b]=a:d.splice(b,0,a),this._onObjectAdded(a),this.renderOnAddRemove&&this.renderAll(),this},remove:function(){for(var a,b=this.getObjects(),c=0,d=arguments.length;d>c;c++)a=b.indexOf(arguments[c]),-1!==a&&(b.splice(a,1),this._onObjectRemoved(arguments[c]));return this.renderOnAddRemove&&this.renderAll(),this},forEachObject:function(a,b){for(var c=this.getObjects(),d=c.length;d--;)a.call(b,c[d],d,c);return this},getObjects:function(a){return"undefined"==typeof a?this._objects:this._objects.filter(function(b){return b.type===a})},item:function(a){return this.getObjects()[a]},isEmpty:function(){return 0===this.getObjects().length},size:function(){return this.getObjects().length},contains:function(a){return this.getObjects().indexOf(a)>-1},complexity:function(){return this.getObjects().reduce(function(a,b){return a+=b.complexity?b.complexity():0},0)}},function(a){var b=Math.sqrt,c=Math.atan2,d=Math.PI/180;fabric.util={removeFromArray:function(a,b){var c=a.indexOf(b);return-1!==c&&a.splice(c,1),a},getRandomInt:function(a,b){return Math.floor(Math.random()*(b-a+1))+a},degreesToRadians:function(a){return a*d},radiansToDegrees:function(a){return a/d},rotatePoint:function(a,b,c){var d=Math.sin(c),e=Math.cos(c);a.subtractEquals(b);var f=a.x*e-a.y*d,g=a.x*d+a.y*e;return new fabric.Point(f,g).addEquals(b)},transformPoint:function(a,b,c){return c?new fabric.Point(b[0]*a.x+b[1]*a.y,b[2]*a.x+b[3]*a.y):new fabric.Point(b[0]*a.x+b[1]*a.y+b[4],b[2]*a.x+b[3]*a.y+b[5])},invertTransform:function(a){var b=a.slice(),c=1/(a[0]*a[3]-a[1]*a[2]);b=[c*a[3],-c*a[1],-c*a[2],c*a[0],0,0];var d=fabric.util.transformPoint({x:a[4],y:a[5]},b);return b[4]=-d.x,b[5]=-d.y,b},toFixed:function(a,b){return parseFloat(Number(a).toFixed(b))},parseUnit:function(a){var b=/\D{0,2}$/.exec(a),c=parseFloat(a);switch(b[0]){case"mm":return c*fabric.DPI/25.4;case"cm":return c*fabric.DPI/2.54;case"in":return c*fabric.DPI;case"pt":return c*fabric.DPI/72;case"pc":return c*fabric.DPI/72*12;default:return c}},falseFunction:function(){return!1},getKlass:function(a,b){return a=fabric.util.string.camelize(a.charAt(0).toUpperCase()+a.slice(1)),fabric.util.resolveNamespace(b)[a]},resolveNamespace:function(b){if(!b)return fabric;for(var c=b.split("."),d=c.length,e=a||fabric.window,f=0;d>f;++f)e=e[c[f]];return e},loadImage:function(a,b,c,d){if(!a)return void(b&&b.call(c,a));var e=fabric.util.createImage();e.onload=function(){b&&b.call(c,e),e=e.onload=e.onerror=null},e.onerror=function(){fabric.log("Error loading "+e.src),b&&b.call(c,null,!0),e=e.onload=e.onerror=null},0!==a.indexOf("data")&&"undefined"!=typeof d&&(e.crossOrigin=d),e.src=a},enlivenObjects:function(a,b,c,d){function e(){++g===h&&b&&b(f)}a=a||[];var f=[],g=0,h=a.length;return h?void a.forEach(function(a,b){if(!a||!a.type)return void e();var g=fabric.util.getKlass(a.type,c);g.async?g.fromObject(a,function(c,g){g||(f[b]=c,d&&d(a,f[b])),e()}):(f[b]=g.fromObject(a),d&&d(a,f[b]),e())}):void(b&&b(f))},groupSVGElements:function(a,b,c){var d;return d=new fabric.PathGroup(a,b),"undefined"!=typeof c&&d.setSourcePath(c),d},populateWithProperties:function(a,b,c){if(c&&"[object Array]"===Object.prototype.toString.call(c))for(var d=0,e=c.length;e>d;d++)c[d]in a&&(b[c[d]]=a[c[d]])},drawDashedLine:function(a,d,e,f,g,h){var i=f-d,j=g-e,k=b(i*i+j*j),l=c(j,i),m=h.length,n=0,o=!0;for(a.save(),a.translate(d,e),a.moveTo(0,0),a.rotate(l),d=0;k>d;)d+=h[n++%m],d>k&&(d=k),a[o?"lineTo":"moveTo"](d,0),o=!o;a.restore()},createCanvasElement:function(a){return a||(a=fabric.document.createElement("canvas")),a.getContext||"undefined"==typeof G_vmlCanvasManager||G_vmlCanvasManager.initElement(a),a},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(a){for(var b=a.prototype,c=b.stateProperties.length;c--;){var d=b.stateProperties[c],e=d.charAt(0).toUpperCase()+d.slice(1),f="set"+e,g="get"+e;b[g]||(b[g]=function(a){return new Function('return this.get("'+a+'")')}(d)),b[f]||(b[f]=function(a){return new Function("value",'return this.set("'+a+'", value)')}(d))}},clipContext:function(a,b){b.save(),b.beginPath(),a.clipTo(b),b.clip()},multiplyTransformMatrices:function(a,b){for(var c=[[a[0],a[2],a[4]],[a[1],a[3],a[5]],[0,0,1]],d=[[b[0],b[2],b[4]],[b[1],b[3],b[5]],[0,0,1]],e=[],f=0;3>f;f++){e[f]=[];for(var g=0;3>g;g++){for(var h=0,i=0;3>i;i++)h+=c[f][i]*d[i][g];e[f][g]=h}}return[e[0][0],e[1][0],e[0][1],e[1][1],e[0][2],e[1][2]]},getFunctionBody:function(a){return(String(a).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(a,b,c,d){d>0&&(b>d?b-=d:b=0,c>d?c-=d:c=0);for(var e=!0,f=a.getImageData(b,c,2*d||1,2*d||1),g=3,h=f.data.length;h>g;g+=4){var i=f.data[g];if(e=0>=i,e===!1)break}return f=null,e}}}("undefined"!=typeof exports?exports:this),function(){function a(a,e,g,h,i,j,k){var l=f.call(arguments);if(d[l])return d[l];var m=Math.PI,n=k*(m/180),o=Math.sin(n),p=Math.cos(n),q=0,r=0;g=Math.abs(g),h=Math.abs(h);var s=-p*a-o*e,t=-p*e+o*a,u=g*g,v=h*h,w=t*t,x=s*s,y=4*u*v-u*w-v*x,z=0;if(0>y){var A=Math.sqrt(1-.25*y/(u*v));g*=A,h*=A}else z=(i===j?-.5:.5)*Math.sqrt(y/(u*w+v*x));var B=z*g*t/h,C=-z*h*s/g,D=p*B-o*C+a/2,E=o*B+p*C+e/2,F=c(1,0,(s-B)/g,(t-C)/h),G=c((s-B)/g,(t-C)/h,(-s-B)/g,(-t-C)/h);0===j&&G>0?G-=2*m:1===j&&0>G&&(G+=2*m);for(var H=Math.ceil(Math.abs(G/(.5*m))),I=[],J=G/H,K=8/3*Math.sin(J/4)*Math.sin(J/4)/Math.sin(J/2),L=F+J,M=0;H>M;M++)I[M]=b(F,L,p,o,g,h,D,E,K,q,r),q=I[M][4],r=I[M][5],F+=J,L+=J;return d[l]=I,I}function b(a,b,c,d,g,h,i,j,k,l,m){var n=f.call(arguments);if(e[n])return e[n];var o=Math.cos(a),p=Math.sin(a),q=Math.cos(b),r=Math.sin(b),s=c*g*q-d*h*r+i,t=d*g*q+c*h*r+j,u=l+k*(-c*g*p-d*h*o),v=m+k*(-d*g*p+c*h*o),w=s+k*(c*g*r+d*h*q),x=t+k*(d*g*r-c*h*q);return e[n]=[u,v,w,x,s,t],e[n]}function c(a,b,c,d){var e=Math.atan2(b,a),f=Math.atan2(d,c);return f>=e?f-e:2*Math.PI-(e-f)}var d={},e={},f=Array.prototype.join;fabric.util.drawArc=function(b,c,d,e){for(var f=e[0],g=e[1],h=e[2],i=e[3],j=e[4],k=e[5],l=e[6],m=[[],[],[],[]],n=a(k-c,l-d,f,g,i,j,h),o=0,p=n.length;p>o;o++)m[o][0]=n[o][0]+c,m[o][1]=n[o][1]+d,m[o][2]=n[o][2]+c,m[o][3]=n[o][3]+d,m[o][4]=n[o][4]+c,m[o][5]=n[o][5]+d,b.bezierCurveTo.apply(b,m[o])}}(),function(){function a(a,b){for(var c=e.call(arguments,2),d=[],f=0,g=a.length;g>f;f++)d[f]=c.length?a[f][b].apply(a[f],c):a[f][b].call(a[f]);return d}function b(a,b){return d(a,b,function(a,b){return a>=b})}function c(a,b){return d(a,b,function(a,b){return b>a})}function d(a,b,c){if(a&&0!==a.length){var d=a.length-1,e=b?a[d][b]:a[d];if(b)for(;d--;)c(a[d][b],e)&&(e=a[d][b]);else for(;d--;)c(a[d],e)&&(e=a[d]);return e}}var e=Array.prototype.slice;fabric.util.array={invoke:a,min:c,max:b}}(),function(){function a(a,b){for(var c in b)a[c]=b[c];return a}function b(b){return a({},b)}fabric.util.object={extend:a,clone:b}}(),function(){function a(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})}function b(a,b){return a.charAt(0).toUpperCase()+(b?a.slice(1):a.slice(1).toLowerCase())}function c(a){return a.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}fabric.util.string={camelize:a,capitalize:b,escapeXml:c}}(),function(){function a(){}function b(a){var b=this.constructor.superclass.prototype[a];return arguments.length>1?b.apply(this,d.call(arguments,1)):b.call(this)}function c(){function c(){this.initialize.apply(this,arguments)}var f=null,h=d.call(arguments,0);"function"==typeof h[0]&&(f=h.shift()),c.superclass=f,c.subclasses=[],f&&(a.prototype=f.prototype,c.prototype=new a,f.subclasses.push(c));for(var i=0,j=h.length;j>i;i++)g(c,h[i],f);return c.prototype.initialize||(c.prototype.initialize=e),c.prototype.constructor=c,c.prototype.callSuper=b,c}var d=Array.prototype.slice,e=function(){},f=function(){for(var a in{toString:1})if("toString"===a)return!1;return!0}(),g=function(a,b,c){for(var d in b)a.prototype[d]=d in a.prototype&&"function"==typeof a.prototype[d]&&(b[d]+"").indexOf("callSuper")>-1?function(a){return function(){var d=this.constructor.superclass;this.constructor.superclass=c;var e=b[a].apply(this,arguments);return this.constructor.superclass=d,"initialize"!==a?e:void 0}}(d):b[d],f&&(b.toString!==Object.prototype.toString&&(a.prototype.toString=b.toString),b.valueOf!==Object.prototype.valueOf&&(a.prototype.valueOf=b.valueOf))};fabric.util.createClass=c}(),function(){function a(a){var b,c,d=Array.prototype.slice.call(arguments,1),e=d.length;for(c=0;e>c;c++)if(b=typeof a[d[c]],!/^(?:function|object|unknown)$/.test(b))return!1;return!0}function b(a,b){return{handler:b,wrappedHandler:c(a,b)}}function c(a,b){return function(c){b.call(g(a),c||fabric.window.event)}}function d(a,b){return function(c){if(p[a]&&p[a][b])for(var d=p[a][b],e=0,f=d.length;f>e;e++)d[e].call(this,c||fabric.window.event)}}function e(a,b){a||(a=fabric.window.event);var c=a.target||(typeof a.srcElement!==i?a.srcElement:null),d=fabric.util.getScrollLeftTop(c,b);return{x:q(a)+d.left,y:r(a)+d.top}}function f(a,b,c){var d="touchend"===a.type?"changedTouches":"touches";return a[d]&&a[d][0]?a[d][0][b]-(a[d][0][b]-a[d][0][c])||a[c]:a[c]}var g,h,i="unknown",j=function(){var a=0;return function(b){return b.__uniqueID||(b.__uniqueID="uniqueID__"+a++)}}();!function(){var a={};g=function(b){return a[b]},h=function(b,c){a[b]=c}}();var k,l,m=a(fabric.document.documentElement,"addEventListener","removeEventListener")&&a(fabric.window,"addEventListener","removeEventListener"),n=a(fabric.document.documentElement,"attachEvent","detachEvent")&&a(fabric.window,"attachEvent","detachEvent"),o={},p={};m?(k=function(a,b,c){a.addEventListener(b,c,!1)},l=function(a,b,c){a.removeEventListener(b,c,!1)}):n?(k=function(a,c,d){var e=j(a);h(e,a),o[e]||(o[e]={}),o[e][c]||(o[e][c]=[]);var f=b(e,d);o[e][c].push(f),a.attachEvent("on"+c,f.wrappedHandler)},l=function(a,b,c){var d,e=j(a);if(o[e]&&o[e][b])for(var f=0,g=o[e][b].length;g>f;f++)d=o[e][b][f],d&&d.handler===c&&(a.detachEvent("on"+b,d.wrappedHandler),o[e][b][f]=null)}):(k=function(a,b,c){var e=j(a);if(p[e]||(p[e]={}),!p[e][b]){p[e][b]=[];var f=a["on"+b];f&&p[e][b].push(f),a["on"+b]=d(e,b)}p[e][b].push(c)},l=function(a,b,c){var d=j(a);if(p[d]&&p[d][b])for(var e=p[d][b],f=0,g=e.length;g>f;f++)e[f]===c&&e.splice(f,1)}),fabric.util.addListener=k,fabric.util.removeListener=l;var q=function(a){return typeof a.clientX!==i?a.clientX:0},r=function(a){return typeof a.clientY!==i?a.clientY:0};fabric.isTouchSupported&&(q=function(a){return f(a,"pageX","clientX")},r=function(a){return f(a,"pageY","clientY") -}),fabric.util.getPointer=e,fabric.util.object.extend(fabric.util,fabric.Observable)}(),function(){function a(a,b){var c=a.style;if(!c)return a;if("string"==typeof b)return a.style.cssText+=";"+b,b.indexOf("opacity")>-1?f(a,b.match(/opacity:\s*(\d?\.?\d*)/)[1]):a;for(var d in b)if("opacity"===d)f(a,b[d]);else{var e="float"===d||"cssFloat"===d?"undefined"==typeof c.styleFloat?"cssFloat":"styleFloat":d;c[e]=b[d]}return a}var b=fabric.document.createElement("div"),c="string"==typeof b.style.opacity,d="string"==typeof b.style.filter,e=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,f=function(a){return a};c?f=function(a,b){return a.style.opacity=b,a}:d&&(f=function(a,b){var c=a.style;return a.currentStyle&&!a.currentStyle.hasLayout&&(c.zoom=1),e.test(c.filter)?(b=b>=.9999?"":"alpha(opacity="+100*b+")",c.filter=c.filter.replace(e,b)):c.filter+=" alpha(opacity="+100*b+")",a}),fabric.util.setStyle=a}(),function(){function a(a){return"string"==typeof a?fabric.document.getElementById(a):a}function b(a,b){var c=fabric.document.createElement(a);for(var d in b)"class"===d?c.className=b[d]:"for"===d?c.htmlFor=b[d]:c.setAttribute(d,b[d]);return c}function c(a,b){a&&-1===(" "+a.className+" ").indexOf(" "+b+" ")&&(a.className+=(a.className?" ":"")+b)}function d(a,c,d){return"string"==typeof c&&(c=b(c,d)),a.parentNode&&a.parentNode.replaceChild(c,a),c.appendChild(a),c}function e(a,b){var c,d,e=0,f=0,g=fabric.document.documentElement,h=fabric.document.body||{scrollLeft:0,scrollTop:0};for(d=a;a&&a.parentNode&&!c;)a=a.parentNode,a!==fabric.document&&"fixed"===fabric.util.getElementStyle(a,"position")&&(c=a),a!==fabric.document&&d!==b&&"absolute"===fabric.util.getElementStyle(a,"position")?(e=0,f=0):a===fabric.document?(e=h.scrollLeft||g.scrollLeft||0,f=h.scrollTop||g.scrollTop||0):(e+=a.scrollLeft||0,f+=a.scrollTop||0);return{left:e,top:f}}function f(a){var b,c,d=a&&a.ownerDocument,e={left:0,top:0},f={left:0,top:0},g={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!d)return{left:0,top:0};for(var h in g)f[g[h]]+=parseInt(k(a,h),10)||0;return b=d.documentElement,"undefined"!=typeof a.getBoundingClientRect&&(e=a.getBoundingClientRect()),c=fabric.util.getScrollLeftTop(a,null),{left:e.left+c.left-(b.clientLeft||0)+f.left,top:e.top+c.top-(b.clientTop||0)+f.top}}var g,h=Array.prototype.slice,i=function(a){return h.call(a,0)};try{g=i(fabric.document.childNodes)instanceof Array}catch(j){}g||(i=function(a){for(var b=new Array(a.length),c=a.length;c--;)b[c]=a[c];return b});var k;k=fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?function(a,b){return fabric.document.defaultView.getComputedStyle(a,null)[b]}:function(a,b){var c=a.style[b];return!c&&a.currentStyle&&(c=a.currentStyle[b]),c},function(){function a(a){return"undefined"!=typeof a.onselectstart&&(a.onselectstart=fabric.util.falseFunction),d?a.style[d]="none":"string"==typeof a.unselectable&&(a.unselectable="on"),a}function b(a){return"undefined"!=typeof a.onselectstart&&(a.onselectstart=null),d?a.style[d]="":"string"==typeof a.unselectable&&(a.unselectable=""),a}var c=fabric.document.documentElement.style,d="userSelect"in c?"userSelect":"MozUserSelect"in c?"MozUserSelect":"WebkitUserSelect"in c?"WebkitUserSelect":"KhtmlUserSelect"in c?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=a,fabric.util.makeElementSelectable=b}(),function(){function a(a,b){var c=fabric.document.getElementsByTagName("head")[0],d=fabric.document.createElement("script"),e=!0;d.onload=d.onreadystatechange=function(a){if(e){if("string"==typeof this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)return;e=!1,b(a||fabric.window.event),d=d.onload=d.onreadystatechange=null}},d.src=a,c.appendChild(d)}fabric.util.getScript=a}(),fabric.util.getById=a,fabric.util.toArray=i,fabric.util.makeElement=b,fabric.util.addClass=c,fabric.util.wrapElement=d,fabric.util.getScrollLeftTop=e,fabric.util.getElementOffset=f,fabric.util.getElementStyle=k}(),function(){function a(a,b){return a+(/\?/.test(a)?"&":"?")+b}function b(){}function c(c,e){e||(e={});var f,g=e.method?e.method.toUpperCase():"GET",h=e.onComplete||function(){},i=d();return i.onreadystatechange=function(){4===i.readyState&&(h(i),i.onreadystatechange=b)},"GET"===g&&(f=null,"string"==typeof e.parameters&&(c=a(c,e.parameters))),i.open(g,c,!0),("POST"===g||"PUT"===g)&&i.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),i.send(f),i}var d=function(){for(var a=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}],b=a.length;b--;)try{var c=a[b]();if(c)return a[b]}catch(d){}}();fabric.util.request=c}(),fabric.log=function(){},fabric.warn=function(){},"undefined"!=typeof console&&["log","warn"].forEach(function(a){"undefined"!=typeof console[a]&&console[a].apply&&(fabric[a]=function(){return console[a].apply(console,arguments)})}),function(a){"use strict";function b(a){return a in w?w[a]:a}function c(a,b,c){var d,e="[object Array]"===Object.prototype.toString.call(b);return"fill"!==a&&"stroke"!==a||"none"!==b?"fillRule"===a?b="evenodd"===b?"destination-over":b:"strokeDashArray"===a?b=b.replace(/,/g," ").split(/\s+/).map(function(a){return parseInt(a)}):"transformMatrix"===a?b=c&&c.transformMatrix?v(c.transformMatrix,p.parseTransformAttribute(b)):p.parseTransformAttribute(b):"visible"===a?(b="none"===b||"hidden"===b?!1:!0,c&&c.visible===!1&&(b=!1)):"originX"===a?b="start"===b?"left":"end"===b?"right":"center":d=e?b.map(u):u(b):b="",!e&&isNaN(d)?b:d}function d(a){for(var b in x)if(a[b]&&"undefined"!=typeof a[x[b]]&&0!==a[b].indexOf("url(")){var c=new p.Color(a[b]);a[b]=c.setAlpha(t(c.getAlpha()*a[x[b]],2)).toRgba()}return a}function e(a,b){var c=a.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);if(c){var d=c[1],e=c[3],f=c[4],g=c[5],h=c[6];d&&(b.fontStyle=d),e&&(b.fontWeight=isNaN(parseFloat(e))?e:parseFloat(e)),f&&(b.fontSize=parseFloat(f)),h&&(b.fontFamily=h),g&&(b.lineHeight="normal"===g?1:g)}}function f(a,d){var f,g;a.replace(/;$/,"").split(";").forEach(function(a){var h=a.split(":");f=b(h[0].trim().toLowerCase()),g=c(f,h[1].trim()),"font"===f?e(g,d):d[f]=g})}function g(a,d){var f,g;for(var h in a)"undefined"!=typeof a[h]&&(f=b(h.toLowerCase()),g=c(f,a[h]),"font"===f?e(g,d):d[f]=g)}function h(a){var b={};for(var c in p.cssRules)if(i(a,c.split(" ")))for(var d in p.cssRules[c])b[d]=p.cssRules[c][d];return b}function i(a,b){var c,d=!0;return c=k(a,b.pop()),c&&b.length&&(d=j(a,b)),c&&d&&0===b.length}function j(a,b){for(var c,d=!0;a.parentNode&&1===a.parentNode.nodeType&&b.length;)d&&(c=b.pop()),a=a.parentNode,d=k(a,c);return 0===b.length}function k(a,b){var c,d=a.nodeName,e=a.getAttribute("class"),f=a.getAttribute("id");if(c=new RegExp("^"+d,"i"),b=b.replace(c,""),f&&b.length&&(c=new RegExp("#"+f+"(?![a-zA-Z\\-]+)","i"),b=b.replace(c,"")),e&&b.length){e=e.split(" ");for(var g=e.length;g--;)c=new RegExp("\\."+e[g]+"(?![a-zA-Z\\-]+)","i"),b=b.replace(c,"")}return 0===b.length}function l(a){for(var b=a.getElementsByTagName("use");b.length;){for(var c,d=b[0],e=d.getAttribute("xlink:href").substr(1),f=d.getAttribute("x")||0,g=d.getAttribute("y")||0,h=a.getElementById(e).cloneNode(!0),i=(d.getAttribute("transform")||"")+" translate("+f+", "+g+")",j=0,k=d.attributes,l=k.length;l>j;j++){var m=k.item(j);"x"!==m.nodeName&&"y"!==m.nodeName&&"xlink:href"!==m.nodeName&&("transform"===m.nodeName?i=i+" "+m.nodeValue:h.setAttribute(m.nodeName,m.nodeValue))}h.setAttribute("transform",i),h.removeAttribute("id"),c=d.parentNode,c.replaceChild(h,d)}}function m(a,b){if(b[3]=b[0]=b[0]>b[3]?b[3]:b[0],1!==b[0]||1!==b[3]||0!==b[4]||0!==b[5]){for(var c=a.ownerDocument.createElement("g");null!=a.firstChild;)c.appendChild(a.firstChild);c.setAttribute("transform","matrix("+b[0]+" "+b[1]+" "+b[2]+" "+b[3]+" "+b[4]+" "+b[5]+")"),a.appendChild(c)}}function n(a){var b=a.objects,c=a.options;return b=b.map(function(a){return p[r(a.type)].fromObject(a)}),{objects:b,options:c}}function o(a,b,c){b[c]&&b[c].toSVG&&a.push('','')}var p=a.fabric||(a.fabric={}),q=p.util.object.extend,r=p.util.string.capitalize,s=p.util.object.clone,t=p.util.toFixed,u=p.util.parseUnit,v=p.util.multiplyTransformMatrices,w={cx:"left",x:"left",r:"radius",cy:"top",y:"top",display:"visible",visibility:"visible",transform:"transformMatrix","fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"originX"},x={stroke:"strokeOpacity",fill:"fillOpacity"};p.parseTransformAttribute=function(){function a(a,b){var c=b[0];a[0]=Math.cos(c),a[1]=Math.sin(c),a[2]=-Math.sin(c),a[3]=Math.cos(c)}function b(a,b){var c=b[0],d=2===b.length?b[1]:b[0];a[0]=c,a[3]=d}function c(a,b){a[2]=b[0]}function d(a,b){a[1]=b[0]}function e(a,b){a[4]=b[0],2===b.length&&(a[5]=b[1])}var f=[1,0,0,1,0,0],g="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",h="(?:\\s+,?\\s*|,\\s*)",i="(?:(skewX)\\s*\\(\\s*("+g+")\\s*\\))",j="(?:(skewY)\\s*\\(\\s*("+g+")\\s*\\))",k="(?:(rotate)\\s*\\(\\s*("+g+")(?:"+h+"("+g+")"+h+"("+g+"))?\\s*\\))",l="(?:(scale)\\s*\\(\\s*("+g+")(?:"+h+"("+g+"))?\\s*\\))",m="(?:(translate)\\s*\\(\\s*("+g+")(?:"+h+"("+g+"))?\\s*\\))",n="(?:(matrix)\\s*\\(\\s*("+g+")"+h+"("+g+")"+h+"("+g+")"+h+"("+g+")"+h+"("+g+")"+h+"("+g+")\\s*\\))",o="(?:"+n+"|"+m+"|"+l+"|"+k+"|"+i+"|"+j+")",q="(?:"+o+"(?:"+h+o+")*)",r="^\\s*(?:"+q+"?)\\s*$",s=new RegExp(r),t=new RegExp(o,"g");return function(g){var h=f.concat(),i=[];if(!g||g&&!s.test(g))return h;g.replace(t,function(g){var j=new RegExp(o).exec(g).filter(function(a){return""!==a&&null!=a}),k=j[1],l=j.slice(2).map(parseFloat);switch(k){case"translate":e(h,l);break;case"rotate":l[0]=p.util.degreesToRadians(l[0]),a(h,l);break;case"scale":b(h,l);break;case"skewX":c(h,l);break;case"skewY":d(h,l);break;case"matrix":h=l}i.push(h.concat()),h=f.concat()});for(var j=i[0];i.length>1;)i.shift(),j=p.util.multiplyTransformMatrices(j,i[0]);return j}}(),p.parseSVGDocument=function(){function a(a,b){for(;a&&(a=a.parentNode);)if(b.test(a.nodeName))return!0;return!1}var b=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,c="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",d=new RegExp("^\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*$");return function(c,e,f){if(c){var g=new Date;l(c);var h,i,j=c.getAttribute("viewBox"),k=u(c.getAttribute("width")||"100%"),n=u(c.getAttribute("height")||"100%");if(j&&(j=j.match(d))){var o=parseFloat(j[1]),q=parseFloat(j[2]),r=1,t=1;h=parseFloat(j[3]),i=parseFloat(j[4]),k&&k!==h&&(r=k/h),n&&n!==i&&(t=n/i),m(c,[r,0,0,t,r*-o,t*-q])}var v=p.util.toArray(c.getElementsByTagName("*"));if(0===v.length&&p.isLikelyNode){v=c.selectNodes('//*[name(.)!="svg"]');for(var w=[],x=0,y=v.length;y>x;x++)w[x]=v[x];v=w}var z=v.filter(function(c){return b.test(c.tagName)&&!a(c,/^(?:pattern|defs)$/)});if(!z||z&&!z.length)return void(e&&e([],{}));var A={width:k?k:h,height:n?n:i,widthAttr:k,heightAttr:n};p.gradientDefs=p.getGradientDefs(c),p.cssRules=p.getCSSRules(c),p.parseElements(z,function(a){p.documentParsingTime=new Date-g,e&&e(a,A)},s(A),f)}}}();var y={has:function(a,b){b(!1)},get:function(){},set:function(){}};q(p,{getGradientDefs:function(a){var b,c,d,e,f=a.getElementsByTagName("linearGradient"),g=a.getElementsByTagName("radialGradient"),h=0,i=[],j={},k={};for(i.length=f.length+g.length,c=f.length;c--;)i[h++]=f[c];for(c=g.length;c--;)i[h++]=g[c];for(;h--;)b=i[h],e=b.getAttribute("xlink:href"),d=b.getAttribute("id"),e&&(k[d]=e.substr(1)),j[d]=b;for(d in k){var l=j[k[d]].cloneNode(!0);for(b=j[d];l.firstChild;)b.appendChild(l.firstChild)}return j},parseAttributes:function(a,e){if(a){var f,g={};a.parentNode&&/^symbol|[g|a]$/i.test(a.parentNode.nodeName)&&(g=p.parseAttributes(a.parentNode,e));var i=e.reduce(function(d,e){return f=a.getAttribute(e),f&&(e=b(e),f=c(e,f,g),d[e]=f),d},{});return i=q(i,q(h(a),p.parseStyleAttribute(a))),d(q(g,i))}},parseElements:function(a,b,c,d){new p.ElementsParser(a,b,c,d).parse()},parseStyleAttribute:function(a){var b={},c=a.getAttribute("style");return c?("string"==typeof c?f(c,b):g(c,b),b):b},parsePointsAttribute:function(a){if(!a)return null;a=a.replace(/,/g," ").trim(),a=a.split(/\s+/);var b,c,d=[];for(b=0,c=a.length;c>b;b+=2)d.push({x:parseFloat(a[b]),y:parseFloat(a[b+1])});return d},getCSSRules:function(a){for(var d,e=a.getElementsByTagName("style"),f={},g=0,h=e.length;h>g;g++){var i=e[0].textContent;i=i.replace(/\/\*[\s\S]*?\*\//g,""),d=i.match(/[^{]*\{[\s\S]*?\}/g),d=d.map(function(a){return a.trim()}),d.forEach(function(a){for(var d=a.match(/([\s\S]*?)\s*\{([^}]*)\}/),e={},g=d[2].trim(),h=g.replace(/;$/,"").split(/\s*;\s*/),i=0,j=h.length;j>i;i++){var k=h[i].split(/\s*:\s*/),l=b(k[0]),m=c(l,k[1],k[0]);e[l]=m}a=d[1],a.split(",").forEach(function(a){f[a.trim()]=p.util.object.clone(e)})})}return f},loadSVGFromURL:function(a,b,c){function d(d){var e=d.responseXML;e&&!e.documentElement&&p.window.ActiveXObject&&d.responseText&&(e=new ActiveXObject("Microsoft.XMLDOM"),e.async="false",e.loadXML(d.responseText.replace(//i,""))),e&&e.documentElement&&p.parseSVGDocument(e.documentElement,function(c,d){y.set(a,{objects:p.util.array.invoke(c,"toObject"),options:d}),b(c,d)},c)}a=a.replace(/^\n\s*/,"").trim(),y.has(a,function(c){c?y.get(a,function(a){var c=n(a);b(c.objects,c.options)}):new p.util.request(a,{method:"get",onComplete:d})})},loadSVGFromString:function(a,b,c){a=a.trim();var d;if("undefined"!=typeof DOMParser){var e=new DOMParser;e&&e.parseFromString&&(d=e.parseFromString(a,"text/xml"))}else p.window.ActiveXObject&&(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(a.replace(//i,"")));p.parseSVGDocument(d.documentElement,function(a,c){b(a,c)},c)},createSVGFontFacesMarkup:function(a){for(var b="",c=0,d=a.length;d>c;c++)"text"===a[c].type&&a[c].path&&(b+=["@font-face {","font-family: ",a[c].fontFamily,"; ","src: url('",a[c].path,"')","}"].join(""));return b&&(b=['"].join("")),b},createSVGRefElementsMarkup:function(a){var b=[];return o(b,a,"backgroundColor"),o(b,a,"overlayColor"),b.join("")}})}("undefined"!=typeof exports?exports:this),fabric.ElementsParser=function(a,b,c,d){this.elements=a,this.callback=b,this.options=c,this.reviver=d},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var a=0,b=this.elements.length;b>a;a++)!function(a,b){setTimeout(function(){a.createObject(a.elements[b],b)},0)}(this,a)},fabric.ElementsParser.prototype.createObject=function(a,b){var c=fabric[fabric.util.string.capitalize(a.tagName)];if(c&&c.fromElement)try{this._createObject(c,a,b)}catch(d){fabric.log(d)}else this.checkIfDone()},fabric.ElementsParser.prototype._createObject=function(a,b,c){if(a.async)a.fromElement(b,this.createCallback(c,b),this.options);else{var d=a.fromElement(b,this.options);this.resolveGradient(d,"fill"),this.resolveGradient(d,"stroke"),this.reviver&&this.reviver(b,d),this.instances[c]=d,this.checkIfDone()}},fabric.ElementsParser.prototype.createCallback=function(a,b){var c=this;return function(d){c.resolveGradient(d,"fill"),c.resolveGradient(d,"stroke"),c.reviver&&c.reviver(b,d),c.instances[a]=d,c.checkIfDone()}},fabric.ElementsParser.prototype.resolveGradient=function(a,b){var c=a.get(b);if(/^url\(/.test(c)){var d=c.slice(5,c.length-1);fabric.gradientDefs[d]&&a.set(b,fabric.Gradient.fromElement(fabric.gradientDefs[d],a))}},fabric.ElementsParser.prototype.checkIfDone=function(){0===--this.numElements&&(this.instances=this.instances.filter(function(a){return null!=a}),this.callback(this.instances))},function(a){"use strict";function b(a,b){this.x=a,this.y=b}var c=a.fabric||(a.fabric={});return c.Point?void c.warn("fabric.Point is already defined"):(c.Point=b,void(b.prototype={constructor:b,add:function(a){return new b(this.x+a.x,this.y+a.y)},addEquals:function(a){return this.x+=a.x,this.y+=a.y,this},scalarAdd:function(a){return new b(this.x+a,this.y+a)},scalarAddEquals:function(a){return this.x+=a,this.y+=a,this},subtract:function(a){return new b(this.x-a.x,this.y-a.y)},subtractEquals:function(a){return this.x-=a.x,this.y-=a.y,this},scalarSubtract:function(a){return new b(this.x-a,this.y-a)},scalarSubtractEquals:function(a){return this.x-=a,this.y-=a,this},multiply:function(a){return new b(this.x*a,this.y*a)},multiplyEquals:function(a){return this.x*=a,this.y*=a,this},divide:function(a){return new b(this.x/a,this.y/a)},divideEquals:function(a){return this.x/=a,this.y/=a,this},eq:function(a){return this.x===a.x&&this.y===a.y},lt:function(a){return this.xa.x&&this.y>a.y},gte:function(a){return this.x>=a.x&&this.y>=a.y},lerp:function(a,c){return new b(this.x+(a.x-this.x)*c,this.y+(a.y-this.y)*c)},distanceFrom:function(a){var b=this.x-a.x,c=this.y-a.y;return Math.sqrt(b*b+c*c)},midPointFrom:function(a){return new b(this.x+(a.x-this.x)/2,this.y+(a.y-this.y)/2)},min:function(a){return new b(Math.min(this.x,a.x),Math.min(this.y,a.y))},max:function(a){return new b(Math.max(this.x,a.x),Math.max(this.y,a.y))},toString:function(){return this.x+","+this.y},setXY:function(a,b){this.x=a,this.y=b},setFromPoint:function(a){this.x=a.x,this.y=a.y},swap:function(a){var b=this.x,c=this.y;this.x=a.x,this.y=a.y,a.x=b,a.y=c}}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){this.status=a,this.points=[]}var c=a.fabric||(a.fabric={});return c.Intersection?void c.warn("fabric.Intersection is already defined"):(c.Intersection=b,c.Intersection.prototype={appendPoint:function(a){this.points.push(a)},appendPoints:function(a){this.points=this.points.concat(a)}},c.Intersection.intersectLineLine=function(a,d,e,f){var g,h=(f.x-e.x)*(a.y-e.y)-(f.y-e.y)*(a.x-e.x),i=(d.x-a.x)*(a.y-e.y)-(d.y-a.y)*(a.x-e.x),j=(f.y-e.y)*(d.x-a.x)-(f.x-e.x)*(d.y-a.y);if(0!==j){var k=h/j,l=i/j;k>=0&&1>=k&&l>=0&&1>=l?(g=new b("Intersection"),g.points.push(new c.Point(a.x+k*(d.x-a.x),a.y+k*(d.y-a.y)))):g=new b}else g=new b(0===h||0===i?"Coincident":"Parallel");return g},c.Intersection.intersectLinePolygon=function(a,c,d){for(var e=new b,f=d.length,g=0;f>g;g++){var h=d[g],i=d[(g+1)%f],j=b.intersectLineLine(a,c,h,i);e.appendPoints(j.points)}return e.points.length>0&&(e.status="Intersection"),e},c.Intersection.intersectPolygonPolygon=function(a,c){for(var d=new b,e=a.length,f=0;e>f;f++){var g=a[f],h=a[(f+1)%e],i=b.intersectLinePolygon(g,h,c);d.appendPoints(i.points)}return d.points.length>0&&(d.status="Intersection"),d},void(c.Intersection.intersectPolygonRectangle=function(a,d,e){var f=d.min(e),g=d.max(e),h=new c.Point(g.x,f.y),i=new c.Point(f.x,g.y),j=b.intersectLinePolygon(f,h,a),k=b.intersectLinePolygon(h,g,a),l=b.intersectLinePolygon(g,i,a),m=b.intersectLinePolygon(i,f,a),n=new b;return n.appendPoints(j.points),n.appendPoints(k.points),n.appendPoints(l.points),n.appendPoints(m.points),n.points.length>0&&(n.status="Intersection"),n}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){a?this._tryParsingColor(a):this.setSource([0,0,0,1])}function c(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a}var d=a.fabric||(a.fabric={});return d.Color?void d.warn("fabric.Color is already defined."):(d.Color=b,d.Color.prototype={_tryParsingColor:function(a){var c;return a in b.colorNameMap&&(a=b.colorNameMap[a]),"transparent"===a?void this.setSource([255,255,255,0]):(c=b.sourceFromHex(a),c||(c=b.sourceFromRgb(a)),c||(c=b.sourceFromHsl(a)),void(c&&this.setSource(c)))},_rgbToHsl:function(a,b,c){a/=255,b/=255,c/=255;var e,f,g,h=d.util.array.max([a,b,c]),i=d.util.array.min([a,b,c]);if(g=(h+i)/2,h===i)e=f=0;else{var j=h-i;switch(f=g>.5?j/(2-h-i):j/(h+i),h){case a:e=(b-c)/j+(c>b?6:0);break;case b:e=(c-a)/j+2;break;case c:e=(a-b)/j+4}e/=6}return[Math.round(360*e),Math.round(100*f),Math.round(100*g)]},getSource:function(){return this._source},setSource:function(a){this._source=a},toRgb:function(){var a=this.getSource();return"rgb("+a[0]+","+a[1]+","+a[2]+")"},toRgba:function(){var a=this.getSource();return"rgba("+a[0]+","+a[1]+","+a[2]+","+a[3]+")"},toHsl:function(){var a=this.getSource(),b=this._rgbToHsl(a[0],a[1],a[2]);return"hsl("+b[0]+","+b[1]+"%,"+b[2]+"%)"},toHsla:function(){var a=this.getSource(),b=this._rgbToHsl(a[0],a[1],a[2]);return"hsla("+b[0]+","+b[1]+"%,"+b[2]+"%,"+a[3]+")"},toHex:function(){var a,b,c,d=this.getSource();return a=d[0].toString(16),a=1===a.length?"0"+a:a,b=d[1].toString(16),b=1===b.length?"0"+b:b,c=d[2].toString(16),c=1===c.length?"0"+c:c,a.toUpperCase()+b.toUpperCase()+c.toUpperCase()},getAlpha:function(){return this.getSource()[3]},setAlpha:function(a){var b=this.getSource();return b[3]=a,this.setSource(b),this},toGrayscale:function(){var a=this.getSource(),b=parseInt((.3*a[0]+.59*a[1]+.11*a[2]).toFixed(0),10),c=a[3];return this.setSource([b,b,b,c]),this},toBlackWhite:function(a){var b=this.getSource(),c=(.3*b[0]+.59*b[1]+.11*b[2]).toFixed(0),d=b[3];return a=a||127,c=Number(c)h;h++)c.push(Math.round(f[h]*(1-e)+g[h]*e));return c[3]=d,this.setSource(c),this}},d.Color.reRGBa=/^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/,d.Color.reHSLa=/^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/,d.Color.reHex=/^#?([0-9a-f]{6}|[0-9a-f]{3})$/i,d.Color.colorNameMap={aqua:"#00FFFF",black:"#000000",blue:"#0000FF",fuchsia:"#FF00FF",gray:"#808080",green:"#008000",lime:"#00FF00",maroon:"#800000",navy:"#000080",olive:"#808000",orange:"#FFA500",purple:"#800080",red:"#FF0000",silver:"#C0C0C0",teal:"#008080",white:"#FFFFFF",yellow:"#FFFF00"},d.Color.fromRgb=function(a){return b.fromSource(b.sourceFromRgb(a))},d.Color.sourceFromRgb=function(a){var c=a.match(b.reRGBa);if(c){var d=parseInt(c[1],10)/(/%$/.test(c[1])?100:1)*(/%$/.test(c[1])?255:1),e=parseInt(c[2],10)/(/%$/.test(c[2])?100:1)*(/%$/.test(c[2])?255:1),f=parseInt(c[3],10)/(/%$/.test(c[3])?100:1)*(/%$/.test(c[3])?255:1);return[parseInt(d,10),parseInt(e,10),parseInt(f,10),c[4]?parseFloat(c[4]):1]}},d.Color.fromRgba=b.fromRgb,d.Color.fromHsl=function(a){return b.fromSource(b.sourceFromHsl(a))},d.Color.sourceFromHsl=function(a){var d=a.match(b.reHSLa);if(d){var e,f,g,h=(parseFloat(d[1])%360+360)%360/360,i=parseFloat(d[2])/(/%$/.test(d[2])?100:1),j=parseFloat(d[3])/(/%$/.test(d[3])?100:1);if(0===i)e=f=g=j;else{var k=.5>=j?j*(i+1):j+i-j*i,l=2*j-k;e=c(l,k,h+1/3),f=c(l,k,h),g=c(l,k,h-1/3)}return[Math.round(255*e),Math.round(255*f),Math.round(255*g),d[4]?parseFloat(d[4]):1]}},d.Color.fromHsla=b.fromHsl,d.Color.fromHex=function(a){return b.fromSource(b.sourceFromHex(a))},d.Color.sourceFromHex=function(a){if(a.match(b.reHex)){var c=a.slice(a.indexOf("#")+1),d=3===c.length,e=d?c.charAt(0)+c.charAt(0):c.substring(0,2),f=d?c.charAt(1)+c.charAt(1):c.substring(2,4),g=d?c.charAt(2)+c.charAt(2):c.substring(4,6);return[parseInt(e,16),parseInt(f,16),parseInt(g,16),1]}},void(d.Color.fromSource=function(a){var c=new b;return c.setSource(a),c}))}("undefined"!=typeof exports?exports:this),function(){function a(a){var b,c,d,e=a.getAttribute("style"),f=a.getAttribute("offset");if(f=parseFloat(f)/(/%$/.test(f)?100:1),f=0>f?0:f>1?1:f,e){var g=e.split(/\s*;\s*/);""===g[g.length-1]&&g.pop();for(var h=g.length;h--;){var i=g[h].split(/\s*:\s*/),j=i[0].trim(),k=i[1].trim();"stop-color"===j?b=k:"stop-opacity"===j&&(d=k)}}return b||(b=a.getAttribute("stop-color")||"rgb(0,0,0)"),d||(d=a.getAttribute("stop-opacity")),b=new fabric.Color(b),c=b.getAlpha(),d=isNaN(parseFloat(d))?1:parseFloat(d),d*=c,{offset:f,color:b.toRgb(),opacity:d}}function b(a){return{x1:a.getAttribute("x1")||0,y1:a.getAttribute("y1")||0,x2:a.getAttribute("x2")||"100%",y2:a.getAttribute("y2")||0}}function c(a){return{x1:a.getAttribute("fx")||a.getAttribute("cx")||"50%",y1:a.getAttribute("fy")||a.getAttribute("cy")||"50%",r1:0,x2:a.getAttribute("cx")||"50%",y2:a.getAttribute("cy")||"50%",r2:a.getAttribute("r")||"50%"}}function d(a,b,c){var d,e=0,f=1,g="";for(var h in b)d=parseFloat(b[h],10),f="string"==typeof b[h]&&/^\d+%$/.test(b[h])?.01:1,"x1"===h||"x2"===h||"r2"===h?(f*="objectBoundingBox"===c?a.width:1,e="objectBoundingBox"===c?a.left||0:0):("y1"===h||"y2"===h)&&(f*="objectBoundingBox"===c?a.height:1,e="objectBoundingBox"===c?a.top||0:0),b[h]=d*f+e;if("ellipse"===a.type&&null!==b.r2&&"objectBoundingBox"===c&&a.rx!==a.ry){var i=a.ry/a.rx;g=" scale(1, "+i+")",b.y1&&(b.y1/=i),b.y2&&(b.y2/=i)}return g}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(a){a||(a={});var b={};this.id=fabric.Object.__uid++,this.type=a.type||"linear",b={x1:a.coords.x1||0,y1:a.coords.y1||0,x2:a.coords.x2||0,y2:a.coords.y2||0},"radial"===this.type&&(b.r1=a.coords.r1||0,b.r2=a.coords.r2||0),this.coords=b,this.colorStops=a.colorStops.slice(),a.gradientTransform&&(this.gradientTransform=a.gradientTransform),this.offsetX=a.offsetX||this.offsetX,this.offsetY=a.offsetY||this.offsetY},addColorStop:function(a){for(var b in a){var c=new fabric.Color(a[b]);this.colorStops.push({offset:b,color:c.toRgb(),opacity:c.getAlpha()})}return this},toObject:function(){return{type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(a){var b,c,d=fabric.util.object.clone(this.coords);if(this.colorStops.sort(function(a,b){return a.offset-b.offset}),!a.group||"path-group"!==a.group.type)for(var e in d)"x1"===e||"x2"===e||"r2"===e?d[e]+=this.offsetX-a.width/2:("y1"===e||"y2"===e)&&(d[e]+=this.offsetY-a.height/2);c='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',this.gradientTransform&&(c+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),"linear"===this.type?b=["\n']:"radial"===this.type&&(b=["\n']);for(var f=0;f\n');return b.push("linear"===this.type?"\n":"\n"),b.join("")},toLive:function(a){var b;if(this.type){"linear"===this.type?b=a.createLinearGradient(this.coords.x1,this.coords.y1,this.coords.x2,this.coords.y2):"radial"===this.type&&(b=a.createRadialGradient(this.coords.x1,this.coords.y1,this.coords.r1,this.coords.x2,this.coords.y2,this.coords.r2));for(var c=0,d=this.colorStops.length;d>c;c++){var e=this.colorStops[c].color,f=this.colorStops[c].opacity,g=this.colorStops[c].offset;"undefined"!=typeof f&&(e=new fabric.Color(e).setAlpha(f).toRgba()),b.addColorStop(parseFloat(g),e)}return b}}}),fabric.util.object.extend(fabric.Gradient,{fromElement:function(e,f){var g,h=e.getElementsByTagName("stop"),i="linearGradient"===e.nodeName?"linear":"radial",j=e.getAttribute("gradientUnits")||"objectBoundingBox",k=e.getAttribute("gradientTransform"),l=[],m={};"linear"===i?m=b(e):"radial"===i&&(m=c(e));for(var n=h.length;n--;)l.push(a(h[n]));g=d(f,m,j);var o=new fabric.Gradient({type:i,coords:m,colorStops:l,offsetX:-f.left,offsetY:-f.top});return(k||""!==g)&&(o.gradientTransform=fabric.parseTransformAttribute((k||"")+g)),o},forObject:function(a,b){return b||(b={}),d(a,b.coords,"userSpaceOnUse"),new fabric.Gradient(b)}})}(),fabric.Pattern=fabric.util.createClass({repeat:"repeat",offsetX:0,offsetY:0,initialize:function(a){if(a||(a={}),this.id=fabric.Object.__uid++,a.source)if("string"==typeof a.source)if("undefined"!=typeof fabric.util.getFunctionBody(a.source))this.source=new Function(fabric.util.getFunctionBody(a.source));else{var b=this;this.source=fabric.util.createImage(),fabric.util.loadImage(a.source,function(a){b.source=a})}else this.source=a.source;a.repeat&&(this.repeat=a.repeat),a.offsetX&&(this.offsetX=a.offsetX),a.offsetY&&(this.offsetY=a.offsetY)},toObject:function(){var a;return"function"==typeof this.source?a=String(this.source):"string"==typeof this.source.src&&(a=this.source.src),{source:a,repeat:this.repeat,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(a){var b="function"==typeof this.source?this.source():this.source,c=b.width/a.getWidth(),d=b.height/a.getHeight(),e="";return b.src?e=b.src:b.toDataURL&&(e=b.toDataURL()),''},toLive:function(a){var b="function"==typeof this.source?this.source():this.source;if(!b)return"";if("undefined"!=typeof b.src){if(!b.complete)return"";if(0===b.naturalWidth||0===b.naturalHeight)return""}return a.createPattern(b,this.repeat)}}),function(a){"use strict";var b=a.fabric||(a.fabric={});return b.Shadow?void b.warn("fabric.Shadow is already defined."):(b.Shadow=b.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(a){"string"==typeof a&&(a=this._parseShadow(a));for(var c in a)this[c]=a[c];this.id=b.Object.__uid++},_parseShadow:function(a){var c=a.trim(),d=b.Shadow.reOffsetsAndBlur.exec(c)||[],e=c.replace(b.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:e.trim(),offsetX:parseInt(d[1],10)||0,offsetY:parseInt(d[2],10)||0,blur:parseInt(d[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(a){var b="SourceAlpha";return!a||a.fill!==this.color&&a.stroke!==this.color||(b="SourceGraphic"),''},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var a={},c=b.Shadow.prototype;return this.color!==c.color&&(a.color=this.color),this.blur!==c.blur&&(a.blur=this.blur),this.offsetX!==c.offsetX&&(a.offsetX=this.offsetX),this.offsetY!==c.offsetY&&(a.offsetY=this.offsetY),a}}),void(b.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/))}("undefined"!=typeof exports?exports:this),function(){"use strict";if(fabric.StaticCanvas)return void fabric.warn("fabric.StaticCanvas is already defined.");var a=fabric.util.object.extend,b=fabric.util.getElementOffset,c=fabric.util.removeFromArray,d=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(a,b){b||(b={}),this._initStatic(a,b),fabric.StaticCanvas.activeInstance=this -},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(a,b){this._objects=[],this._createLowerCanvas(a),this._initOptions(b),this._setImageSmoothing(),b.overlayImage&&this.setOverlayImage(b.overlayImage,this.renderAll.bind(this)),b.backgroundImage&&this.setBackgroundImage(b.backgroundImage,this.renderAll.bind(this)),b.backgroundColor&&this.setBackgroundColor(b.backgroundColor,this.renderAll.bind(this)),b.overlayColor&&this.setOverlayColor(b.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=b(this.lowerCanvasEl),this},setOverlayImage:function(a,b,c){return this.__setBgOverlayImage("overlayImage",a,b,c)},setBackgroundImage:function(a,b,c){return this.__setBgOverlayImage("backgroundImage",a,b,c)},setOverlayColor:function(a,b){return this.__setBgOverlayColor("overlayColor",a,b)},setBackgroundColor:function(a,b){return this.__setBgOverlayColor("backgroundColor",a,b)},_setImageSmoothing:function(){var a=this.getContext();a.imageSmoothingEnabled=this.imageSmoothingEnabled,a.webkitImageSmoothingEnabled=this.imageSmoothingEnabled,a.mozImageSmoothingEnabled=this.imageSmoothingEnabled,a.msImageSmoothingEnabled=this.imageSmoothingEnabled,a.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(a,b,c,d){return"string"==typeof b?fabric.util.loadImage(b,function(b){this[a]=new fabric.Image(b,d),c&&c()},this):(this[a]=b,c&&c()),this},__setBgOverlayColor:function(a,b,c){if(b&&b.source){var d=this;fabric.util.loadImage(b.source,function(e){d[a]=new fabric.Pattern({source:e,repeat:b.repeat,offsetX:b.offsetX,offsetY:b.offsetY}),c&&c()})}else this[a]=b,c&&c();return this},_createCanvasElement:function(){var a=fabric.document.createElement("canvas");if(a.style||(a.style={}),!a)throw d;return this._initCanvasElement(a),a},_initCanvasElement:function(a){if(fabric.util.createCanvasElement(a),"undefined"==typeof a.getContext)throw d},_initOptions:function(a){for(var b in a)this[b]=a[b];this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0,this.lowerCanvasEl.style&&(this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(a){this.lowerCanvasEl=fabric.util.getById(a)||this._createCanvasElement(),this._initCanvasElement(this.lowerCanvasEl),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(a,b){return this.setDimensions({width:a},b)},setHeight:function(a,b){return this.setDimensions({height:a},b)},setDimensions:function(a,b){var c;b=b||{};for(var d in a)c=a[d],b.cssOnly||(this._setBackstoreDimension(d,a[d]),c+="px"),b.backstoreOnly||this._setCssDimension(d,c);return b.cssOnly||this.renderAll(),this.calcOffset(),this},_setBackstoreDimension:function(a,b){return this.lowerCanvasEl[a]=b,this.upperCanvasEl&&(this.upperCanvasEl[a]=b),this.cacheCanvasEl&&(this.cacheCanvasEl[a]=b),this[a]=b,this},_setCssDimension:function(a,b){return this.lowerCanvasEl.style[a]=b,this.upperCanvasEl&&(this.upperCanvasEl.style[a]=b),this.wrapperEl&&(this.wrapperEl.style[a]=b),this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(a){this.viewportTransform=a,this.renderAll();for(var b=0,c=this._objects.length;c>b;b++)this._objects[b].setCoords();return this},zoomToPoint:function(a,b){var c=a;a=fabric.util.transformPoint(a,fabric.util.invertTransform(this.viewportTransform)),this.viewportTransform[0]=b,this.viewportTransform[3]=b;var d=fabric.util.transformPoint(a,this.viewportTransform);this.viewportTransform[4]+=c.x-d.x,this.viewportTransform[5]+=c.y-d.y,this.renderAll();for(var e=0,f=this._objects.length;f>e;e++)this._objects[e].setCoords();return this},setZoom:function(a){return this.zoomToPoint(new fabric.Point(0,0),a),this},absolutePan:function(a){this.viewportTransform[4]=-a.x,this.viewportTransform[5]=-a.y,this.renderAll();for(var b=0,c=this._objects.length;c>b;b++)this._objects[b].setCoords();return this},relativePan:function(a){return this.absolutePan(new fabric.Point(-a.x-this.viewportTransform[4],-a.y-this.viewportTransform[5]))},getElement:function(){return this.lowerCanvasEl},getActiveObject:function(){return null},getActiveGroup:function(){return null},_draw:function(a,b){if(b){a.save();var c=this.viewportTransform;a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),b.render(a),a.restore(),this.controlsAboveOverlay||b._renderControls(a)}},_onObjectAdded:function(a){this.stateful&&a.setupState(),a.canvas=this,a.setCoords(),this.fire("object:added",{target:a}),a.fire("added")},_onObjectRemoved:function(a){this.getActiveObject()===a&&(this.fire("before:selection:cleared",{target:a}),this._discardActiveObject(),this.fire("selection:cleared")),this.fire("object:removed",{target:a}),a.fire("removed")},clearContext:function(a){return a.clearRect(0,0,this.width,this.height),this},getContext:function(){return this.contextContainer},clear:function(){return this._objects.length=0,this.discardActiveGroup&&this.discardActiveGroup(),this.discardActiveObject&&this.discardActiveObject(),this.clearContext(this.contextContainer),this.contextTop&&this.clearContext(this.contextTop),this.fire("canvas:cleared"),this.renderAll(),this},renderAll:function(a){var b=this[a===!0&&this.interactive?"contextTop":"contextContainer"],c=this.getActiveGroup();return this.contextTop&&this.selection&&!this._groupSelector&&this.clearContext(this.contextTop),a||this.clearContext(b),this.fire("before:render"),this.clipTo&&fabric.util.clipContext(this,b),this._renderBackground(b),this._renderObjects(b,c),this._renderActiveGroup(b,c),this.clipTo&&b.restore(),this._renderOverlay(b),this.controlsAboveOverlay&&this.interactive&&this.drawControls(b),this.fire("after:render"),this},_renderObjects:function(a,b){var c,d;if(b)for(c=0,d=this._objects.length;d>c;++c)this._objects[c]&&!b.contains(this._objects[c])&&this._draw(a,this._objects[c]);else for(c=0,d=this._objects.length;d>c;++c)this._draw(a,this._objects[c])},_renderActiveGroup:function(a,b){if(b){var c=[];this.forEachObject(function(a){b.contains(a)&&c.push(a)}),b._set("objects",c),this._draw(a,b)}},_renderBackground:function(a){this.backgroundColor&&(a.fillStyle=this.backgroundColor.toLive?this.backgroundColor.toLive(a):this.backgroundColor,a.fillRect(this.backgroundColor.offsetX||0,this.backgroundColor.offsetY||0,this.width,this.height)),this.backgroundImage&&this._draw(a,this.backgroundImage)},_renderOverlay:function(a){this.overlayColor&&(a.fillStyle=this.overlayColor.toLive?this.overlayColor.toLive(a):this.overlayColor,a.fillRect(this.overlayColor.offsetX||0,this.overlayColor.offsetY||0,this.width,this.height)),this.overlayImage&&this._draw(a,this.overlayImage)},renderTop:function(){var a=this.contextTop||this.contextContainer;this.clearContext(a),this.selection&&this._groupSelector&&this._drawSelection();var b=this.getActiveGroup();return b&&b.render(a),this._renderOverlay(a),this.fire("after:render"),this},getCenter:function(){return{top:this.getHeight()/2,left:this.getWidth()/2}},centerObjectH:function(a){return this._centerObject(a,new fabric.Point(this.getCenter().left,a.getCenterPoint().y)),this.renderAll(),this},centerObjectV:function(a){return this._centerObject(a,new fabric.Point(a.getCenterPoint().x,this.getCenter().top)),this.renderAll(),this},centerObject:function(a){var b=this.getCenter();return this._centerObject(a,new fabric.Point(b.left,b.top)),this.renderAll(),this},_centerObject:function(a,b){return a.setPositionByOrigin(b,"center","center"),this},toDatalessJSON:function(a){return this.toDatalessObject(a)},toObject:function(a){return this._toObjectMethod("toObject",a)},toDatalessObject:function(a){return this._toObjectMethod("toDatalessObject",a)},_toObjectMethod:function(b,c){var d=this.getActiveGroup();d&&this.discardActiveGroup();var e={objects:this._toObjects(b,c)};return a(e,this.__serializeBgOverlay()),fabric.util.populateWithProperties(this,e,c),d&&(this.setActiveGroup(new fabric.Group(d.getObjects(),{originX:"center",originY:"center"})),d.forEachObject(function(a){a.set("active",!0)}),this._currentTransform&&(this._currentTransform.target=this.getActiveGroup())),e},_toObjects:function(a,b){return this.getObjects().map(function(c){return this._toObject(c,a,b)},this)},_toObject:function(a,b,c){var d;this.includeDefaultValues||(d=a.includeDefaultValues,a.includeDefaultValues=!1);var e=a[b](c);return this.includeDefaultValues||(a.includeDefaultValues=d),e},__serializeBgOverlay:function(){var a={background:this.backgroundColor&&this.backgroundColor.toObject?this.backgroundColor.toObject():this.backgroundColor};return this.overlayColor&&(a.overlay=this.overlayColor.toObject?this.overlayColor.toObject():this.overlayColor),this.backgroundImage&&(a.backgroundImage=this.backgroundImage.toObject()),this.overlayImage&&(a.overlayImage=this.overlayImage.toObject()),a},svgViewportTransformation:!0,toSVG:function(a,b){a||(a={});var c=[];return this._setSVGPreamble(c,a),this._setSVGHeader(c,a),this._setSVGBgOverlayColor(c,"backgroundColor"),this._setSVGBgOverlayImage(c,"backgroundImage"),this._setSVGObjects(c,b),this._setSVGBgOverlayColor(c,"overlayColor"),this._setSVGBgOverlayImage(c,"overlayImage"),c.push(""),c.join("")},_setSVGPreamble:function(a,b){b.suppressPreamble||a.push('','\n')},_setSVGHeader:function(a,b){var c,d,e;b.viewBox?(c=b.viewBox.width,d=b.viewBox.height):(c=this.width,d=this.height,this.svgViewportTransformation||(e=this.viewportTransform,c/=e[0],d/=e[3])),a.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(a,b){var c=this.getActiveGroup();c&&this.discardActiveGroup();for(var d=0,e=this.getObjects(),f=e.length;f>d;d++)a.push(e[d].toSVG(b));c&&(this.setActiveGroup(new fabric.Group(c.getObjects())),c.forEachObject(function(a){a.set("active",!0)}))},_setSVGBgOverlayImage:function(a,b){this[b]&&this[b].toSVG&&a.push(this[b].toSVG())},_setSVGBgOverlayColor:function(a,b){this[b]&&this[b].source?a.push('"):this[b]&&"overlayColor"===b&&a.push('")},sendToBack:function(a){return c(this._objects,a),this._objects.unshift(a),this.renderAll&&this.renderAll()},bringToFront:function(a){return c(this._objects,a),this._objects.push(a),this.renderAll&&this.renderAll()},sendBackwards:function(a,b){var d=this._objects.indexOf(a);if(0!==d){var e=this._findNewLowerIndex(a,d,b);c(this._objects,a),this._objects.splice(e,0,a),this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(a,b,c){var d;if(c){d=b;for(var e=b-1;e>=0;--e){var f=a.intersectsWithObject(this._objects[e])||a.isContainedWithinObject(this._objects[e])||this._objects[e].isContainedWithinObject(a);if(f){d=e;break}}}else d=b-1;return d},bringForward:function(a,b){var d=this._objects.indexOf(a);if(d!==this._objects.length-1){var e=this._findNewUpperIndex(a,d,b);c(this._objects,a),this._objects.splice(e,0,a),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(a,b,c){var d;if(c){d=b;for(var e=b+1;e"}}),a(fabric.StaticCanvas.prototype,fabric.Observable),a(fabric.StaticCanvas.prototype,fabric.Collection),a(fabric.StaticCanvas.prototype,fabric.DataURLExporter),a(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(a){var b=fabric.util.createCanvasElement();if(!b||!b.getContext)return null;var c=b.getContext("2d");if(!c)return null;switch(a){case"getImageData":return"undefined"!=typeof c.getImageData;case"setLineDash":return"undefined"!=typeof c.setLineDash;case"toDataURL":return"undefined"!=typeof b.toDataURL;case"toDataURLWithQuality":try{return b.toDataURL("image/jpeg",0),!0}catch(d){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(a){return this.shadow=new fabric.Shadow(a),this},_setBrushStyles:function(){var a=this.canvas.contextTop;a.strokeStyle=this.color,a.lineWidth=this.width,a.lineCap=this.strokeLineCap,a.lineJoin=this.strokeLineJoin},_setShadow:function(){if(this.shadow){var a=this.canvas.contextTop;a.shadowColor=this.shadow.color,a.shadowBlur=this.shadow.blur,a.shadowOffsetX=this.shadow.offsetX,a.shadowOffsetY=this.shadow.offsetY}},_resetShadow:function(){var a=this.canvas.contextTop;a.shadowColor="",a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0}}),function(){var a=fabric.util.array.min,b=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(a){this.canvas=a,this._points=[]},onMouseDown:function(a){this._prepareForDrawing(a),this._captureDrawingPath(a),this._render()},onMouseMove:function(a){this._captureDrawingPath(a),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(a){var b=new fabric.Point(a.x,a.y);this._reset(),this._addPoint(b),this.canvas.contextTop.moveTo(b.x,b.y)},_addPoint:function(a){this._points.push(a)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(a){var b=new fabric.Point(a.x,a.y);this._addPoint(b)},_render:function(){var a=this.canvas.contextTop,b=this.canvas.viewportTransform,c=this._points[0],d=this._points[1];a.save(),a.transform(b[0],b[1],b[2],b[3],b[4],b[5]),a.beginPath(),2===this._points.length&&c.x===d.x&&c.y===d.y&&(c.x-=.5,d.x+=.5),a.moveTo(c.x,c.y);for(var e=1,f=this._points.length;f>e;e++){var g=c.midPointFrom(d);a.quadraticCurveTo(c.x,c.y,g.x,g.y),c=this._points[e],d=this._points[e+1]}a.lineTo(c.x,c.y),a.stroke(),a.restore()},_getSVGPathData:function(){return this.box=this.getPathBoundingBox(this._points),this.convertPointsToSVGPath(this._points,this.box.minX,this.box.minY)},getPathBoundingBox:function(c){for(var d=[],e=[],f=c[0],g=c[1],h=f,i=1,j=c.length;j>i;i++){var k=f.midPointFrom(g);d.push(h.x),d.push(k.x),e.push(h.y),e.push(k.y),f=c[i],g=c[i+1],h=k}return d.push(f.x),e.push(f.y),{minX:a(d),minY:a(e),maxX:b(d),maxY:b(e)}},convertPointsToSVGPath:function(a,b,c){var d=[],e=new fabric.Point(a[0].x-b,a[0].y-c),f=new fabric.Point(a[1].x-b,a[1].y-c);d.push("M ",a[0].x-b," ",a[0].y-c," ");for(var g=1,h=a.length;h>g;g++){var i=e.midPointFrom(f);d.push("Q ",e.x," ",e.y," ",i.x," ",i.y," "),e=new fabric.Point(a[g].x-b,a[g].y-c),g+1c;c++){var e=this.points[c],f=new fabric.Circle({radius:e.radius,left:e.x,top:e.y,originX:"center",originY:"center",fill:e.fill});this.shadow&&f.setShadow(this.shadow),b.push(f)}var g=new fabric.Group(b,{originX:"center",originY:"center"});g.canvas=this.canvas,this.canvas.add(g),this.canvas.fire("path:created",{path:g}),this.canvas.clearContext(this.canvas.contextTop),this._resetShadow(),this.canvas.renderOnAddRemove=a,this.canvas.renderAll()},addPoint:function(a){var b=new fabric.Point(a.x,a.y),c=fabric.util.getRandomInt(Math.max(0,this.width-20),this.width+20)/2,d=new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0,100)/100).toRgba();return b.radius=c,b.fill=d,this.points.push(b),b}}),fabric.SprayBrush=fabric.util.createClass(fabric.BaseBrush,{width:10,density:20,dotWidth:1,dotWidthVariance:1,randomOpacity:!1,optimizeOverlapping:!0,initialize:function(a){this.canvas=a,this.sprayChunks=[]},onMouseDown:function(a){this.sprayChunks.length=0,this.canvas.clearContext(this.canvas.contextTop),this._setShadow(),this.addSprayChunk(a),this.render()},onMouseMove:function(a){this.addSprayChunk(a),this.render()},onMouseUp:function(){var a=this.canvas.renderOnAddRemove;this.canvas.renderOnAddRemove=!1;for(var b=[],c=0,d=this.sprayChunks.length;d>c;c++)for(var e=this.sprayChunks[c],f=0,g=e.length;g>f;f++){var h=new fabric.Rect({width:e[f].width,height:e[f].width,left:e[f].x+1,top:e[f].y+1,originX:"center",originY:"center",fill:this.color});this.shadow&&h.setShadow(this.shadow),b.push(h)}this.optimizeOverlapping&&(b=this._getOptimizedRects(b));var i=new fabric.Group(b,{originX:"center",originY:"center"});i.canvas=this.canvas,this.canvas.add(i),this.canvas.fire("path:created",{path:i}),this.canvas.clearContext(this.canvas.contextTop),this._resetShadow(),this.canvas.renderOnAddRemove=a,this.canvas.renderAll()},_getOptimizedRects:function(a){for(var b,c={},d=0,e=a.length;e>d;d++)b=a[d].left+""+a[d].top,c[b]||(c[b]=a[d]);var f=[];for(b in c)f.push(c[b]);return f},render:function(){var a=this.canvas.contextTop;a.fillStyle=this.color;var b=this.canvas.viewportTransform;a.save(),a.transform(b[0],b[1],b[2],b[3],b[4],b[5]);for(var c=0,d=this.sprayChunkPoints.length;d>c;c++){var e=this.sprayChunkPoints[c];"undefined"!=typeof e.opacity&&(a.globalAlpha=e.opacity),a.fillRect(e.x,e.y,e.width,e.width)}a.restore()},addSprayChunk:function(a){this.sprayChunkPoints=[];for(var b,c,d,e=this.width/2,f=0;f1&&this.setWidth(g).setHeight(h),k.scale(d,d),c.left&&(c.left*=d),c.top&&(c.top*=d),c.width?c.width*=d:1>d&&(c.width=g),c.height?c.height*=d:1>d&&(c.height=h),j?this._tempRemoveBordersControlsFromGroup(j):i&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var l=this.__toDataURL(a,b,c);return this.width=e,this.height=f,k.scale(1/d,1/d),this.setWidth(e).setHeight(f),j?this._restoreBordersControlsOnGroup(j):i&&this.setActiveObject&&this.setActiveObject(i),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),l},toDataURLWithMultiplier:function(a,b,c){return this.toDataURL({format:a,multiplier:b,quality:c})},_tempRemoveBordersControlsFromGroup:function(a){a.origHasControls=a.hasControls,a.origBorderColor=a.borderColor,a.hasControls=!0,a.borderColor="rgba(0,0,0,0)",a.forEachObject(function(a){a.origBorderColor=a.borderColor,a.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(a){a.hideControls=a.origHideControls,a.borderColor=a.origBorderColor,a.forEachObject(function(a){a.borderColor=a.origBorderColor,delete a.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(a,b,c){return this.loadFromJSON(a,b,c)},loadFromJSON:function(a,b,c){if(a){var d="string"==typeof a?JSON.parse(a):a;this.clear();var e=this;return this._enlivenObjects(d.objects,function(){e._setBgOverlay(d,b)},c),this}},_setBgOverlay:function(a,b){var c=this,d={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!(a.backgroundImage||a.overlayImage||a.background||a.overlay))return void(b&&b());var e=function(){d.backgroundImage&&d.overlayImage&&d.backgroundColor&&d.overlayColor&&(c.renderAll(),b&&b())};this.__setBgOverlay("backgroundImage",a.backgroundImage,d,e),this.__setBgOverlay("overlayImage",a.overlayImage,d,e),this.__setBgOverlay("backgroundColor",a.background,d,e),this.__setBgOverlay("overlayColor",a.overlay,d,e),e()},__setBgOverlay:function(a,b,c,d){var e=this;return b?void("backgroundImage"===a||"overlayImage"===a?fabric.Image.fromObject(b,function(b){e[a]=b,c[a]=!0,d&&d()}):this["set"+fabric.util.string.capitalize(a,!0)](b,function(){c[a]=!0,d&&d()})):void(c[a]=!0)},_enlivenObjects:function(a,b,c){var d=this;if(!a||0===a.length)return void(b&&b());var e=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(a,function(a){a.forEach(function(a,b){d.insertAt(a,b,!0)}),d.renderOnAddRemove=e,b&&b()},null,c)},_toDataURL:function(a,b){this.clone(function(c){b(c.toDataURL(a))})},_toDataURLWithMultiplier:function(a,b,c){this.clone(function(d){c(d.toDataURLWithMultiplier(a,b))})},clone:function(a,b){var c=JSON.stringify(this.toJSON(b));this.cloneWithoutData(function(b){b.loadFromJSON(c,function(){a&&a(b)})})},cloneWithoutData:function(a){var b=fabric.document.createElement("canvas");b.width=this.getWidth(),b.height=this.getHeight();var c=new fabric.Canvas(b);c.clipTo=this.clipTo,this.backgroundImage?(c.setBackgroundImage(this.backgroundImage.src,function(){c.renderAll(),a&&a(c)}),c.backgroundImageOpacity=this.backgroundImageOpacity,c.backgroundImageStretch=this.backgroundImageStretch):a&&a(c)}}),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.toFixed,e=b.util.string.capitalize,f=b.util.degreesToRadians,g=b.StaticCanvas.supports("setLineDash");b.Object||(b.Object=b.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,lockScalingFlip:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule shadow clipTo visible backgroundColor".split(" "),initialize:function(a){a&&this.setOptions(a)},_initGradient:function(a){!a.fill||!a.fill.colorStops||a.fill instanceof b.Gradient||this.set("fill",new b.Gradient(a.fill))},_initPattern:function(a){!a.fill||!a.fill.source||a.fill instanceof b.Pattern||this.set("fill",new b.Pattern(a.fill)),!a.stroke||!a.stroke.source||a.stroke instanceof b.Pattern||this.set("stroke",new b.Pattern(a.stroke))},_initClipping:function(a){if(a.clipTo&&"string"==typeof a.clipTo){var c=b.util.getFunctionBody(a.clipTo);"undefined"!=typeof c&&(this.clipTo=new Function("ctx",c))}},setOptions:function(a){for(var b in a)this.set(b,a[b]);this._initGradient(a),this._initPattern(a),this._initClipping(a)},transform:function(a,b){this.group&&this.group.transform(a,b),a.globalAlpha=this.opacity;var c=b?this._getLeftTopCoords():this.getCenterPoint();a.translate(c.x,c.y),a.rotate(f(this.angle)),a.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(a){var c=b.Object.NUM_FRACTION_DIGITS,e={type:this.type,originX:this.originX,originY:this.originY,left:d(this.left,c),top:d(this.top,c),width:d(this.width,c),height:d(this.height,c),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:d(this.strokeWidth,c),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:d(this.strokeMiterLimit,c),scaleX:d(this.scaleX,c),scaleY:d(this.scaleY,c),angle:d(this.getAngle(),c),flipX:this.flipX,flipY:this.flipY,opacity:d(this.opacity,c),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};return this.includeDefaultValues||(e=this._removeDefaultValues(e)),b.util.populateWithProperties(this,e,a),e},toDatalessObject:function(a){return this.toObject(a)},_removeDefaultValues:function(a){var c=b.util.getKlass(a.type).prototype,d=c.stateProperties;return d.forEach(function(b){a[b]===c[b]&&delete a[b]}),a},toString:function(){return"#"},get:function(a){return this[a]},_setObject:function(a){for(var b in a)this._set(b,a[b])},set:function(a,b){return"object"==typeof a?this._setObject(a):"function"==typeof b&&"clipTo"!==a?this._set(a,b(this.get(a))):this._set(a,b),this},_set:function(a,c){var e="scaleX"===a||"scaleY"===a;return e&&(c=this._constrainScale(c)),"scaleX"===a&&0>c?(this.flipX=!this.flipX,c*=-1):"scaleY"===a&&0>c?(this.flipY=!this.flipY,c*=-1):"width"===a||"height"===a?this.minScaleLimit=d(Math.min(.1,1/Math.max(this.width,this.height)),2):"shadow"!==a||!c||c instanceof b.Shadow||(c=new b.Shadow(c)),this[a]=c,this},toggle:function(a){var b=this.get(a);return"boolean"==typeof b&&this.set(a,!b),this},setSourcePath:function(a){return this.sourcePath=a,this},getViewportTransform:function(){return this.canvas&&this.canvas.viewportTransform?this.canvas.viewportTransform:[1,0,0,1,0,0]},render:function(a,c){if(0!==this.width&&0!==this.height&&this.visible){if(a.save(),this._setupFillRule(a),this._transform(a,c),this._setStrokeStyles(a),this._setFillStyles(a),this.group&&"path-group"===this.group.type){a.translate(-this.group.width/2,-this.group.height/2);var d=this.transformMatrix;d&&a.transform.apply(a,d)}a.globalAlpha=this.group?a.globalAlpha*this.opacity:this.opacity,this._setShadow(a),this.clipTo&&b.util.clipContext(this,a),this._render(a,c),this.clipTo&&a.restore(),this._removeShadow(a),this._restoreFillRule(a),a.restore()}},_transform:function(a,b){var c=this.transformMatrix;c&&!this.group&&a.setTransform.apply(a,c),b||this.transform(a)},_setStrokeStyles:function(a){this.stroke&&(a.lineWidth=this.strokeWidth,a.lineCap=this.strokeLineCap,a.lineJoin=this.strokeLineJoin,a.miterLimit=this.strokeMiterLimit,a.strokeStyle=this.stroke.toLive?this.stroke.toLive(a):this.stroke)},_setFillStyles:function(a){this.fill&&(a.fillStyle=this.fill.toLive?this.fill.toLive(a):this.fill)},_renderControls:function(a,c){var d=this.getViewportTransform();if(a.save(),this.active&&!c){var e;this.group&&(e=b.util.transformPoint(this.group.getCenterPoint(),d),a.translate(e.x,e.y),a.rotate(f(this.group.angle))),e=b.util.transformPoint(this.getCenterPoint(),d,null!=this.group),this.group&&(e.x*=this.group.scaleX,e.y*=this.group.scaleY),a.translate(e.x,e.y),a.rotate(f(this.angle)),this.drawBorders(a),this.drawControls(a)}a.restore()},_setShadow:function(a){this.shadow&&(a.shadowColor=this.shadow.color,a.shadowBlur=this.shadow.blur,a.shadowOffsetX=this.shadow.offsetX,a.shadowOffsetY=this.shadow.offsetY) -},_removeShadow:function(a){this.shadow&&(a.shadowColor="",a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0)},_renderFill:function(a){if(this.fill){if(a.save(),this.fill.toLive&&a.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0),this.fill.gradientTransform){var b=this.fill.gradientTransform;a.transform.apply(a,b)}"destination-over"===this.fillRule?a.fill("evenodd"):a.fill(),a.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(a)}},_renderStroke:function(a){if(this.stroke&&0!==this.strokeWidth){if(a.save(),this.strokeDashArray)1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),g?(a.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(a)):this._renderDashedStroke&&this._renderDashedStroke(a),a.stroke();else{if(this.stroke.gradientTransform){var b=this.stroke.gradientTransform;a.transform.apply(a,b)}this._stroke?this._stroke(a):a.stroke()}this._removeShadow(a),a.restore()}},clone:function(a,c){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(c),a):new b.Object(this.toObject(c))},cloneAsImage:function(a){var c=this.toDataURL();return b.util.loadImage(c,function(c){a&&a(new b.Image(c))}),this},toDataURL:function(a){a||(a={});var c=b.util.createCanvasElement(),d=this.getBoundingRect();c.width=d.width,c.height=d.height,b.util.wrapElement(c,"div");var e=new b.Canvas(c);"jpg"===a.format&&(a.format="jpeg"),"jpeg"===a.format&&(e.backgroundColor="#fff");var f={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new b.Point(c.width/2,c.height/2),"center","center");var g=this.canvas;e.add(this);var h=e.toDataURL(a);return this.set(f).setCoords(),this.canvas=g,e.dispose(),e=null,h},isType:function(a){return this.type===a},complexity:function(){return 0},toJSON:function(a){return this.toObject(a)},setGradient:function(a,c){c||(c={});var d={colorStops:[]};d.type=c.type||(c.r1||c.r2?"radial":"linear"),d.coords={x1:c.x1,y1:c.y1,x2:c.x2,y2:c.y2},(c.r1||c.r2)&&(d.coords.r1=c.r1,d.coords.r2=c.r2);for(var e in c.colorStops){var f=new b.Color(c.colorStops[e]);d.colorStops.push({offset:e,color:f.toRgb(),opacity:f.getAlpha()})}return this.set(a,b.Gradient.forObject(this,d))},setPatternFill:function(a){return this.set("fill",new b.Pattern(a))},setShadow:function(a){return this.set("shadow",a?new b.Shadow(a):null)},setColor:function(a){return this.set("fill",a),this},setAngle:function(a){var b=("center"!==this.originX||"center"!==this.originY)&&this.centeredRotation;return b&&this._setOriginToCenter(),this.set("angle",a),b&&this._resetOrigin(),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this),this},getLocalPointer:function(a,b){b=b||this.canvas.getPointer(a);var c=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:b.x-c.x,y:b.y-c.y}},_setupFillRule:function(a){this.fillRule&&(this._prevFillRule=a.globalCompositeOperation,a.globalCompositeOperation=this.fillRule)},_restoreFillRule:function(a){this.fillRule&&this._prevFillRule&&(a.globalCompositeOperation=this._prevFillRule)}}),b.util.createAccessors(b.Object),b.Object.prototype.rotate=b.Object.prototype.setAngle,c(b.Object.prototype,b.Observable),b.Object.NUM_FRACTION_DIGITS=2,b.Object.__uid=0)}("undefined"!=typeof exports?exports:this),function(){var a=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(b,c,d){var e=b.x,f=b.y,g=this.stroke?this.strokeWidth:0;return"left"===c?e=b.x+(this.getWidth()+g*this.scaleX)/2:"right"===c&&(e=b.x-(this.getWidth()+g*this.scaleX)/2),"top"===d?f=b.y+(this.getHeight()+g*this.scaleY)/2:"bottom"===d&&(f=b.y-(this.getHeight()+g*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(e,f),b,a(this.angle))},translateToOriginPoint:function(b,c,d){var e=b.x,f=b.y,g=this.stroke?this.strokeWidth:0;return"left"===c?e=b.x-(this.getWidth()+g*this.scaleX)/2:"right"===c&&(e=b.x+(this.getWidth()+g*this.scaleX)/2),"top"===d?f=b.y-(this.getHeight()+g*this.scaleY)/2:"bottom"===d&&(f=b.y+(this.getHeight()+g*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(e,f),b,a(this.angle))},getCenterPoint:function(){var a=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(a,this.originX,this.originY)},getPointByOrigin:function(a,b){var c=this.getCenterPoint();return this.translateToOriginPoint(c,a,b)},toLocalPoint:function(b,c,d){var e,f,g=this.getCenterPoint(),h=this.stroke?this.strokeWidth:0;return c&&d?(e="left"===c?g.x-(this.getWidth()+h*this.scaleX)/2:"right"===c?g.x+(this.getWidth()+h*this.scaleX)/2:g.x,f="top"===d?g.y-(this.getHeight()+h*this.scaleY)/2:"bottom"===d?g.y+(this.getHeight()+h*this.scaleY)/2:g.y):(e=this.left,f=this.top),fabric.util.rotatePoint(new fabric.Point(b.x,b.y),g,-a(this.angle)).subtractEquals(new fabric.Point(e,f))},setPositionByOrigin:function(a,b,c){var d=this.translateToCenterPoint(a,b,c),e=this.translateToOriginPoint(d,this.originX,this.originY);this.set("left",e.x),this.set("top",e.y)},adjustPosition:function(b){var c=a(this.angle),d=this.getWidth()/2,e=Math.cos(c)*d,f=Math.sin(c)*d,g=this.getWidth(),h=Math.cos(c)*g,i=Math.sin(c)*g;"center"===this.originX&&"left"===b||"right"===this.originX&&"center"===b?(this.left-=e,this.top-=f):"left"===this.originX&&"center"===b||"center"===this.originX&&"right"===b?(this.left+=e,this.top+=f):"left"===this.originX&&"right"===b?(this.left+=h,this.top+=i):"right"===this.originX&&"left"===b&&(this.left-=h,this.top-=i),this.setCoords(),this.originX=b},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var a=this.getCenterPoint();this.originX="center",this.originY="center",this.left=a.x,this.top=a.y},_resetOrigin:function(){var a=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=a.x,this.top=a.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var a=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(a,b){var c=this.oCoords,d=new fabric.Point(c.tl.x,c.tl.y),e=new fabric.Point(c.tr.x,c.tr.y),f=new fabric.Point(c.bl.x,c.bl.y),g=new fabric.Point(c.br.x,c.br.y),h=fabric.Intersection.intersectPolygonRectangle([d,e,g,f],a,b);return"Intersection"===h.status},intersectsWithObject:function(a){function b(a){return{tl:new fabric.Point(a.tl.x,a.tl.y),tr:new fabric.Point(a.tr.x,a.tr.y),bl:new fabric.Point(a.bl.x,a.bl.y),br:new fabric.Point(a.br.x,a.br.y)}}var c=b(this.oCoords),d=b(a.oCoords),e=fabric.Intersection.intersectPolygonPolygon([c.tl,c.tr,c.br,c.bl],[d.tl,d.tr,d.br,d.bl]);return"Intersection"===e.status},isContainedWithinObject:function(a){var b=a.getBoundingRect(),c=new fabric.Point(b.left,b.top),d=new fabric.Point(b.left+b.width,b.top+b.height);return this.isContainedWithinRect(c,d)},isContainedWithinRect:function(a,b){var c=this.getBoundingRect();return c.left>=a.x&&c.left+c.width<=b.x&&c.top>=a.y&&c.top+c.height<=b.y},containsPoint:function(a){var b=this._getImageLines(this.oCoords),c=this._findCrossPoints(a,b);return 0!==c&&c%2===1},_getImageLines:function(a){return{topline:{o:a.tl,d:a.tr},rightline:{o:a.tr,d:a.br},bottomline:{o:a.br,d:a.bl},leftline:{o:a.bl,d:a.tl}}},_findCrossPoints:function(a,b){var c,d,e,f,g,h,i,j=0;for(var k in b)if(i=b[k],!(i.o.y=a.y&&i.d.y>=a.y||(i.o.x===i.d.x&&i.o.x>=a.x?(g=i.o.x,h=a.y):(c=0,d=(i.d.y-i.o.y)/(i.d.x-i.o.x),e=a.y-c*a.x,f=i.o.y-d*i.o.x,g=-(e-f)/(c-d),h=e+c*g),g>=a.x&&(j+=1),2!==j)))break;return j},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var a=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],b=fabric.util.array.min(a),c=fabric.util.array.max(a),d=Math.abs(b-c),e=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],f=fabric.util.array.min(e),g=fabric.util.array.max(e),h=Math.abs(f-g);return{left:b,top:f,width:d,height:h}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(a){return Math.abs(a)a?-this.minScaleLimit:this.minScaleLimit:a},scale:function(a){return a=this._constrainScale(a),0>a&&(this.flipX=!this.flipX,this.flipY=!this.flipY,a*=-1),this.scaleX=a,this.scaleY=a,this.setCoords(),this},scaleToWidth:function(a){var b=this.getBoundingRectWidth()/this.getWidth();return this.scale(a/this.width/b)},scaleToHeight:function(a){var b=this.getBoundingRectHeight()/this.getHeight();return this.scale(a/this.height/b)},setCoords:function(){var b=this.strokeWidth>1?this.strokeWidth:0,c=a(this.angle),d=this.getViewportTransform(),e=function(a){return fabric.util.transformPoint(a,d)},f=this.width,g=this.height,h="round"===this.strokeLineCap||"square"===this.strokeLineCap,i="line"===this.type&&1===this.width,j="line"===this.type&&1===this.height,k=h&&j||"line"!==this.type,l=h&&i||"line"!==this.type;i?f=b:j&&(g=b),k&&(f+=b),l&&(g+=b),this.currentWidth=f*this.scaleX,this.currentHeight=g*this.scaleY,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var m=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),n=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),o=Math.cos(n+c)*m,p=Math.sin(n+c)*m,q=Math.sin(c),r=Math.cos(c),s=this.getCenterPoint(),t=new fabric.Point(this.currentWidth,this.currentHeight),u=new fabric.Point(s.x-o,s.y-p),v=new fabric.Point(u.x+t.x*r,u.y+t.x*q),w=new fabric.Point(u.x-t.y*q,u.y+t.y*r),x=new fabric.Point(u.x+t.x/2*r,u.y+t.x/2*q),y=e(u),z=e(v),A=e(new fabric.Point(v.x-t.y*q,v.y+t.y*r)),B=e(w),C=e(new fabric.Point(u.x-t.y/2*q,u.y+t.y/2*r)),D=e(x),E=e(new fabric.Point(v.x-t.y/2*q,v.y+t.y/2*r)),F=e(new fabric.Point(w.x+t.x/2*r,w.y+t.x/2*q)),G=e(new fabric.Point(x.x,x.y)),H=Math.cos(n+c)*this.padding*Math.sqrt(2),I=Math.sin(n+c)*this.padding*Math.sqrt(2);return y=y.add(new fabric.Point(-H,-I)),z=z.add(new fabric.Point(I,-H)),A=A.add(new fabric.Point(H,I)),B=B.add(new fabric.Point(-I,H)),C=C.add(new fabric.Point((-H-I)/2,(-I+H)/2)),D=D.add(new fabric.Point((I-H)/2,-(I+H)/2)),E=E.add(new fabric.Point((I+H)/2,(I-H)/2)),F=F.add(new fabric.Point((H-I)/2,(H+I)/2)),G=G.add(new fabric.Point((I-H)/2,-(I+H)/2)),this.oCoords={tl:y,tr:z,br:A,bl:B,ml:C,mt:D,mr:E,mb:F,mtr:G},this._setCornerCoords&&this._setCornerCoords(),this}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(a){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,a):this.canvas.sendBackwards(this,a),this},bringForward:function(a){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,a):this.canvas.bringForward(this,a),this},moveTo:function(a){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,a):this.canvas.moveTo(this,a),this}}),fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var a=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",b="destination-over"===this.fillRule?"evenodd":this.fillRule,c=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",d=this.strokeWidth?this.strokeWidth:"0",e=this.strokeDashArray?this.strokeDashArray.join(" "):"",f=this.strokeLineCap?this.strokeLineCap:"butt",g=this.strokeLineJoin?this.strokeLineJoin:"miter",h=this.strokeMiterLimit?this.strokeMiterLimit:"4",i="undefined"!=typeof this.opacity?this.opacity:"1",j=this.visible?"":" visibility: hidden;",k=this.shadow&&"text"!==this.type?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",c,"; ","stroke-width: ",d,"; ","stroke-dasharray: ",e,"; ","stroke-linecap: ",f,"; ","stroke-linejoin: ",g,"; ","stroke-miterlimit: ",h,"; ","fill: ",a,"; ","fill-rule: ",b,"; ","opacity: ",i,";",k,j].join("")},getSvgTransform:function(){if(this.group)return"";var a=fabric.util.toFixed,b=this.getAngle(),c=!this.canvas||this.canvas.svgViewportTransformation?this.getViewportTransform():[1,0,0,1,0,0],d=fabric.util.transformPoint(this.getCenterPoint(),c),e=fabric.Object.NUM_FRACTION_DIGITS,f="path-group"===this.type?"":"translate("+a(d.x,e)+" "+a(d.y,e)+")",g=0!==b?" rotate("+a(b,e)+")":"",h=1===this.scaleX&&1===this.scaleY&&1===c[0]&&1===c[3]?"":" scale("+a(this.scaleX*c[0],e)+" "+a(this.scaleY*c[3],e)+")",i="path-group"===this.type?this.width*c[0]:0,j=this.flipX?" matrix(-1 0 0 1 "+i+" 0) ":"",k="path-group"===this.type?this.height*c[3]:0,l=this.flipY?" matrix(1 0 0 -1 0 "+k+")":"";return[f,g,h,j,l].join("")},getSvgTransformMatrix:function(){return this.transformMatrix?" matrix("+this.transformMatrix.join(" ")+")":""},_createBaseSVGMarkup:function(){var a=[];return this.fill&&this.fill.toLive&&a.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&a.push(this.stroke.toSVG(this,!1)),this.shadow&&a.push(this.shadow.toSVG(this)),a}}),fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(a){return this.get(a)!==this.originalState[a]},this)},saveState:function(a){return this.stateProperties.forEach(function(a){this.originalState[a]=this.get(a)},this),a&&a.stateProperties&&a.stateProperties.forEach(function(a){this.originalState[a]=this.get(a)},this),this},setupState:function(){return this.originalState={},this.saveState(),this}}),function(a){"use strict";function b(a,b){var c=a.origin,d=a.axis1,e=a.axis2,f=a.dimension,g=b.nearest,h=b.center,i=b.farthest;return function(){switch(this.get(c)){case g:return Math.min(this.get(d),this.get(e));case h:return Math.min(this.get(d),this.get(e))+.5*this.get(f);case i:return Math.max(this.get(d),this.get(e))}}}var c=a.fabric||(a.fabric={}),d=c.util.object.extend,e={x1:1,x2:1,y1:1,y2:1},f=c.StaticCanvas.supports("setLineDash");return c.Line?void c.warn("fabric.Line is already defined"):(c.Line=c.util.createClass(c.Object,{type:"line",x1:0,y1:0,x2:0,y2:0,initialize:function(a,b){b=b||{},a||(a=[0,0,0,0]),this.callSuper("initialize",b),this.set("x1",a[0]),this.set("y1",a[1]),this.set("x2",a[2]),this.set("y2",a[3]),this._setWidthHeight(b)},_setWidthHeight:function(a){a||(a={}),this.width=Math.abs(this.x2-this.x1)||1,this.height=Math.abs(this.y2-this.y1)||1,this.left="left"in a?a.left:this._getLeftToOriginX(),this.top="top"in a?a.top:this._getTopToOriginY()},_set:function(a,b){return this[a]=b,"undefined"!=typeof e[a]&&this._setWidthHeight(),this},_getLeftToOriginX:b({origin:"originX",axis1:"x1",axis2:"x2",dimension:"width"},{nearest:"left",center:"center",farthest:"right"}),_getTopToOriginY:b({origin:"originY",axis1:"y1",axis2:"y2",dimension:"height"},{nearest:"top",center:"center",farthest:"bottom"}),_render:function(a,b){if(a.beginPath(),b){var c=this.getCenterPoint();a.translate(c.x,c.y)}if(!this.strokeDashArray||this.strokeDashArray&&f){var d=this.x1<=this.x2?-1:1,e=this.y1<=this.y2?-1:1;a.moveTo(1===this.width?0:d*this.width/2,1===this.height?0:e*this.height/2),a.lineTo(1===this.width?0:-1*d*this.width/2,1===this.height?0:-1*e*this.height/2)}a.lineWidth=this.strokeWidth;var g=a.strokeStyle;a.strokeStyle=this.stroke||a.fillStyle,this.stroke&&this._renderStroke(a),a.strokeStyle=g},_renderDashedStroke:function(a){var b=this.x1<=this.x2?-1:1,d=this.y1<=this.y2?-1:1,e=1===this.width?0:b*this.width/2,f=1===this.height?0:d*this.height/2;a.beginPath(),c.util.drawDashedLine(a,e,f,-e,-f,this.strokeDashArray),a.closePath()},toObject:function(a){return d(this.callSuper("toObject",a),{x1:this.get("x1"),y1:this.get("y1"),x2:this.get("x2"),y2:this.get("y2")})},toSVG:function(a){var b=this._createBaseSVGMarkup(),c="";if(!this.group){var d=-this.width/2-(this.x1>this.x2?this.x2:this.x1),e=-this.height/2-(this.y1>this.y2?this.y2:this.y1);c="translate("+d+", "+e+") "}return b.push("\n'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),c.Line.ATTRIBUTE_NAMES=c.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),c.Line.fromElement=function(a,b){var e=c.parseAttributes(a,c.Line.ATTRIBUTE_NAMES),f=[e.x1||0,e.y1||0,e.x2||0,e.y2||0];return new c.Line(f,d(e,b))},void(c.Line.fromObject=function(a){var b=[a.x1,a.y1,a.x2,a.y2];return new c.Line(b,a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){return"radius"in a&&a.radius>0}var c=a.fabric||(a.fabric={}),d=2*Math.PI,e=c.util.object.extend;return c.Circle?void c.warn("fabric.Circle is already defined."):(c.Circle=c.util.createClass(c.Object,{type:"circle",radius:0,initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("radius",a.radius||0)},_set:function(a,b){return this.callSuper("_set",a,b),"radius"===a&&this.setRadius(b),this},toObject:function(a){return e(this.callSuper("toObject",a),{radius:this.get("radius")})},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=0,d=0;return this.group&&(c=this.left+this.radius,d=this.top+this.radius),b.push("\n'),a?a(b.join("")):b.join("")},_render:function(a,b){a.beginPath(),a.arc(b?this.left+this.radius:0,b?this.top+this.radius:0,this.radius,0,d,!1),this._renderFill(a),this._renderStroke(a)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(a){this.radius=a,this.set("width",2*a).set("height",2*a)},complexity:function(){return 1}}),c.Circle.ATTRIBUTE_NAMES=c.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),c.Circle.fromElement=function(a,d){d||(d={});var f=c.parseAttributes(a,c.Circle.ATTRIBUTE_NAMES);if(!b(f))throw new Error("value of `r` attribute is required and can not be negative");f.left=f.left||0,f.top=f.top||0;var g=new c.Circle(e(f,d));return g.left-=g.radius,g.top-=g.radius,g},void(c.Circle.fromObject=function(a){return new c.Circle(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});return b.Triangle?void b.warn("fabric.Triangle is already defined"):(b.Triangle=b.util.createClass(b.Object,{type:"triangle",initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("width",a.width||100).set("height",a.height||100)},_render:function(a){var b=this.width/2,c=this.height/2;a.beginPath(),a.moveTo(-b,c),a.lineTo(0,-c),a.lineTo(b,c),a.closePath(),this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c=this.width/2,d=this.height/2;a.beginPath(),b.util.drawDashedLine(a,-c,d,0,-d,this.strokeDashArray),b.util.drawDashedLine(a,0,-d,c,d,this.strokeDashArray),b.util.drawDashedLine(a,c,d,-c,d,this.strokeDashArray),a.closePath()},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=this.width/2,d=this.height/2,e=[-c+" "+d,"0 "+-d,c+" "+d].join(",");return b.push("'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),void(b.Triangle.fromObject=function(a){return new b.Triangle(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=2*Math.PI,d=b.util.object.extend;return b.Ellipse?void b.warn("fabric.Ellipse is already defined."):(b.Ellipse=b.util.createClass(b.Object,{type:"ellipse",rx:0,ry:0,initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("rx",a.rx||0),this.set("ry",a.ry||0),this.set("width",2*this.get("rx")),this.set("height",2*this.get("ry"))},toObject:function(a){return d(this.callSuper("toObject",a),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=0,d=0;return this.group&&(c=this.left+this.rx,d=this.top+this.ry),b.push("\n'),a?a(b.join("")):b.join("")},_render:function(a,b){a.beginPath(),a.save(),a.transform(1,0,0,this.ry/this.rx,0,0),a.arc(b?this.left+this.rx:0,b?(this.top+this.ry)*this.rx/this.ry:0,this.rx,0,c,!1),a.restore(),this._renderFill(a),this._renderStroke(a)},complexity:function(){return 1}}),b.Ellipse.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),b.Ellipse.fromElement=function(a,c){c||(c={});var e=b.parseAttributes(a,b.Ellipse.ATTRIBUTE_NAMES);e.left=e.left||0,e.top=e.top||0;var f=new b.Ellipse(d(e,c));return f.top-=f.ry,f.left-=f.rx,f},void(b.Ellipse.fromObject=function(a){return new b.Ellipse(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;if(b.Rect)return void console.warn("fabric.Rect is already defined");var d=b.Object.prototype.stateProperties.concat();d.push("rx","ry","x","y"),b.Rect=b.util.createClass(b.Object,{stateProperties:d,type:"rect",rx:0,ry:0,strokeDashArray:null,initialize:function(a){a=a||{},this.callSuper("initialize",a),this._initRxRy()},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(a,b){if(1===this.width&&1===this.height)return void a.fillRect(0,0,1,1);var c=this.rx?Math.min(this.rx,this.width/2):0,d=this.ry?Math.min(this.ry,this.height/2):0,e=this.width,f=this.height,g=b?this.left:-this.width/2,h=b?this.top:-this.height/2,i=0!==c||0!==d,j=.4477152502;a.beginPath(),a.moveTo(g+c,h),a.lineTo(g+e-c,h),i&&a.bezierCurveTo(g+e-j*c,h,g+e,h+j*d,g+e,h+d),a.lineTo(g+e,h+f-d),i&&a.bezierCurveTo(g+e,h+f-j*d,g+e-j*c,h+f,g+e-c,h+f),a.lineTo(g+c,h+f),i&&a.bezierCurveTo(g+j*c,h+f,g,h+f-j*d,g,h+f-d),a.lineTo(g,h+d),i&&a.bezierCurveTo(g,h+j*d,g+j*c,h,g+c,h),a.closePath(),this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c=-this.width/2,d=-this.height/2,e=this.width,f=this.height;a.beginPath(),b.util.drawDashedLine(a,c,d,c+e,d,this.strokeDashArray),b.util.drawDashedLine(a,c+e,d,c+e,d+f,this.strokeDashArray),b.util.drawDashedLine(a,c+e,d+f,c,d+f,this.strokeDashArray),b.util.drawDashedLine(a,c,d+f,c,d,this.strokeDashArray),a.closePath()},toObject:function(a){var b=c(this.callSuper("toObject",a),{rx:this.get("rx")||0,ry:this.get("ry")||0});return this.includeDefaultValues||this._removeDefaultValues(b),b},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=this.left,d=this.top;return this.group||(c=-this.width/2,d=-this.height/2),b.push("\n'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),b.Rect.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),b.Rect.fromElement=function(a,d){if(!a)return null;d=d||{};var e=b.parseAttributes(a,b.Rect.ATTRIBUTE_NAMES);return e.left=e.left||0,e.top=e.top||0,new b.Rect(c(d?b.util.object.clone(d):{},e))},b.Rect.fromObject=function(a){return new b.Rect(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.toFixed;return b.Polyline?void b.warn("fabric.Polyline is already defined"):(b.Polyline=b.util.createClass(b.Object,{type:"polyline",points:null,initialize:function(a,b){b=b||{},this.set("points",a),this.callSuper("initialize",b),this._calcDimensions()},_calcDimensions:function(){return b.Polygon.prototype._calcDimensions.call(this)},_applyPointOffset:function(){return b.Polygon.prototype._applyPointOffset.call(this)},toObject:function(a){return b.Polygon.prototype.toObject.call(this,a)},toSVG:function(a){for(var b=[],d=this._createBaseSVGMarkup(),e=0,f=this.points.length;f>e;e++)b.push(c(this.points[e].x,2),",",c(this.points[e].y,2)," ");return d.push("\n'),a?a(d.join("")):d.join("")},_render:function(a){var b;a.beginPath(),this._applyPointOffset&&(this.group&&"path-group"===this.group.type||this._applyPointOffset(),this._applyPointOffset=null),a.moveTo(this.points[0].x,this.points[0].y);for(var c=0,d=this.points.length;d>c;c++)b=this.points[c],a.lineTo(b.x,b.y);this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c,d;a.beginPath();for(var e=0,f=this.points.length;f>e;e++)c=this.points[e],d=this.points[e+1]||c,b.util.drawDashedLine(a,c.x,c.y,d.x,d.y,this.strokeDashArray)},complexity:function(){return this.get("points").length}}),b.Polyline.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat(),b.Polyline.fromElement=function(a,c){if(!a)return null;c||(c={});var d=b.parsePointsAttribute(a.getAttribute("points")),e=b.parseAttributes(a,b.Polyline.ATTRIBUTE_NAMES);return null===d?null:new b.Polyline(d,b.util.object.extend(e,c))},void(b.Polyline.fromObject=function(a){var c=a.points;return new b.Polyline(c,a,!0)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.array.min,e=b.util.array.max,f=b.util.toFixed;return b.Polygon?void b.warn("fabric.Polygon is already defined"):(b.Polygon=b.util.createClass(b.Object,{type:"polygon",points:null,initialize:function(a,b){b=b||{},this.points=a,this.callSuper("initialize",b),this._calcDimensions()},_calcDimensions:function(){var a=this.points,b=d(a,"x"),c=d(a,"y"),f=e(a,"x"),g=e(a,"y");this.width=f-b||1,this.height=g-c||1,this.left=b,this.top=c},_applyPointOffset:function(){this.points.forEach(function(a){a.x-=this.left+this.width/2,a.y-=this.top+this.height/2},this)},toObject:function(a){return c(this.callSuper("toObject",a),{points:this.points.concat()})},toSVG:function(a){for(var b=[],c=this._createBaseSVGMarkup(),d=0,e=this.points.length;e>d;d++)b.push(f(this.points[d].x,2),",",f(this.points[d].y,2)," ");return c.push("\n'),a?a(c.join("")):c.join("")},_render:function(a){var b;a.beginPath(),this._applyPointOffset&&(this.group&&"path-group"===this.group.type||this._applyPointOffset(),this._applyPointOffset=null),a.moveTo(this.points[0].x,this.points[0].y);for(var c=0,d=this.points.length;d>c;c++)b=this.points[c],a.lineTo(b.x,b.y);this._renderFill(a),(this.stroke||this.strokeDashArray)&&(a.closePath(),this._renderStroke(a))},_renderDashedStroke:function(a){var c,d;a.beginPath();for(var e=0,f=this.points.length;f>e;e++)c=this.points[e],d=this.points[e+1]||this.points[0],b.util.drawDashedLine(a,c.x,c.y,d.x,d.y,this.strokeDashArray);a.closePath()},complexity:function(){return this.points.length}}),b.Polygon.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat(),b.Polygon.fromElement=function(a,d){if(!a)return null;d||(d={});var e=b.parsePointsAttribute(a.getAttribute("points")),f=b.parseAttributes(a,b.Polygon.ATTRIBUTE_NAMES);return null===e?null:new b.Polygon(e,c(f,d))},void(b.Polygon.fromObject=function(a){return new b.Polygon(a.points,a,!0)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){return"H"===a[0]?a[1]:a[a.length-2]}function c(a){return"V"===a[0]?a[1]:a[a.length-1]}var d=a.fabric||(a.fabric={}),e=d.util.array.min,f=d.util.array.max,g=d.util.object.extend,h=Object.prototype.toString,i=d.util.drawArc,j={m:2,l:2,h:1,v:1,c:6,s:4,q:4,t:2,a:7},k={m:"l",M:"L"};return d.Path?void d.warn("fabric.Path is already defined"):(d.Path=d.util.createClass(d.Object,{type:"path",path:null,initialize:function(a,b){if(b=b||{},this.setOptions(b),!a)throw new Error("`path` argument is required");var c="[object Array]"===h.call(a);this.path=c?a:a.match&&a.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi),this.path&&(c||(this.path=this._parsePath()),this._initializePath(b),b.sourcePath&&this.setSourcePath(b.sourcePath))},_initializePath:function(a){var b="width"in a&&null!=a.width,c="height"in a&&null!=a.width,d="left"in a,e="top"in a,f=d?this.left:0,h=e?this.top:0;b&&c?(e||(this.top=this.height/2),d||(this.left=this.width/2)):(g(this,this._parseDimensions()),b&&(this.width=a.width),c&&(this.height=a.height)),this.pathOffset=this.pathOffset||this._calculatePathOffset(f,h)},_calculatePathOffset:function(a,b){return{x:this.left-a-this.width/2,y:this.top-b-this.height/2}},_render:function(a,b){var c,d,e,f,g,h=null,j=0,k=0,l=0,m=0,n=0,o=0,p=-(this.width/2+this.pathOffset.x),q=-(this.height/2+this.pathOffset.y);b&&(p+=this.width/2,q+=this.height/2);for(var r=0,s=this.path.length;s>r;++r){switch(c=this.path[r],c[0]){case"l":l+=c[1],m+=c[2],a.lineTo(l+p,m+q);break;case"L":l=c[1],m=c[2],a.lineTo(l+p,m+q);break;case"h":l+=c[1],a.lineTo(l+p,m+q);break;case"H":l=c[1],a.lineTo(l+p,m+q);break;case"v":m+=c[1],a.lineTo(l+p,m+q);break;case"V":m=c[1],a.lineTo(l+p,m+q);break;case"m":l+=c[1],m+=c[2],j=l,k=m,a.moveTo(l+p,m+q);break;case"M":l=c[1],m=c[2],j=l,k=m,a.moveTo(l+p,m+q);break;case"c":d=l+c[5],e=m+c[6],n=l+c[3],o=m+c[4],a.bezierCurveTo(l+c[1]+p,m+c[2]+q,n+p,o+q,d+p,e+q),l=d,m=e;break;case"C":l=c[5],m=c[6],n=c[3],o=c[4],a.bezierCurveTo(c[1]+p,c[2]+q,n+p,o+q,l+p,m+q);break;case"s":d=l+c[3],e=m+c[4],n=n?2*l-n:l,o=o?2*m-o:m,a.bezierCurveTo(n+p,o+q,l+c[1]+p,m+c[2]+q,d+p,e+q),n=l+c[1],o=m+c[2],l=d,m=e;break;case"S":d=c[3],e=c[4],n=2*l-n,o=2*m-o,a.bezierCurveTo(n+p,o+q,c[1]+p,c[2]+q,d+p,e+q),l=d,m=e,n=c[1],o=c[2];break;case"q":d=l+c[3],e=m+c[4],n=l+c[1],o=m+c[2],a.quadraticCurveTo(n+p,o+q,d+p,e+q),l=d,m=e;break;case"Q":d=c[3],e=c[4],a.quadraticCurveTo(c[1]+p,c[2]+q,d+p,e+q),l=d,m=e,n=c[1],o=c[2];break;case"t":d=l+c[1],e=m+c[2],null===h[0].match(/[QqTt]/)?(n=l,o=m):"t"===h[0]?(n=2*l-f,o=2*m-g):"q"===h[0]&&(n=2*l-n,o=2*m-o),f=n,g=o,a.quadraticCurveTo(n+p,o+q,d+p,e+q),l=d,m=e,n=l+c[1],o=m+c[2];break;case"T":d=c[1],e=c[2],n=2*l-n,o=2*m-o,a.quadraticCurveTo(n+p,o+q,d+p,e+q),l=d,m=e;break;case"a":i(a,l+p,m+q,[c[1],c[2],c[3],c[4],c[5],c[6]+l+p,c[7]+m+q]),l+=c[6],m+=c[7];break;case"A":i(a,l+p,m+q,[c[1],c[2],c[3],c[4],c[5],c[6]+p,c[7]+q]),l=c[6],m=c[7];break;case"z":case"Z":l=j,m=k,a.closePath()}h=c}},render:function(a,b){if(this.visible){a.save(),b&&a.translate(-this.width/2,-this.height/2);var c=this.transformMatrix;c&&a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),b||this.transform(a),this._setStrokeStyles(a),this._setFillStyles(a),this._setShadow(a),this.clipTo&&d.util.clipContext(this,a),a.beginPath(),a.globalAlpha=this.group?a.globalAlpha*this.opacity:this.opacity,this._render(a,b),this._renderFill(a),this._renderStroke(a),this.clipTo&&a.restore(),this._removeShadow(a),a.restore()}},toString:function(){return"#"},toObject:function(a){var b=g(this.callSuper("toObject",a),{path:this.path.map(function(a){return a.slice()}),pathOffset:this.pathOffset});return this.sourcePath&&(b.sourcePath=this.sourcePath),this.transformMatrix&&(b.transformMatrix=this.transformMatrix),b},toDatalessObject:function(a){var b=this.toObject(a);return this.sourcePath&&(b.path=this.sourcePath),delete b.sourcePath,b},toSVG:function(a){for(var b=[],c=this._createBaseSVGMarkup(),d=0,e=this.path.length;e>d;d++)b.push(this.path[d].join(" ")); -var f=b.join(" ");return c.push("\n"),a?a(c.join("")):c.join("")},complexity:function(){return this.path.length},_parsePath:function(){for(var a,b,c,d,e,f=[],g=[],h=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,i=0,l=this.path.length;l>i;i++){for(a=this.path[i],d=a.slice(1).trim(),g.length=0;c=h.exec(d);)g.push(c[0]);e=[a.charAt(0)];for(var m=0,n=g.length;n>m;m++)b=parseFloat(g[m]),isNaN(b)||e.push(b);var o=e[0],p=j[o.toLowerCase()],q=k[o]||o;if(e.length-1>p)for(var r=1,s=e.length;s>r;r+=p)f.push([o].concat(e.slice(r,r+p))),o=q;else f.push(e)}return f},_parseDimensions:function(){var a=[],b=[],c={};this.path.forEach(function(d,e){this._getCoordsFromCommand(d,e,a,b,c)},this);var d=e(a),g=e(b),h=f(a),i=f(b),j=h-d,k=i-g,l={left:this.left+(d+j/2),top:this.top+(g+k/2),width:j,height:k};return l},_getCoordsFromCommand:function(a,d,e,f,g){var h=!1;"H"!==a[0]&&(g.x=b(0===d?a:this.path[d-1])),"V"!==a[0]&&(g.y=c(0===d?a:this.path[d-1])),a[0]===a[0].toLowerCase()&&(h=!0);var i,j=this._getXY(a,h,g);i=parseInt(j.x,10),isNaN(i)||e.push(i),i=parseInt(j.y,10),isNaN(i)||f.push(i)},_getXY:function(a,d,e){var f=d?e.x+b(a):"V"===a[0]?e.x:b(a),g=d?e.y+c(a):"H"===a[0]?e.y:c(a);return{x:f,y:g}}}),d.Path.fromObject=function(a,b){"string"==typeof a.path?d.loadSVGFromURL(a.path,function(c){var e=c[0],f=a.path;delete a.path,d.util.object.extend(e,a),e.setSourcePath(f),b(e)}):b(new d.Path(a.path,a))},d.Path.ATTRIBUTE_NAMES=d.SHARED_ATTRIBUTES.concat(["d"]),d.Path.fromElement=function(a,b,c){var e=d.parseAttributes(a,d.Path.ATTRIBUTE_NAMES);b&&b(new d.Path(e.d,g(e,c)))},void(d.Path.async=!0))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.array.invoke,e=b.Object.prototype.toObject;return b.PathGroup?void b.warn("fabric.PathGroup is already defined"):(b.PathGroup=b.util.createClass(b.Path,{type:"path-group",fill:"",initialize:function(a,b){b=b||{},this.paths=a||[];for(var c=this.paths.length;c--;)this.paths[c].group=this;this.setOptions(b),b.widthAttr&&(this.scaleX=b.widthAttr/b.width),b.heightAttr&&(this.scaleY=b.heightAttr/b.height),this.setCoords(),b.sourcePath&&this.setSourcePath(b.sourcePath)},render:function(a){if(this.visible){a.save();var c=this.transformMatrix;c&&a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),this.transform(a),this._setShadow(a),this.clipTo&&b.util.clipContext(this,a);for(var d=0,e=this.paths.length;e>d;++d)this.paths[d].render(a,!0);this.clipTo&&a.restore(),this._removeShadow(a),a.restore()}},_set:function(a,b){if("fill"===a&&b&&this.isSameColor())for(var c=this.paths.length;c--;)this.paths[c]._set(a,b);return this.callSuper("_set",a,b)},toObject:function(a){var b=c(e.call(this,a),{paths:d(this.getObjects(),"toObject",a)});return this.sourcePath&&(b.sourcePath=this.sourcePath),b},toDatalessObject:function(a){var b=this.toObject(a);return this.sourcePath&&(b.paths=this.sourcePath),b},toSVG:function(a){for(var b=this.getObjects(),c="translate("+this.left+" "+this.top+")",d=["\n"],e=0,f=b.length;f>e;e++)d.push(b[e].toSVG(a));return d.push("\n"),a?a(d.join("")):d.join("")},toString:function(){return"#"},isSameColor:function(){var a=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(b){return(b.get("fill")||"").toLowerCase()===a})},complexity:function(){return this.paths.reduce(function(a,b){return a+(b&&b.complexity?b.complexity():0)},0)},getObjects:function(){return this.paths}}),b.PathGroup.fromObject=function(a,c){"string"==typeof a.paths?b.loadSVGFromURL(a.paths,function(d){var e=a.paths;delete a.paths;var f=b.util.groupSVGElements(d,a,e);c(f)}):b.util.enlivenObjects(a.paths,function(d){delete a.paths,c(new b.PathGroup(d,a))})},void(b.PathGroup.async=!0))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.array.min,e=b.util.array.max,f=b.util.array.invoke;if(!b.Group){var g={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};b.Group=b.util.createClass(b.Object,b.Collection,{type:"group",initialize:function(a,b){b=b||{},this._objects=a||[];for(var d=this._objects.length;d--;)this._objects[d].group=this;this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),b&&c(this,b),this._setOpacityIfSame(),this.setCoords(),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(a){var b=a.getLeft(),c=a.getTop();a.set({originalLeft:b,originalTop:c,left:b-this.left,top:c-this.top}),a.setCoords(),a.__origHasControls=a.hasControls,a.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(a){return this._restoreObjectsState(),a&&(this._objects.push(a),a.group=this),this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(a){a.set("active",!0),a.group=this},removeWithUpdate:function(a){return this._moveFlippedObject(a),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(a),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(a){a.group=this},_onObjectRemoved:function(a){delete a.group,a.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(a,b){if(a in this.delegatedProperties){var c=this._objects.length;for(this[a]=b;c--;)this._objects[c].set(a,b)}else this[a]=b},toObject:function(a){return c(this.callSuper("toObject",a),{objects:f(this._objects,"toObject",a)})},render:function(a){if(this.visible){a.save(),this.clipTo&&b.util.clipContext(this,a);for(var c=0,d=this._objects.length;d>c;c++)this._renderObject(this._objects[c],a);this.clipTo&&a.restore(),a.restore()}},_renderControls:function(a,b){this.callSuper("_renderControls",a,b);for(var c=0,d=this._objects.length;d>c;c++)this._objects[c]._renderControls(a)},_renderObject:function(a,b){var c=a.hasRotatingPoint;a.visible&&(a.hasRotatingPoint=!1,a.render(b),a.hasRotatingPoint=c)},_restoreObjectsState:function(){return this._objects.forEach(this._restoreObjectState,this),this},_moveFlippedObject:function(a){var b=a.get("originX"),c=a.get("originY"),d=a.getCenterPoint();a.set({originX:"center",originY:"center",left:d.x,top:d.y}),this._toggleFlipping(a);var e=a.getPointByOrigin(b,c);return a.set({originX:b,originY:c,left:e.x,top:e.y}),this},_toggleFlipping:function(a){this.flipX&&(a.toggle("flipX"),a.set("left",-a.get("left")),a.setAngle(-a.getAngle())),this.flipY&&(a.toggle("flipY"),a.set("top",-a.get("top")),a.setAngle(-a.getAngle()))},_restoreObjectState:function(a){return this._setObjectPosition(a),a.setCoords(),a.hasControls=a.__origHasControls,delete a.__origHasControls,a.set("active",!1),a.setCoords(),delete a.group,this},_setObjectPosition:function(a){var b=this.getLeft(),c=this.getTop(),d=this._getRotatedLeftTop(a);a.set({angle:a.getAngle()+this.getAngle(),left:b+d.left,top:c+d.top,scaleX:a.get("scaleX")*this.get("scaleX"),scaleY:a.get("scaleY")*this.get("scaleY")})},_getRotatedLeftTop:function(a){var b=this.getAngle()*(Math.PI/180);return{left:-Math.sin(b)*a.getTop()*this.get("scaleY")+Math.cos(b)*a.getLeft()*this.get("scaleX"),top:Math.cos(b)*a.getTop()*this.get("scaleY")+Math.sin(b)*a.getLeft()*this.get("scaleX")}},destroy:function(){return this._objects.forEach(this._moveFlippedObject,this),this._restoreObjectsState()},saveCoords:function(){return this._originalLeft=this.get("left"),this._originalTop=this.get("top"),this},hasMoved:function(){return this._originalLeft!==this.get("left")||this._originalTop!==this.get("top")},setObjectsCoords:function(){return this.forEachObject(function(a){a.setCoords()}),this},_setOpacityIfSame:function(){var a=this.getObjects(),b=a[0]?a[0].get("opacity"):1,c=a.every(function(a){return a.get("opacity")===b});c&&(this.opacity=b)},_calcBounds:function(a){for(var b,c=[],d=[],e=0,f=this._objects.length;f>e;++e){b=this._objects[e],b.setCoords();for(var g in b.oCoords)c.push(b.oCoords[g].x),d.push(b.oCoords[g].y)}this.set(this._getBounds(c,d,a))},_getBounds:function(a,c,f){var g=b.util.invertTransform(this.getViewportTransform()),h=b.util.transformPoint(new b.Point(d(a),d(c)),g),i=b.util.transformPoint(new b.Point(e(a),e(c)),g),j={width:i.x-h.x||0,height:i.y-h.y||0};return f||(j.left=(h.x+i.x)/2||0,j.top=(h.y+i.y)/2||0),j},toSVG:function(a){for(var b=["\n'],c=0,d=this._objects.length;d>c;c++)b.push(this._objects[c].toSVG(a));return b.push("\n"),a?a(b.join("")):b.join("")},get:function(a){if(a in g){if(this[a])return this[a];for(var b=0,c=this._objects.length;c>b;b++)if(this._objects[b][a])return!0;return!1}return a in this.delegatedProperties?this._objects[0]&&this._objects[0].get(a):this[a]}}),b.Group.fromObject=function(a,c){b.util.enlivenObjects(a.objects,function(d){delete a.objects,c&&c(new b.Group(d,a))})},b.Group.async=!0}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=fabric.util.object.extend;return a.fabric||(a.fabric={}),a.fabric.Image?void fabric.warn("fabric.Image is already defined."):(fabric.Image=fabric.util.createClass(fabric.Object,{type:"image",crossOrigin:"",initialize:function(a,b){b||(b={}),this.filters=[],this.callSuper("initialize",b),this._initElement(a,b),this._initConfig(b),b.filters&&(this.filters=b.filters,this.applyFilters())},getElement:function(){return this._element},setElement:function(a,b){return this._element=a,this._originalElement=a,this._initConfig(),0!==this.filters.length&&this.applyFilters(b),this},setCrossOrigin:function(a){return this.crossOrigin=a,this._element.crossOrigin=a,this},getOriginalSize:function(){var a=this.getElement();return{width:a.width,height:a.height}},_stroke:function(a){a.save(),this._setStrokeStyles(a),a.beginPath(),a.strokeRect(-this.width/2,-this.height/2,this.width,this.height),a.closePath(),a.restore()},_renderDashedStroke:function(a){var b=-this.width/2,c=-this.height/2,d=this.width,e=this.height;a.save(),this._setStrokeStyles(a),a.beginPath(),fabric.util.drawDashedLine(a,b,c,b+d,c,this.strokeDashArray),fabric.util.drawDashedLine(a,b+d,c,b+d,c+e,this.strokeDashArray),fabric.util.drawDashedLine(a,b+d,c+e,b,c+e,this.strokeDashArray),fabric.util.drawDashedLine(a,b,c+e,b,c,this.strokeDashArray),a.closePath(),a.restore()},toObject:function(a){return b(this.callSuper("toObject",a),{src:this._originalElement.src||this._originalElement._src,filters:this.filters.map(function(a){return a&&a.toObject()}),crossOrigin:this.crossOrigin})},toSVG:function(a){var b=[],c=-this.width/2,d=-this.height/2;if(this.group&&(c=this.left,d=this.top),b.push('\n','\n"),this.stroke||this.strokeDashArray){var e=this.fill;this.fill=null,b.push("\n'),this.fill=e}return b.push("\n"),a?a(b.join("")):b.join("")},getSrc:function(){return this.getElement()?this.getElement().src||this.getElement()._src:void 0},toString:function(){return'#'},clone:function(a,b){this.constructor.fromObject(this.toObject(b),a)},applyFilters:function(a){if(this._originalElement){if(0===this.filters.length)return this._element=this._originalElement,void(a&&a());var b=this._originalElement,c=fabric.util.createCanvasElement(),d=fabric.util.createImage(),e=this;return c.width=b.width,c.height=b.height,c.getContext("2d").drawImage(b,0,0,b.width,b.height),this.filters.forEach(function(a){a&&a.applyTo(c)}),d.width=b.width,d.height=b.height,fabric.isLikelyNode?(d.src=c.toBuffer(undefined,fabric.Image.pngCompression),e._element=d,a&&a()):(d.onload=function(){e._element=d,a&&a(),d.onload=c=b=null},d.src=c.toDataURL("image/png")),this}},_render:function(a,b){this._element&&a.drawImage(this._element,b?this.left:-this.width/2,b?this.top:-this.height/2,this.width,this.height),this._renderStroke(a)},_resetWidthHeight:function(){var a=this.getElement();this.set("width",a.width),this.set("height",a.height)},_initElement:function(a){this.setElement(fabric.util.getById(a)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(a){a||(a={}),this.setOptions(a),this._setWidthHeight(a),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(a,b){a.filters&&a.filters.length?fabric.util.enlivenObjects(a.filters,function(a){b&&b(a)},"fabric.Image.filters"):b&&b()},_setWidthHeight:function(a){this.width="width"in a?a.width:this.getElement()?this.getElement().width||0:0,this.height="height"in a?a.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(a,b){fabric.util.loadImage(a.src,function(c){fabric.Image.prototype._initFilters.call(a,a,function(d){a.filters=d||[];var e=new fabric.Image(c,a);b&&b(e)})},null,a.crossOrigin)},fabric.Image.fromURL=function(a,b,c){fabric.util.loadImage(a,function(a){b(new fabric.Image(a,c))},null,c&&c.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")),fabric.Image.fromElement=function(a,c,d){var e=fabric.parseAttributes(a,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(e["xlink:href"],c,b(d?fabric.util.object.clone(d):{},e))},fabric.Image.async=!0,void(fabric.Image.pngCompression=1))}("undefined"!=typeof exports?exports:this),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Brightness=b.util.createClass(b.Image.filters.BaseFilter,{type:"Brightness",initialize:function(a){a=a||{},this.brightness=a.brightness||0},applyTo:function(a){for(var b=a.getContext("2d"),c=b.getImageData(0,0,a.width,a.height),d=c.data,e=this.brightness,f=0,g=d.length;g>f;f+=4)d[f]+=e,d[f+1]+=e,d[f+2]+=e;b.putImageData(c,0,0)},toObject:function(){return c(this.callSuper("toObject"),{brightness:this.brightness})}}),b.Image.filters.Brightness.fromObject=function(a){return new b.Image.filters.Brightness(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Convolute=b.util.createClass(b.Image.filters.BaseFilter,{type:"Convolute",initialize:function(a){a=a||{},this.opaque=a.opaque,this.matrix=a.matrix||[0,0,0,0,1,0,0,0,0];var c=b.util.createCanvasElement();this.tmpCtx=c.getContext("2d")},_createImageData:function(a,b){return this.tmpCtx.createImageData(a,b)},applyTo:function(a){for(var b=this.matrix,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=Math.round(Math.sqrt(b.length)),f=Math.floor(e/2),g=d.data,h=d.width,i=d.height,j=h,k=i,l=this._createImageData(j,k),m=l.data,n=this.opaque?1:0,o=0;k>o;o++)for(var p=0;j>p;p++){for(var q=o,r=p,s=4*(o*j+p),t=0,u=0,v=0,w=0,x=0;e>x;x++)for(var y=0;e>y;y++){var z=q+x-f,A=r+y-f;if(!(0>z||z>i||0>A||A>h)){var B=4*(z*h+A),C=b[x*e+y];t+=g[B]*C,u+=g[B+1]*C,v+=g[B+2]*C,w+=g[B+3]*C}}m[s]=t,m[s+1]=u,m[s+2]=v,m[s+3]=w+n*(255-w)}c.putImageData(l,0,0)},toObject:function(){return c(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),b.Image.filters.Convolute.fromObject=function(a){return new b.Image.filters.Convolute(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.GradientTransparency=b.util.createClass(b.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(a){a=a||{},this.threshold=a.threshold||100},applyTo:function(a){for(var b=a.getContext("2d"),c=b.getImageData(0,0,a.width,a.height),d=c.data,e=this.threshold,f=d.length,g=0,h=d.length;h>g;g+=4)d[g+3]=e+255*(f-g)/f;b.putImageData(c,0,0)},toObject:function(){return c(this.callSuper("toObject"),{threshold:this.threshold})}}),b.Image.filters.GradientTransparency.fromObject=function(a){return new b.Image.filters.GradientTransparency(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});b.Image.filters.Grayscale=b.util.createClass(b.Image.filters.BaseFilter,{type:"Grayscale",applyTo:function(a){for(var b,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=d.data,f=d.width*d.height*4,g=0;f>g;)b=(e[g]+e[g+1]+e[g+2])/3,e[g]=b,e[g+1]=b,e[g+2]=b,g+=4;c.putImageData(d,0,0)}}),b.Image.filters.Grayscale.fromObject=function(){return new b.Image.filters.Grayscale}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});b.Image.filters.Invert=b.util.createClass(b.Image.filters.BaseFilter,{type:"Invert",applyTo:function(a){var b,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=d.data,f=e.length;for(b=0;f>b;b+=4)e[b]=255-e[b],e[b+1]=255-e[b+1],e[b+2]=255-e[b+2];c.putImageData(d,0,0)}}),b.Image.filters.Invert.fromObject=function(){return new b.Image.filters.Invert}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Mask=b.util.createClass(b.Image.filters.BaseFilter,{type:"Mask",initialize:function(a){a=a||{},this.mask=a.mask,this.channel=[0,1,2,3].indexOf(a.channel)>-1?a.channel:0},applyTo:function(a){if(this.mask){var c,d=a.getContext("2d"),e=d.getImageData(0,0,a.width,a.height),f=e.data,g=this.mask.getElement(),h=b.util.createCanvasElement(),i=this.channel,j=e.width*e.height*4;h.width=g.width,h.height=g.height,h.getContext("2d").drawImage(g,0,0,g.width,g.height);var k=h.getContext("2d").getImageData(0,0,g.width,g.height),l=k.data;for(c=0;j>c;c+=4)f[c+3]=l[c+i];d.putImageData(e,0,0)}},toObject:function(){return c(this.callSuper("toObject"),{mask:this.mask.toObject(),channel:this.channel})}}),b.Image.filters.Mask.fromObject=function(a,c){b.util.loadImage(a.mask.src,function(d){a.mask=new b.Image(d,a.mask),c&&c(new b.Image.filters.Mask(a))})},b.Image.filters.Mask.async=!0}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Noise=b.util.createClass(b.Image.filters.BaseFilter,{type:"Noise",initialize:function(a){a=a||{},this.noise=a.noise||0},applyTo:function(a){for(var b,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=d.data,f=this.noise,g=0,h=e.length;h>g;g+=4)b=(.5-Math.random())*f,e[g]+=b,e[g+1]+=b,e[g+2]+=b;c.putImageData(d,0,0)},toObject:function(){return c(this.callSuper("toObject"),{noise:this.noise})}}),b.Image.filters.Noise.fromObject=function(a){return new b.Image.filters.Noise(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Pixelate=b.util.createClass(b.Image.filters.BaseFilter,{type:"Pixelate",initialize:function(a){a=a||{},this.blocksize=a.blocksize||4},applyTo:function(a){var b,c,d,e,f,g,h,i=a.getContext("2d"),j=i.getImageData(0,0,a.width,a.height),k=j.data,l=j.height,m=j.width;for(c=0;l>c;c+=this.blocksize)for(d=0;m>d;d+=this.blocksize){b=4*c*m+4*d,e=k[b],f=k[b+1],g=k[b+2],h=k[b+3];for(var n=c,o=c+this.blocksize;o>n;n++)for(var p=d,q=d+this.blocksize;q>p;p++)b=4*n*m+4*p,k[b]=e,k[b+1]=f,k[b+2]=g,k[b+3]=h}i.putImageData(j,0,0)},toObject:function(){return c(this.callSuper("toObject"),{blocksize:this.blocksize})}}),b.Image.filters.Pixelate.fromObject=function(a){return new b.Image.filters.Pixelate(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.RemoveWhite=b.util.createClass(b.Image.filters.BaseFilter,{type:"RemoveWhite",initialize:function(a){a=a||{},this.threshold=a.threshold||30,this.distance=a.distance||20},applyTo:function(a){for(var b,c,d,e=a.getContext("2d"),f=e.getImageData(0,0,a.width,a.height),g=f.data,h=this.threshold,i=this.distance,j=255-h,k=Math.abs,l=0,m=g.length;m>l;l+=4)b=g[l],c=g[l+1],d=g[l+2],b>j&&c>j&&d>j&&k(b-c)b;b+=4)c=.3*f[b]+.59*f[b+1]+.11*f[b+2],f[b]=c+100,f[b+1]=c+50,f[b+2]=c+255;d.putImageData(e,0,0)}}),b.Image.filters.Sepia.fromObject=function(){return new b.Image.filters.Sepia}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});b.Image.filters.Sepia2=b.util.createClass(b.Image.filters.BaseFilter,{type:"Sepia2",applyTo:function(a){var b,c,d,e,f=a.getContext("2d"),g=f.getImageData(0,0,a.width,a.height),h=g.data,i=h.length;for(b=0;i>b;b+=4)c=h[b],d=h[b+1],e=h[b+2],h[b]=(.393*c+.769*d+.189*e)/1.351,h[b+1]=(.349*c+.686*d+.168*e)/1.203,h[b+2]=(.272*c+.534*d+.131*e)/2.14;f.putImageData(g,0,0)}}),b.Image.filters.Sepia2.fromObject=function(){return new b.Image.filters.Sepia2}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Tint=b.util.createClass(b.Image.filters.BaseFilter,{type:"Tint",initialize:function(a){a=a||{},this.color=a.color||"#000000",this.opacity="undefined"!=typeof a.opacity?a.opacity:new b.Color(this.color).getAlpha()},applyTo:function(a){var c,d,e,f,g,h,i,j,k,l=a.getContext("2d"),m=l.getImageData(0,0,a.width,a.height),n=m.data,o=n.length;for(k=new b.Color(this.color).getSource(),d=k[0]*this.opacity,e=k[1]*this.opacity,f=k[2]*this.opacity,j=1-this.opacity,c=0;o>c;c+=4)g=n[c],h=n[c+1],i=n[c+2],n[c]=d+g*j,n[c+1]=e+h*j,n[c+2]=f+i*j;l.putImageData(m,0,0)},toObject:function(){return c(this.callSuper("toObject"),{color:this.color,opacity:this.opacity})}}),b.Image.filters.Tint.fromObject=function(a){return new b.Image.filters.Tint(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Multiply=b.util.createClass(b.Image.filters.BaseFilter,{type:"Multiply",initialize:function(a){a=a||{},this.color=a.color||"#000000"},applyTo:function(a){var c,d,e=a.getContext("2d"),f=e.getImageData(0,0,a.width,a.height),g=f.data,h=g.length;for(d=new b.Color(this.color).getSource(),c=0;h>c;c+=4)g[c]*=d[0]/255,g[c+1]*=d[1]/255,g[c+2]*=d[2]/255;e.putImageData(f,0,0)},toObject:function(){return c(this.callSuper("toObject"),{color:this.color})}}),b.Image.filters.Multiply.fromObject=function(a){return new b.Image.filters.Multiply(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric;b.Image.filters.Blend=b.util.createClass({type:"Blend",initialize:function(a){a=a||{},this.color=a.color||"#000",this.image=a.image||!1,this.mode=a.mode||"multiply",this.alpha=a.alpha||1},applyTo:function(a){var c,d,e,f,g,h,i,j=a.getContext("2d"),k=j.getImageData(0,0,a.width,a.height),l=k.data,m=!1;if(this.image){m=!0;var n=b.util.createCanvasElement();n.width=this.image.width,n.height=this.image.height;var o=new b.StaticCanvas(n);o.add(this.image);var p=o.getContext("2d");i=p.getImageData(0,0,o.width,o.height).data}else i=new b.Color(this.color).getSource(),c=i[0]*this.alpha,d=i[1]*this.alpha,e=i[2]*this.alpha;for(var q=0,r=l.length;r>q;q+=4)switch(f=l[q],g=l[q+1],h=l[q+2],m&&(c=i[q]*this.alpha,d=i[q+1]*this.alpha,e=i[q+2]*this.alpha),this.mode){case"multiply":l[q]=f*c/255,l[q+1]=g*d/255,l[q+2]=h*e/255;break;case"screen":l[q]=1-(1-f)*(1-c),l[q+1]=1-(1-g)*(1-d),l[q+2]=1-(1-h)*(1-e);break;case"add":l[q]=Math.min(255,f+c),l[q+1]=Math.min(255,g+d),l[q+2]=Math.min(255,h+e);break;case"diff":case"difference":l[q]=Math.abs(f-c),l[q+1]=Math.abs(g-d),l[q+2]=Math.abs(h-e);break;case"subtract":var s=f-c,t=g-d,u=h-e;l[q]=0>s?0:s,l[q+1]=0>t?0:t,l[q+2]=0>u?0:u;break;case"darken":l[q]=Math.min(f,c),l[q+1]=Math.min(g,d),l[q+2]=Math.min(h,e);break;case"lighten":l[q]=Math.max(f,c),l[q+1]=Math.max(g,d),l[q+2]=Math.max(h,e)}j.putImageData(k,0,0)}}),b.Image.filters.Blend.fromObject=function(a){return new b.Image.filters.Blend(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.object.clone,e=b.util.toFixed,f=b.StaticCanvas.supports("setLineDash");if(b.Text)return void b.warn("fabric.Text is already defined");var g=b.Object.prototype.stateProperties.concat();g.push("fontFamily","fontWeight","fontSize","text","textDecoration","textAlign","fontStyle","lineHeight","textBackgroundColor","useNative","path"),b.Text=b.util.createClass(b.Object,{_dimensionAffectingProps:{fontSize:!0,fontWeight:!0,fontFamily:!0,textDecoration:!0,fontStyle:!0,lineHeight:!0,stroke:!0,strokeWidth:!0,text:!0},_reNewline:/\r?\n/,type:"text",fontSize:40,fontWeight:"normal",fontFamily:"Times New Roman",textDecoration:"",textAlign:"left",fontStyle:"",lineHeight:1.3,textBackgroundColor:"",path:null,useNative:!0,stateProperties:g,stroke:null,shadow:null,initialize:function(a,b){b=b||{},this.text=a,this.__skipDimension=!0,this.setOptions(b),this.__skipDimension=!1,this._initDimensions()},_initDimensions:function(){if(!this.__skipDimension){var a=b.util.createCanvasElement();this._render(a.getContext("2d"))}},toString:function(){return"#'},_render:function(a){"undefined"==typeof Cufon||this.useNative===!0?this._renderViaNative(a):this._renderViaCufon(a)},_renderViaNative:function(a){var c=this.text.split(this._reNewline);this._setTextStyles(a),this.width=this._getTextWidth(a,c),this.height=this._getTextHeight(a,c),this.clipTo&&b.util.clipContext(this,a),this._renderTextBackground(a,c),this._translateForTextAlign(a),this._renderText(a,c),"left"!==this.textAlign&&"justify"!==this.textAlign&&a.restore(),this._renderTextDecoration(a,c),this.clipTo&&a.restore(),this._setBoundaries(a,c),this._totalLineHeight=0},_renderText:function(a,b){a.save(),this._setShadow(a),this._setupFillRule(a),this._renderTextFill(a,b),this._renderTextStroke(a,b),this._restoreFillRule(a),this._removeShadow(a),a.restore()},_translateForTextAlign:function(a){"left"!==this.textAlign&&"justify"!==this.textAlign&&(a.save(),a.translate("center"===this.textAlign?this.width/2:this.width,0))},_setBoundaries:function(a,b){this._boundaries=[];for(var c=0,d=b.length;d>c;c++){var e=this._getLineWidth(a,b[c]),f=this._getLineLeftOffset(e);this._boundaries.push({height:this.fontSize*this.lineHeight,width:e,left:f})}},_setTextStyles:function(a){this._setFillStyles(a),this._setStrokeStyles(a),a.textBaseline="alphabetic",this.skipTextAlign||(a.textAlign=this.textAlign),a.font=this._getFontDeclaration()},_getTextHeight:function(a,b){return this.fontSize*b.length*this.lineHeight},_getTextWidth:function(a,b){for(var c=a.measureText(b[0]||"|").width,d=1,e=b.length;e>d;d++){var f=a.measureText(b[d]).width;f>c&&(c=f)}return c},_renderChars:function(a,b,c,d,e){b[a](c,d,e)},_renderTextLine:function(a,b,c,d,e,f){if(e-=this.fontSize/4,"justify"!==this.textAlign)return void this._renderChars(a,b,c,d,e,f);var g=b.measureText(c).width,h=this.width;if(h>g)for(var i=c.split(/\s+/),j=b.measureText(c.replace(/\s+/g,"")).width,k=h-j,l=i.length-1,m=k/l,n=0,o=0,p=i.length;p>o;o++)this._renderChars(a,b,i[o],d+n,e,f),n+=b.measureText(i[o]).width+m;else this._renderChars(a,b,c,d,e,f)},_getLeftOffset:function(){return b.isLikelyNode?0:-this.width/2},_getTopOffset:function(){return-this.height/2},_renderTextFill:function(a,b){if(this.fill||this._skipFillStrokeCheck){this._boundaries=[];for(var c=0,d=0,e=b.length;e>d;d++){var f=this._getHeightOfLine(a,d,b);c+=f,this._renderTextLine("fillText",a,b[d],this._getLeftOffset(),this._getTopOffset()+c,d)}}},_renderTextStroke:function(a,b){if(this.stroke&&0!==this.strokeWidth||this._skipFillStrokeCheck){var c=0;a.save(),this.strokeDashArray&&(1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),f&&a.setLineDash(this.strokeDashArray)),a.beginPath();for(var d=0,e=b.length;e>d;d++){var g=this._getHeightOfLine(a,d,b);c+=g,this._renderTextLine("strokeText",a,b[d],this._getLeftOffset(),this._getTopOffset()+c,d)}a.closePath(),a.restore()}},_getHeightOfLine:function(){return this.fontSize*this.lineHeight},_renderTextBackground:function(a,b){this._renderTextBoxBackground(a),this._renderTextLinesBackground(a,b)},_renderTextBoxBackground:function(a){this.backgroundColor&&(a.save(),a.fillStyle=this.backgroundColor,a.fillRect(this._getLeftOffset(),this._getTopOffset(),this.width,this.height),a.restore())},_renderTextLinesBackground:function(a,b){if(this.textBackgroundColor){a.save(),a.fillStyle=this.textBackgroundColor;for(var c=0,d=b.length;d>c;c++)if(""!==b[c]){var e=this._getLineWidth(a,b[c]),f=this._getLineLeftOffset(e);a.fillRect(this._getLeftOffset()+f,this._getTopOffset()+c*this.fontSize*this.lineHeight,e,this.fontSize*this.lineHeight)}a.restore()}},_getLineLeftOffset:function(a){return"center"===this.textAlign?(this.width-a)/2:"right"===this.textAlign?this.width-a:0},_getLineWidth:function(a,b){return"justify"===this.textAlign?this.width:a.measureText(b).width},_renderTextDecoration:function(a,b){function c(c){for(var f=0,g=b.length;g>f;f++){var h=e._getLineWidth(a,b[f]),i=e._getLineLeftOffset(h);a.fillRect(e._getLeftOffset()+i,~~(c+f*e._getHeightOfLine(a,f,b)-d),h,1)}}if(this.textDecoration){var d=this._getTextHeight(a,b)/2,e=this;this.textDecoration.indexOf("underline")>-1&&c(this.fontSize*this.lineHeight),this.textDecoration.indexOf("line-through")>-1&&c(this.fontSize*this.lineHeight-this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&c(this.fontSize*this.lineHeight-this.fontSize)}},_getFontDeclaration:function(){return[b.isLikelyNode?this.fontWeight:this.fontStyle,b.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",b.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(a,b){if(this.visible){a.save(),this._transform(a,b);var c=this.transformMatrix,d=this.group&&"path-group"===this.group.type;d&&a.translate(-this.group.width/2,-this.group.height/2),c&&a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),d&&a.translate(this.left,this.top),this._render(a),a.restore()}},toObject:function(a){var b=c(this.callSuper("toObject",a),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});return this.includeDefaultValues||this._removeDefaultValues(b),b},toSVG:function(a){var b=[],c=this.text.split(this._reNewline),d=this._getSVGLeftTopOffsets(c),e=this._getSVGTextAndBg(d.lineTop,d.textLeft,c),f=this._getSVGShadows(d.lineTop,c);return d.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,this._wrapSVGTextAndBg(b,e,f,d),a?a(b.join("")):b.join("") -},_getSVGLeftTopOffsets:function(a){var b=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,c=-(this.width/2),d=this.useNative?this.fontSize-1:this.height/2-a.length*this.fontSize-this._totalLineHeight;return{textLeft:c+(this.group&&"path-group"===this.group.type?this.left:0),textTop:d+(this.group&&"path-group"===this.group.type?this.top:0),lineTop:b}},_wrapSVGTextAndBg:function(a,b,c,d){a.push('\n',b.textBgRects.join(""),"',c.join(""),b.textSpans.join(""),"\n","\n")},_getSVGShadows:function(a,c){var d,f,g=[],h=1;if(!this.shadow||!this._boundaries)return g;for(d=0,f=c.length;f>d;d++)if(""!==c[d]){var i=this._boundaries&&this._boundaries[d]?this._boundaries[d].left:0;g.push('",b.util.string.escapeXml(c[d]),""),h=1}else h++;return g},_getSVGTextAndBg:function(a,b,c){var d=[],e=[],f=1;this._setSVGBg(e);for(var g=0,h=c.length;h>g;g++)""!==c[g]?(this._setSVGTextLineText(c[g],g,d,a,f,e),f=1):f++,this.textBackgroundColor&&this._boundaries&&this._setSVGTextLineBg(e,g,b,a);return{textSpans:d,textBgRects:e}},_setSVGTextLineText:function(a,c,d,f,g){var h=this._boundaries&&this._boundaries[c]?e(this._boundaries[c].left,2):0;d.push('",b.util.string.escapeXml(a),"")},_setSVGTextLineBg:function(a,b,c,d){a.push("\n')},_setSVGBg:function(a){this.backgroundColor&&this._boundaries&&a.push("')},_getFillAttributes:function(a){var c=a&&"string"==typeof a?new b.Color(a):"";return c&&c.getSource()&&1!==c.getAlpha()?'opacity="'+c.getAlpha()+'" fill="'+c.setAlpha(1).toRgb()+'"':'fill="'+a+'"'},_set:function(a,b){"fontFamily"===a&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+b+"$3")),this.callSuper("_set",a,b),a in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),b.Text.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),b.Text.DEFAULT_SVG_FONT_SIZE=16,b.Text.fromElement=function(a,c){if(!a)return null;var d=b.parseAttributes(a,b.Text.ATTRIBUTE_NAMES);c=b.util.object.extend(c?b.util.object.clone(c):{},d),"dx"in d&&(c.left+=d.dx),"dy"in d&&(c.top+=d.dy),"fontSize"in c||(c.fontSize=b.Text.DEFAULT_SVG_FONT_SIZE),c.originX||(c.originX="left");var e=new b.Text(a.textContent,c),f=0;return"left"===e.originX&&(f=e.getWidth()/2),"right"===e.originX&&(f=-e.getWidth()/2),e.set({left:e.getLeft()+f,top:e.getTop()-e.getHeight()/2}),e},b.Text.fromObject=function(a){return new b.Text(a.text,d(a))},b.util.createAccessors(b.Text)}("undefined"!=typeof exports?exports:this)}).call({},window,document,html2canvas); \ No newline at end of file +(function(){}).call({},window,document,html2canvas); \ No newline at end of file diff --git a/src/core.js b/src/core.js index 0d3cee2..953c41f 100644 --- a/src/core.js +++ b/src/core.js @@ -124,6 +124,7 @@ function createWindowClone(ownerDocument, containerDocument, width, height, opti var documentElement = ownerDocument.documentElement.cloneNode(true), container = containerDocument.createElement("iframe"); + container.className = "html2canvas-container"; container.style.visibility = "hidden"; container.style.position = "absolute"; container.style.left = container.style.top = "-10000px";