Date.now()?this.asyncRenderer(a,b,c):setTimeout(cb(function(){this.asyncRenderer(a,b)},this),0)},E.prototype.createPseudoHideStyles=function(a){var b=a.createElement("style");b.innerHTML="."+this.pseudoHideClass+':before { content: "" !important; display: none !important; }.'+this.pseudoHideClass+':after { content: "" !important; display: none !important; }',a.body.appendChild(b)},E.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&&(a.node.insertBefore(c[0].node,a.node.firstChild),b.push(c)),d&&(a.node.appendChild(d[0].node),b.push(d)),(c||d)&&(a.node.className+=" "+this.pseudoHideClass)}return gb(b)},E.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=hb(d.content),f="url"===e.substr(0,3),g=b.createElement(f?"img":"html2canvaspseudoelement"),h=new v(g,a),i=d.length-1;i>=0;i--){var j=F(d.item(i));g.style[j]=d[j]}if(g.className=this.pseudoHideClass,f)return g.src=z(e)[0].args[0],[h];var k=b.createTextNode(e);return g.appendChild(k),[h,new nb(k,h)]},E.prototype.getChildren=function(a){return gb([].filter.call(a.node.childNodes,U).map(function(b){var c=[b.nodeType===Node.TEXT_NODE?new nb(b,a):new v(b,a)].filter(fb);return b.nodeType===Node.ELEMENT_NODE&&c.length&&"TEXTAREA"!==b.tagName?c[0].isElementVisible()?c.concat(this.getChildren(c[0])):[]:c},this))},E.prototype.newStackingContext=function(a,b){var c=new kb(b,a.cssFloat("opacity"),a.node,a.parent);c.visible=a.visible;var d=b?c.getParentStack(this):c.parent.stack;d.contexts.push(c),a.stack=c},E.prototype.createStackingContexts=function(){this.nodes.forEach(function(a){$(a)&&(this.isRootElement(a)||bb(a)||V(a)||this.isBodyWithTransparentRoot(a)||a.hasTransform())?this.newStackingContext(a,!0):$(a)&&(W(a)&&O(a)||Y(a)||X(a))?this.newStackingContext(a,!1):a.assignStack(a.parent.stack)},this)},E.prototype.isBodyWithTransparentRoot=function(a){return"BODY"===a.node.nodeName&&this.renderer.isTransparent(a.parent.css("backgroundColor"))},E.prototype.isRootElement=function(a){return null===a.parent},E.prototype.sortStackingContexts=function(a){a.contexts.sort(ab),a.contexts.forEach(this.sortStackingContexts,this)},E.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{}}},E.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?D(c):C(c);return d.replaceChild(e,c),f},E.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()},E.prototype.parse=function(a){var b=a.contexts.filter(M),c=a.children.filter($),d=c.filter(Z(X)),e=d.filter(Z(W)).filter(Z(P)),f=c.filter(Z(W)).filter(X),g=d.filter(Z(W)).filter(P),h=a.contexts.concat(d.filter(W)).filter(O),i=a.children.filter(_).filter(R),j=a.contexts.filter(N);b.concat(e).concat(f).concat(g).concat(h).concat(i).concat(j).forEach(function(a){this.renderQueue.push(a),Q(a)&&(this.parse(a),this.renderQueue.push(new G))},this)},E.prototype.paint=function(a){try{a instanceof G?this.renderer.ctx.restore():_(a)?this.paintText(a):this.paintNode(a)}catch(b){u(b)}},E.prototype.paintNode=function(a){Q(a)&&(this.renderer.setOpacity(a.opacity),this.renderer.ctx.save(),a.hasTransform()&&this.renderer.setTransform(a.parseTransform()));var b=a.parseBounds(),c=this.parseBorders(a);switch(this.renderer.clip(c.clip,function(){this.renderer.renderBackground(a,b,c.borders.map(eb))},this),this.renderer.renderBorders(c.borders),a.node.nodeName){case"IMG":var d=this.images.get(a.node.src);d?this.renderer.renderImage(a,b,c,d):u("Error loading
",a.node.src);break;case"SELECT":case"INPUT":case"TEXTAREA":this.paintFormValue(a)}},E.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){u("html2canvas: Parse: Exception caught in renderFormValue: "+d.message)}});var e=a.parseBounds();c.style.position="absolute",c.style.left=e.left+"px",c.style.top=e.top+"px",c.textContent=a.getValue(),b.body.appendChild(c),this.paintText(new nb(c.firstChild,a)),b.body.removeChild(c)}},E.prototype.paintText=function(a){a.applyTextTransform();var b=a.node.data.split(!this.options.letterRendering||S(a)?/(\b| )/:""),c=a.parent.fontWeight(),d=a.parent.css("fontSize"),e=a.parent.css("fontFamily"),f=a.parent.parseTextShadows();this.renderer.font(a.parent.css("color"),a.parent.css("fontStyle"),a.parent.css("fontVariant"),c,d,e),f.length?this.renderer.fontShadow(f[0].color,f[0].offsetX,f[0].offsetY,f[0].blur):this.renderer.clearShadow(),b.map(this.parseTextBounds(a),this).forEach(function(c,f){c&&(this.renderer.text(b[f],c.left,c.bottom),this.renderTextDecoration(a.parent,c,this.fontMetrics.getMetrics(e,d)))
+},this)},E.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"))}},E.prototype.parseBorders=function(a){var b=a.bounds,c=T(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=I(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=K({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=K({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=K({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=K({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})}},E.prototype.parseBackgroundClip=function(a,b,c,d,e){var f=a.css("backgroundClip"),g=[];switch(f){case"content-box":case"padding-box":L(g,d[0],d[1],b.topLeftInner,b.topRightInner,e.left+c[3].width,e.top+c[0].width),L(g,d[1],d[2],b.topRightInner,b.bottomRightInner,e.left+e.width-c[1].width,e.top+c[0].width),L(g,d[2],d[3],b.bottomRightInner,b.bottomLeftInner,e.left+e.width-c[1].width,e.top+e.height-c[2].width),L(g,d[3],d[0],b.bottomLeftInner,b.topLeftInner,e.left+c[3].width,e.top+e.height-c[2].width);break;default:L(g,d[0],d[1],b.topLeftOuter,b.topRightOuter,e.left,e.top),L(g,d[1],d[2],b.topRightOuter,b.bottomRightOuter,e.left+e.width,e.top),L(g,d[2],d[3],b.bottomRightOuter,b.bottomLeftOuter,e.left+e.width,e.top+e.height),L(g,d[3],d[0],b.bottomLeftOuter,b.topLeftOuter,e.left,e.top+e.height)}return g},E.prototype.pseudoHideClass="___html2canvas___pseudoelement";var tb=0;jb.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;this.drawImage(d,0,0,d.image.width,d.image.height,b.left+e+i[3].width,b.top+f+i[0].width,b.width-(i[1].width+i[3].width+e+g),b.height-(i[0].width+i[2].width+f+h))},jb.prototype.renderBackground=function(a,b,c){b.height>0&&b.width>0&&(this.renderBackgroundColor(a,b),this.renderBackgroundImage(a,b,c))},jb.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"))},jb.prototype.renderBorders=function(a){a.forEach(this.renderBorder,this)},jb.prototype.renderBorder=function(a){this.isTransparent(a.color)||null===a.args||this.drawShape(a.args,a.color)},jb.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):u("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):u("Error loading background-image",d.args[0]);break;case"none":break;default:u("Unknown background-image type",d.args[0])}},this)},jb.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,c.image.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],c.image.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],c.image.width,c.image.height,e);break;default:this.renderBackgroundRepeat(c,g,f,{top:b.top,left:b.left},e[3],e[0])}},jb.prototype.isTransparent=function(a){return!a||"transparent"===a||"rgba(0, 0, 0, 0)"===a},kb.prototype=Object.create(v.prototype),kb.prototype.getParentStack=function(a){var b=this.parent?this.parent.stack:null;return b?b.ownStacking?b:b.getParentStack(a):a.stack},lb.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},lb.prototype.testCORS=function(){return"undefined"!=typeof(new Image).crossOrigin},lb.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},nb.prototype=Object.create(v.prototype),nb.prototype.applyTextTransform=function(){this.node.data=this.transform(this.parent.css("textTransform"))},nb.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,ob);case"uppercase":return b.toUpperCase();default:return b}},pb.prototype=Object.create(o.prototype),rb.prototype=Object.create(jb.prototype),rb.prototype.setFillStyle=function(a){return this.ctx.fillStyle=a,this.ctx},rb.prototype.rectangle=function(a,b,c,d,e){this.setFillStyle(e).fillRect(a,b,c,d)},rb.prototype.drawShape=function(a,b){this.shape(a),this.setFillStyle(b).fill()},rb.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},rb.prototype.drawImage=function(a,b,c,d,e,f,g,h,i){this.taints(a)||this.ctx.drawImage(a.image,b,c,d,e,f,g,h,i)},rb.prototype.clip=function(a,b,c){this.ctx.save(),this.shape(a).clip(),b.call(c),this.ctx.restore()},rb.prototype.shape=function(a){return this.ctx.beginPath(),a.forEach(function(a,b){this.ctx[0===b?"moveTo":a[0]+"To"].apply(this.ctx,a.slice(1))},this),this.ctx.closePath(),this.ctx},rb.prototype.font=function(a,b,c,d,e,f){this.setFillStyle(a).font=[b,c,d,e,f].join(" ")},rb.prototype.fontShadow=function(a,b,c,d){this.setVariable("shadowColor",a).setVariable("shadowOffsetY",b).setVariable("shadowOffsetX",c).setVariable("shadowBlur",d)},rb.prototype.clearShadow=function(){this.setVariable("shadowColor","rgba(0,0,0,0)")},rb.prototype.setOpacity=function(a){this.ctx.globalAlpha=a},rb.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])},rb.prototype.setVariable=function(a,b){return this.variables[a]!==b&&(this.variables[a]=this.ctx[a]=b),this},rb.prototype.text=function(a,b,c){this.ctx.fillText(a,b,c)},rb.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)},rb.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)},rb.prototype.renderBackgroundGradient=function(a,b){if(a instanceof t){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)}},rb.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}}(window,document);
\ No newline at end of file
diff --git a/dist/html2canvas.svg.js b/dist/html2canvas.svg.js
index 056e309..8583a79 100644
--- a/dist/html2canvas.svg.js
+++ b/dist/html2canvas.svg.js
@@ -7,7 +7,7 @@
(function(window, document, exports, undefined){
-/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` */
+/* 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" };
@@ -61,6 +61,1725 @@ fabric.SHARED_ATTRIBUTES = [
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(){
/**
@@ -1035,173 +2754,7 @@ fabric.Collection = {
var slice = Array.prototype.slice;
- /* _ES5_COMPAT_START_ */
-
- if (!Array.prototype.indexOf) {
- /**
- * Finds index of an element in an array
- * @param {Any} searchElement
- * @param {Number} [fromIndex]
- * @return {Number}
- */
- Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
- if (this === void 0 || this === null) {
- throw new TypeError();
- }
- var t = Object(this), len = t.length >>> 0;
- if (len === 0) {
- return -1;
- }
- var n = 0;
- if (arguments.length > 0) {
- n = Number(arguments[1]);
- if (n !== n) { // shortcut for verifying if it's NaN
- n = 0;
- }
- else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
- }
- }
- if (n >= len) {
- return -1;
- }
- var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
- for (; k < len; k++) {
- if (k in t && t[k] === searchElement) {
- return k;
- }
- }
- return -1;
- };
- }
-
- if (!Array.prototype.forEach) {
- /**
- * Iterates an array, invoking callback for each element
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Array}
- */
- Array.prototype.forEach = function(fn, context) {
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this) {
- fn.call(context, this[i], i, this);
- }
- }
- };
- }
-
- if (!Array.prototype.map) {
- /**
- * Returns a result of iterating over an array, invoking callback for each element
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Array}
- */
- Array.prototype.map = function(fn, context) {
- var result = [ ];
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this) {
- result[i] = fn.call(context, this[i], i, this);
- }
- }
- return result;
- };
- }
-
- if (!Array.prototype.every) {
- /**
- * Returns true if a callback returns truthy value for all elements in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Boolean}
- */
- Array.prototype.every = function(fn, context) {
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this && !fn.call(context, this[i], i, this)) {
- return false;
- }
- }
- return true;
- };
- }
-
- if (!Array.prototype.some) {
- /**
- * Returns true if a callback returns truthy value for at least one element in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Boolean}
- */
- Array.prototype.some = function(fn, context) {
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this && fn.call(context, this[i], i, this)) {
- return true;
- }
- }
- return false;
- };
- }
-
- if (!Array.prototype.filter) {
- /**
- * Returns the result of iterating over elements in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Array}
- */
- Array.prototype.filter = function(fn, context) {
- var result = [ ], val;
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this) {
- val = this[i]; // in case fn mutates this
- if (fn.call(context, val, i, this)) {
- result.push(val);
- }
- }
- }
- return result;
- };
- }
-
- if (!Array.prototype.reduce) {
- /**
- * Returns "folded" (reduced) result of iterating over elements in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [initial] Object to use as the first argument to the first call of the callback
- * @return {Any}
- */
- Array.prototype.reduce = function(fn /*, initial*/) {
- var len = this.length >>> 0,
- i = 0,
- rv;
-
- if (arguments.length > 1) {
- rv = arguments[1];
- }
- else {
- do {
- if (i in this) {
- rv = this[i++];
- break;
- }
- // if array contains no values, no initial value to return
- if (++i >= len) {
- throw new TypeError();
- }
- }
- while (true);
- }
- for (; i < len; i++) {
- if (i in this) {
- rv = fn.call(null, rv, this[i], i, this);
- }
- }
- return rv;
- };
- }
-
- /* _ES5_COMPAT_END_ */
+
/**
* Invokes method on all items in a given array
@@ -1321,19 +2874,7 @@ fabric.Collection = {
(function() {
- /* _ES5_COMPAT_START_ */
- if (!String.prototype.trim) {
- /**
- * Trims a string (removing whitespace from the beginning and the end)
- * @function external:String#trim
- * @see String#trim on MDN
- */
- String.prototype.trim = function () {
- // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
- return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
- };
- }
- /* _ES5_COMPAT_END_ */
+
/**
* Camelizes a string
@@ -1387,43 +2928,7 @@ fabric.Collection = {
}());
-/* _ES5_COMPAT_START_ */
-(function() {
- var slice = Array.prototype.slice,
- apply = Function.prototype.apply,
- Dummy = function() { };
-
- if (!Function.prototype.bind) {
- /**
- * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
- * @see Function#bind on MDN
- * @param {Object} thisArg Object to bind function to
- * @param {Any[]} [...] Values to pass to a bound function
- * @return {Function}
- */
- Function.prototype.bind = function(thisArg) {
- var _this = this, args = slice.call(arguments, 1), bound;
- if (args.length) {
- bound = function() {
- return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
- };
- }
- else {
- /** @ignore */
- bound = function() {
- return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments);
- };
- }
- Dummy.prototype = this.prototype;
- bound.prototype = new Dummy();
-
- return bound;
- };
- }
-
-})();
-/* _ES5_COMPAT_END_ */
(function() {
@@ -2229,473 +3734,6 @@ if (typeof console !== 'undefined') {
}
-(function() {
-
- /**
- * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
- * @memberOf fabric.util
- * @param {Object} [options] Animation options
- * @param {Function} [options.onChange] Callback; invoked on every value change
- * @param {Function} [options.onComplete] Callback; invoked when value change is completed
- * @param {Number} [options.startValue=0] Starting value
- * @param {Number} [options.endValue=100] Ending value
- * @param {Number} [options.byValue=100] Value to modify the property by
- * @param {Function} [options.easing] Easing function
- * @param {Number} [options.duration=500] Duration of change (in ms)
- */
- function animate(options) {
-
- requestAnimFrame(function(timestamp) {
- options || (options = { });
-
- var start = timestamp || +new Date(),
- duration = options.duration || 500,
- finish = start + duration, time,
- onChange = options.onChange || function() { },
- abort = options.abort || function() { return false; },
- easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;},
- startValue = 'startValue' in options ? options.startValue : 0,
- endValue = 'endValue' in options ? options.endValue : 100,
- byValue = options.byValue || endValue - startValue;
-
- options.onStart && options.onStart();
-
- (function tick(ticktime) {
- time = ticktime || +new Date();
- var currentTime = time > finish ? duration : (time - start);
- if (abort()) {
- options.onComplete && options.onComplete();
- return;
- }
- onChange(easing(currentTime, startValue, byValue, duration));
- if (time > finish) {
- options.onComplete && options.onComplete();
- return;
- }
- requestAnimFrame(tick);
- })(start);
- });
-
- }
-
- var _requestAnimFrame = fabric.window.requestAnimationFrame ||
- fabric.window.webkitRequestAnimationFrame ||
- fabric.window.mozRequestAnimationFrame ||
- fabric.window.oRequestAnimationFrame ||
- fabric.window.msRequestAnimationFrame ||
- function(callback) {
- fabric.window.setTimeout(callback, 1000 / 60);
- };
- /**
- * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
- * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
- * @memberOf fabric.util
- * @param {Function} callback Callback to invoke
- * @param {DOMElement} element optional Element to associate with animation
- */
- function requestAnimFrame() {
- return _requestAnimFrame.apply(fabric.window, arguments);
- }
-
- fabric.util.animate = animate;
- fabric.util.requestAnimFrame = requestAnimFrame;
-
-})();
-
-
-(function() {
-
- function normalize(a, c, p, s) {
- if (a < Math.abs(c)) {
- a = c;
- s = p / 4;
- }
- else {
- s = p / (2 * Math.PI) * Math.asin(c / a);
- }
- return { a: a, c: c, p: p, s: s };
- }
-
- function elastic(opts, t, d) {
- return opts.a *
- Math.pow(2, 10 * (t -= 1)) *
- Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
- }
-
- /**
- * Cubic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutCubic(t, b, c, d) {
- return c * ((t = t / d - 1) * t * t + 1) + b;
- }
-
- /**
- * Cubic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutCubic(t, b, c, d) {
- t /= d/2;
- if (t < 1) {
- return c / 2 * t * t * t + b;
- }
- return c / 2 * ((t -= 2) * t * t + 2) + b;
- }
-
- /**
- * Quartic easing in
- * @memberOf fabric.util.ease
- */
- function easeInQuart(t, b, c, d) {
- return c * (t /= d) * t * t * t + b;
- }
-
- /**
- * Quartic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutQuart(t, b, c, d) {
- return -c * ((t = t / d - 1) * t * t * t - 1) + b;
- }
-
- /**
- * Quartic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutQuart(t, b, c, d) {
- t /= d / 2;
- if (t < 1) {
- return c / 2 * t * t * t * t + b;
- }
- return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
- }
-
- /**
- * Quintic easing in
- * @memberOf fabric.util.ease
- */
- function easeInQuint(t, b, c, d) {
- return c * (t /= d) * t * t * t * t + b;
- }
-
- /**
- * Quintic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutQuint(t, b, c, d) {
- return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
- }
-
- /**
- * Quintic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutQuint(t, b, c, d) {
- t /= d / 2;
- if (t < 1) {
- return c / 2 * t * t * t * t * t + b;
- }
- return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
- }
-
- /**
- * Sinusoidal easing in
- * @memberOf fabric.util.ease
- */
- function easeInSine(t, b, c, d) {
- return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
- }
-
- /**
- * Sinusoidal easing out
- * @memberOf fabric.util.ease
- */
- function easeOutSine(t, b, c, d) {
- return c * Math.sin(t / d * (Math.PI / 2)) + b;
- }
-
- /**
- * Sinusoidal easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutSine(t, b, c, d) {
- return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
- }
-
- /**
- * Exponential easing in
- * @memberOf fabric.util.ease
- */
- function easeInExpo(t, b, c, d) {
- return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
- }
-
- /**
- * Exponential easing out
- * @memberOf fabric.util.ease
- */
- function easeOutExpo(t, b, c, d) {
- return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
- }
-
- /**
- * Exponential easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutExpo(t, b, c, d) {
- if (t === 0) {
- return b;
- }
- if (t === d) {
- return b + c;
- }
- t /= d / 2;
- if (t < 1) {
- return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
- }
- return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
- }
-
- /**
- * Circular easing in
- * @memberOf fabric.util.ease
- */
- function easeInCirc(t, b, c, d) {
- return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
- }
-
- /**
- * Circular easing out
- * @memberOf fabric.util.ease
- */
- function easeOutCirc(t, b, c, d) {
- return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
- }
-
- /**
- * Circular easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutCirc(t, b, c, d) {
- t /= d / 2;
- if (t < 1) {
- return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
- }
- return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
- }
-
- /**
- * Elastic easing in
- * @memberOf fabric.util.ease
- */
- function easeInElastic(t, b, c, d) {
- var s = 1.70158, p = 0, a = c;
- if (t === 0) {
- return b;
- }
- t /= d;
- if (t === 1) {
- return b + c;
- }
- if (!p) {
- p = d * 0.3;
- }
- var opts = normalize(a, c, p, s);
- return -elastic(opts, t, d) + b;
- }
-
- /**
- * Elastic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutElastic(t, b, c, d) {
- var s = 1.70158, p = 0, a = c;
- if (t === 0) {
- return b;
- }
- t /= d;
- if (t === 1) {
- return b + c;
- }
- if (!p) {
- p = d * 0.3;
- }
- var opts = normalize(a, c, p, s);
- return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b;
- }
-
- /**
- * Elastic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutElastic(t, b, c, d) {
- var s = 1.70158, p = 0, a = c;
- if (t === 0) {
- return b;
- }
- t /= d / 2;
- if (t === 2) {
- return b + c;
- }
- if (!p) {
- p = d * (0.3 * 1.5);
- }
- var opts = normalize(a, c, p, s);
- if (t < 1) {
- return -0.5 * elastic(opts, t, d) + b;
- }
- return opts.a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b;
- }
-
- /**
- * Backwards easing in
- * @memberOf fabric.util.ease
- */
- function easeInBack(t, b, c, d, s) {
- if (s === undefined) {
- s = 1.70158;
- }
- return c * (t /= d) * t * ((s + 1) * t - s) + b;
- }
-
- /**
- * Backwards easing out
- * @memberOf fabric.util.ease
- */
- function easeOutBack(t, b, c, d, s) {
- if (s === undefined) {
- s = 1.70158;
- }
- return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
- }
-
- /**
- * Backwards easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutBack(t, b, c, d, s) {
- if (s === undefined) {
- s = 1.70158;
- }
- t /= d / 2;
- if (t < 1) {
- return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
- }
- return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
- }
-
- /**
- * Bouncing easing in
- * @memberOf fabric.util.ease
- */
- function easeInBounce(t, b, c, d) {
- return c - easeOutBounce (d - t, 0, c, d) + b;
- }
-
- /**
- * Bouncing easing out
- * @memberOf fabric.util.ease
- */
- function easeOutBounce(t, b, c, d) {
- if ((t /= d) < (1 / 2.75)) {
- return c * (7.5625 * t * t) + b;
- }
- else if (t < (2/2.75)) {
- return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
- }
- else if (t < (2.5/2.75)) {
- return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
- }
- else {
- return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
- }
- }
-
- /**
- * Bouncing easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutBounce(t, b, c, d) {
- if (t < d / 2) {
- return easeInBounce (t * 2, 0, c, d) * 0.5 + b;
- }
- return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
- }
-
- /**
- * Easing functions
- * See Easing Equations by Robert Penner
- * @namespace fabric.util.ease
- */
- fabric.util.ease = {
-
- /**
- * Quadratic easing in
- * @memberOf fabric.util.ease
- */
- easeInQuad: function(t, b, c, d) {
- return c * (t /= d) * t + b;
- },
-
- /**
- * Quadratic easing out
- * @memberOf fabric.util.ease
- */
- easeOutQuad: function(t, b, c, d) {
- return -c * (t /= d) * (t - 2) + b;
- },
-
- /**
- * Quadratic easing in and out
- * @memberOf fabric.util.ease
- */
- easeInOutQuad: function(t, b, c, d) {
- t /= (d / 2);
- if (t < 1) {
- return c / 2 * t * t + b;
- }
- return -c / 2 * ((--t) * (t - 2) - 1) + b;
- },
-
- /**
- * Cubic easing in
- * @memberOf fabric.util.ease
- */
- easeInCubic: function(t, b, c, d) {
- return c * (t /= d) * t * t + b;
- },
-
- easeOutCubic: easeOutCubic,
- easeInOutCubic: easeInOutCubic,
- easeInQuart: easeInQuart,
- easeOutQuart: easeOutQuart,
- easeInOutQuart: easeInOutQuart,
- easeInQuint: easeInQuint,
- easeOutQuint: easeOutQuint,
- easeInOutQuint: easeInOutQuint,
- easeInSine: easeInSine,
- easeOutSine: easeOutSine,
- easeInOutSine: easeInOutSine,
- easeInExpo: easeInExpo,
- easeOutExpo: easeOutExpo,
- easeInOutExpo: easeInOutExpo,
- easeInCirc: easeInCirc,
- easeOutCirc: easeOutCirc,
- easeInOutCirc: easeInOutCirc,
- easeInElastic: easeInElastic,
- easeOutElastic: easeOutElastic,
- easeInOutElastic: easeInOutElastic,
- easeInBack: easeInBack,
- easeOutBack: easeOutBack,
- easeInOutBack: easeInOutBack,
- easeInBounce: easeInBounce,
- easeOutBounce: easeOutBounce,
- easeInOutBounce: easeInOutBounce
- };
-
-}());
-
-
(function(global) {
'use strict';
@@ -4705,7 +5743,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
// 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*/);
@@ -4775,19 +5813,20 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
* @see {@link fabric.Gradient#initialize} for constructor definition
*/
fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
- /*
- * Stores the original position of the gradient when we convert from % to fixed values, for objectBoundingBox case.
- * @type Number
- * @default 0
- */
- origX: 0,
- /*
- * Stores the original position of the gradient when we convert from % to fixed values, for objectBoundingBox case.
+ /**
+ * Horizontal offset for aligning gradients coming from SVG when outside pathgroups
* @type Number
* @default 0
*/
- origY: 0,
+ offsetX: 0,
+
+ /**
+ * Vertical offset for aligning gradients coming from SVG when outside pathgroups
+ * @type Number
+ * @default 0
+ */
+ offsetY: 0,
/**
* Constructor
@@ -4813,15 +5852,13 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
coords.r1 = options.coords.r1 || 0;
coords.r2 = options.coords.r2 || 0;
}
-
this.coords = coords;
- this.gradientUnits = options.gradientUnits || 'objectBoundingBox';
this.colorStops = options.colorStops.slice();
if (options.gradientTransform) {
this.gradientTransform = options.gradientTransform;
}
- this.origX = options.left || this.origX;
- this.origY = options.top || this.origY;
+ this.offsetX = options.offsetX || this.offsetX;
+ this.offsetY = options.offsetY || this.offsetY;
},
/**
@@ -4849,8 +5886,9 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
return {
type: this.type,
coords: this.coords,
- gradientUnits: this.gradientUnits,
- colorStops: this.colorStops
+ colorStops: this.colorStops,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
};
},
@@ -4861,7 +5899,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
* @param {Boolean} normalize Whether coords should be normalized
* @return {String} SVG representation of an gradient (linear/radial)
*/
- toSVG: function(object, normalize) {
+ toSVG: function(object) {
var coords = fabric.util.object.clone(this.coords),
markup, commonAttributes;
@@ -4870,17 +5908,19 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
return a.offset - b.offset;
});
- if (normalize && this.gradientUnits === 'userSpaceOnUse') {
- coords.x1 += object.width / 2;
- coords.y1 += object.height / 2;
- coords.x2 += object.width / 2;
- coords.y2 += object.height / 2;
- }
- else if (this.gradientUnits === 'objectBoundingBox') {
- _convertValuesToPercentUnits(object, coords);
+ if (!(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="' + this.gradientUnits + '"';
+ '" gradientUnits="userSpaceOnUse"';
if (this.gradientTransform) {
commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
}
@@ -5019,7 +6059,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
gradientTransform = el.getAttribute('gradientTransform'),
colorStops = [],
- coords = { };
+ coords = { }, ellipseMatrix;
if (type === 'linear') {
coords = getLinearCoords(el);
@@ -5032,19 +6072,19 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
colorStops.push(getColorStop(colorStopEls[i]));
}
- _convertPercentUnitsToValues(instance, coords);
+ ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
var gradient = new fabric.Gradient({
type: type,
coords: coords,
- gradientUnits: gradientUnits,
- colorStops: colorStops
+ colorStops: colorStops,
+ offsetX: -instance.left,
+ offsetY: -instance.top
});
- if (gradientTransform) {
- gradient.gradientTransform = fabric.parseTransformAttribute(gradientTransform);
+ if (gradientTransform || ellipseMatrix !== '') {
+ gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
}
-
return gradient;
},
/* _FROM_SVG_END_ */
@@ -5058,7 +6098,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
*/
forObject: function(obj, options) {
options || (options = { });
- _convertPercentUnitsToValues(obj, options);
+ _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');
return new fabric.Gradient(options);
}
});
@@ -5066,37 +6106,38 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
/**
* @private
*/
- function _convertPercentUnitsToValues(object, options) {
+ 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])) {
- var percents = parseFloat(options[prop], 10);
- if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
- options[prop] = fabric.util.toFixed(object.width * percents / 100, 2) + object.left;
- }
- else if (prop === 'y1' || prop === 'y2') {
- options[prop] = fabric.util.toFixed(object.height * percents / 100, 2) + object.top;
- }
+ multFactor = 0.01;
+ }
+ else {
+ multFactor = 1;
}
- }
- }
-
- /* _TO_SVG_START_ */
- /**
- * @private
- */
- function _convertValuesToPercentUnits(object, options) {
- for (var prop in options) {
- //convert to percent units
if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
- options[prop] = fabric.util.toFixed((options[prop] - object.fill.origX) / object.width * 100, 2) + '%';
+ multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
+ addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
}
else if (prop === 'y1' || prop === 'y2') {
- options[prop] = fabric.util.toFixed((options[prop] - object.fill.origY) / object.height * 100, 2) + '%';
+ 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;
}
- /* _TO_SVG_END_ */
-
})();
@@ -7727,2069 +8768,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
});
-(function() {
-
- var getPointer = fabric.util.getPointer,
- degreesToRadians = fabric.util.degreesToRadians,
- radiansToDegrees = fabric.util.radiansToDegrees,
- atan2 = Math.atan2,
- abs = Math.abs,
-
- STROKE_OFFSET = 0.5;
-
- /**
- * Canvas class
- * @class fabric.Canvas
- * @extends fabric.StaticCanvas
- * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas}
- * @see {@link fabric.Canvas#initialize} for constructor definition
- *
- * @fires object:modified
- * @fires object:rotating
- * @fires object:scaling
- * @fires object:moving
- * @fires object:selected
- *
- * @fires before:selection:cleared
- * @fires selection:cleared
- * @fires selection:created
- *
- * @fires path:created
- * @fires mouse:down
- * @fires mouse:move
- * @fires mouse:up
- * @fires mouse:over
- * @fires mouse:out
- *
- */
- fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
-
- /**
- * Constructor
- * @param {HTMLElement | String} el <canvas> element to initialize instance on
- * @param {Object} [options] Options object
- * @return {Object} thisArg
- */
- initialize: function(el, options) {
- options || (options = { });
-
- this._initStatic(el, options);
- this._initInteractive();
- this._createCacheCanvas();
-
- fabric.Canvas.activeInstance = this;
- },
-
- /**
- * When true, objects can be transformed by one side (unproportionally)
- * @type Boolean
- * @default
- */
- uniScaleTransform: false,
-
- /**
- * When true, objects use center point as the origin of scale transformation.
- * Backwards incompatibility note: This property replaces "centerTransform" (Boolean).
- * @since 1.3.4
- * @type Boolean
- * @default
- */
- centeredScaling: false,
-
- /**
- * When true, objects use center point as the origin of rotate transformation.
- * Backwards incompatibility note: This property replaces "centerTransform" (Boolean).
- * @since 1.3.4
- * @type Boolean
- * @default
- */
- centeredRotation: false,
-
- /**
- * Indicates that canvas is interactive. This property should not be changed.
- * @type Boolean
- * @default
- */
- interactive: true,
-
- /**
- * Indicates whether group selection should be enabled
- * @type Boolean
- * @default
- */
- selection: true,
-
- /**
- * Color of selection
- * @type String
- * @default
- */
- selectionColor: 'rgba(100, 100, 255, 0.3)', // blue
-
- /**
- * Default dash array pattern
- * If not empty the selection border is dashed
- * @type Array
- */
- selectionDashArray: [ ],
-
- /**
- * Color of the border of selection (usually slightly darker than color of selection itself)
- * @type String
- * @default
- */
- selectionBorderColor: 'rgba(255, 255, 255, 0.3)',
-
- /**
- * Width of a line used in object/group selection
- * @type Number
- * @default
- */
- selectionLineWidth: 1,
-
- /**
- * Default cursor value used when hovering over an object on canvas
- * @type String
- * @default
- */
- hoverCursor: 'move',
-
- /**
- * Default cursor value used when moving an object on canvas
- * @type String
- * @default
- */
- moveCursor: 'move',
-
- /**
- * Default cursor value used for the entire canvas
- * @type String
- * @default
- */
- defaultCursor: 'default',
-
- /**
- * Cursor value used during free drawing
- * @type String
- * @default
- */
- freeDrawingCursor: 'crosshair',
-
- /**
- * Cursor value used for rotation point
- * @type String
- * @default
- */
- rotationCursor: 'crosshair',
-
- /**
- * Default element class that's given to wrapper (div) element of canvas
- * @type String
- * @default
- */
- containerClass: 'canvas-container',
-
- /**
- * When true, object detection happens on per-pixel basis rather than on per-bounding-box
- * @type Boolean
- * @default
- */
- perPixelTargetFind: false,
-
- /**
- * Number of pixels around target pixel to tolerate (consider active) during object detection
- * @type Number
- * @default
- */
- targetFindTolerance: 0,
-
- /**
- * When true, target detection is skipped when hovering over canvas. This can be used to improve performance.
- * @type Boolean
- * @default
- */
- skipTargetFind: false,
-
- /**
- * @private
- */
- _initInteractive: function() {
- this._currentTransform = null;
- this._groupSelector = null;
- this._initWrapperElement();
- this._createUpperCanvas();
- this._initEventListeners();
-
- this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);
-
- this.calcOffset();
- },
-
- /**
- * Resets the current transform to its original values and chooses the type of resizing based on the event
- * @private
- * @param {Event} e Event object fired on mousemove
- */
- _resetCurrentTransform: function(e) {
- var t = this._currentTransform;
-
- t.target.set({
- scaleX: t.original.scaleX,
- scaleY: t.original.scaleY,
- left: t.original.left,
- top: t.original.top
- });
-
- if (this._shouldCenterTransform(e, t.target)) {
- if (t.action === 'rotate') {
- this._setOriginToCenter(t.target);
- }
- else {
- if (t.originX !== 'center') {
- if (t.originX === 'right') {
- t.mouseXSign = -1;
- }
- else {
- t.mouseXSign = 1;
- }
- }
- if (t.originY !== 'center') {
- if (t.originY === 'bottom') {
- t.mouseYSign = -1;
- }
- else {
- t.mouseYSign = 1;
- }
- }
-
- t.originX = 'center';
- t.originY = 'center';
- }
- }
- else {
- t.originX = t.original.originX;
- t.originY = t.original.originY;
- }
- },
-
- /**
- * Checks if point is contained within an area of given object
- * @param {Event} e Event object
- * @param {fabric.Object} target Object to test against
- * @return {Boolean} true if point is contained within an area of given object
- */
- containsPoint: function (e, target) {
- var pointer = this.getPointer(e, true),
- xy = this._normalizePointer(target, pointer);
-
- // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
- // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
- return (target.containsPoint(xy) || target._findTargetCorner(pointer));
- },
-
- /**
- * @private
- */
- _normalizePointer: function (object, pointer) {
- var activeGroup = this.getActiveGroup(),
- x = pointer.x,
- y = pointer.y,
- isObjectInGroup = (
- activeGroup &&
- object.type !== 'group' &&
- activeGroup.contains(object)),
- lt;
-
- if (isObjectInGroup) {
- lt = new fabric.Point(activeGroup.left, activeGroup.top);
- lt = fabric.util.transformPoint(lt, this.viewportTransform, true);
- x -= lt.x;
- y -= lt.y;
- }
- return { x: x, y: y };
- },
-
- /**
- * Returns true if object is transparent at a certain location
- * @param {fabric.Object} target Object to check
- * @param {Number} x Left coordinate
- * @param {Number} y Top coordinate
- * @return {Boolean}
- */
- isTargetTransparent: function (target, x, y) {
- var hasBorders = target.hasBorders,
- transparentCorners = target.transparentCorners;
-
- target.hasBorders = target.transparentCorners = false;
-
- this._draw(this.contextCache, target);
-
- target.hasBorders = hasBorders;
- target.transparentCorners = transparentCorners;
-
- var isTransparent = fabric.util.isTransparent(
- this.contextCache, x, y, this.targetFindTolerance);
-
- this.clearContext(this.contextCache);
-
- return isTransparent;
- },
-
- /**
- * @private
- * @param {Event} e Event object
- * @param {fabric.Object} target
- */
- _shouldClearSelection: function (e, target) {
- var activeGroup = this.getActiveGroup(),
- activeObject = this.getActiveObject();
-
- return (
- !target
- ||
- (target &&
- activeGroup &&
- !activeGroup.contains(target) &&
- activeGroup !== target &&
- !e.shiftKey)
- ||
- (target && !target.evented)
- ||
- (target &&
- !target.selectable &&
- activeObject &&
- activeObject !== target)
- );
- },
-
- /**
- * @private
- * @param {Event} e Event object
- * @param {fabric.Object} target
- */
- _shouldCenterTransform: function (e, target) {
- if (!target) {
- return;
- }
-
- var t = this._currentTransform,
- centerTransform;
-
- if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') {
- centerTransform = this.centeredScaling || target.centeredScaling;
- }
- else if (t.action === 'rotate') {
- centerTransform = this.centeredRotation || target.centeredRotation;
- }
-
- return centerTransform ? !e.altKey : e.altKey;
- },
-
- /**
- * @private
- */
- _getOriginFromCorner: function(target, corner) {
- var origin = {
- x: target.originX,
- y: target.originY
- };
-
- if (corner === 'ml' || corner === 'tl' || corner === 'bl') {
- origin.x = 'right';
- }
- else if (corner === 'mr' || corner === 'tr' || corner === 'br') {
- origin.x = 'left';
- }
-
- if (corner === 'tl' || corner === 'mt' || corner === 'tr') {
- origin.y = 'bottom';
- }
- else if (corner === 'bl' || corner === 'mb' || corner === 'br') {
- origin.y = 'top';
- }
-
- return origin;
- },
-
- /**
- * @private
- */
- _getActionFromCorner: function(target, corner) {
- var action = 'drag';
- if (corner) {
- action = (corner === 'ml' || corner === 'mr')
- ? 'scaleX'
- : (corner === 'mt' || corner === 'mb')
- ? 'scaleY'
- : corner === 'mtr'
- ? 'rotate'
- : 'scale';
- }
- return action;
- },
-
- /**
- * @private
- * @param {Event} e Event object
- * @param {fabric.Object} target
- */
- _setupCurrentTransform: function (e, target) {
- if (!target) {
- return;
- }
-
- var pointer = this.getPointer(e),
- corner = target._findTargetCorner(this.getPointer(e, true)),
- action = this._getActionFromCorner(target, corner),
- origin = this._getOriginFromCorner(target, corner);
-
- this._currentTransform = {
- target: target,
- action: action,
- scaleX: target.scaleX,
- scaleY: target.scaleY,
- offsetX: pointer.x - target.left,
- offsetY: pointer.y - target.top,
- originX: origin.x,
- originY: origin.y,
- ex: pointer.x,
- ey: pointer.y,
- left: target.left,
- top: target.top,
- theta: degreesToRadians(target.angle),
- width: target.width * target.scaleX,
- mouseXSign: 1,
- mouseYSign: 1
- };
-
- this._currentTransform.original = {
- left: target.left,
- top: target.top,
- scaleX: target.scaleX,
- scaleY: target.scaleY,
- originX: origin.x,
- originY: origin.y
- };
-
- this._resetCurrentTransform(e);
- },
-
- /**
- * Translates object by "setting" its left/top
- * @private
- * @param {Number} x pointer's x coordinate
- * @param {Number} y pointer's y coordinate
- */
- _translateObject: function (x, y) {
- var target = this._currentTransform.target;
-
- if (!target.get('lockMovementX')) {
- target.set('left', x - this._currentTransform.offsetX);
- }
- if (!target.get('lockMovementY')) {
- target.set('top', y - this._currentTransform.offsetY);
- }
- },
-
- /**
- * Scales object by invoking its scaleX/scaleY methods
- * @private
- * @param {Number} x pointer's x coordinate
- * @param {Number} y pointer's y coordinate
- * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
- * When not provided, an object is scaled by both dimensions equally
- */
- _scaleObject: function (x, y, by) {
- var t = this._currentTransform,
- target = t.target,
- lockScalingX = target.get('lockScalingX'),
- lockScalingY = target.get('lockScalingY'),
- lockScalingFlip = target.get('lockScalingFlip');
-
- if (lockScalingX && lockScalingY) {
- return;
- }
-
- // Get the constraint point
- var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY),
- localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY);
-
- this._setLocalMouse(localMouse, t);
-
- // Actually scale the object
- this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip);
-
- // Make sure the constraints apply
- target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
- },
-
- /**
- * @private
- */
- _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) {
- var target = transform.target, forbidScalingX = false, forbidScalingY = false;
-
- transform.newScaleX = localMouse.x / (target.width + target.strokeWidth);
- transform.newScaleY = localMouse.y / (target.height + target.strokeWidth);
-
- if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) {
- forbidScalingX = true;
- }
-
- if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) {
- forbidScalingY = true;
- }
-
- if (by === 'equally' && !lockScalingX && !lockScalingY) {
- forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform);
- }
- else if (!by) {
- forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX);
- forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY);
- }
- else if (by === 'x' && !target.get('lockUniScaling')) {
- forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX);
- }
- else if (by === 'y' && !target.get('lockUniScaling')) {
- forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY);
- }
-
- forbidScalingX || forbidScalingY || this._flipObject(transform);
-
- },
-
- /**
- * @private
- */
- _scaleObjectEqually: function(localMouse, target, transform) {
-
- var dist = localMouse.y + localMouse.x,
- lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY +
- (target.width + (target.strokeWidth)) * transform.original.scaleX;
-
- // We use transform.scaleX/Y instead of target.scaleX/Y
- // because the object may have a min scale and we'll loose the proportions
- transform.newScaleX = transform.original.scaleX * dist / lastDist;
- transform.newScaleY = transform.original.scaleY * dist / lastDist;
-
- target.set('scaleX', transform.newScaleX);
- target.set('scaleY', transform.newScaleY);
- },
-
- /**
- * @private
- */
- _flipObject: function(transform) {
- if (transform.newScaleX < 0) {
- if (transform.originX === 'left') {
- transform.originX = 'right';
- }
- else if (transform.originX === 'right') {
- transform.originX = 'left';
- }
- }
-
- if (transform.newScaleY < 0) {
- if (transform.originY === 'top') {
- transform.originY = 'bottom';
- }
- else if (transform.originY === 'bottom') {
- transform.originY = 'top';
- }
- }
- },
-
- /**
- * @private
- */
- _setLocalMouse: function(localMouse, t) {
- var target = t.target;
-
- if (t.originX === 'right') {
- localMouse.x *= -1;
- }
- else if (t.originX === 'center') {
- localMouse.x *= t.mouseXSign * 2;
-
- if (localMouse.x < 0) {
- t.mouseXSign = -t.mouseXSign;
- }
- }
-
- if (t.originY === 'bottom') {
- localMouse.y *= -1;
- }
- else if (t.originY === 'center') {
- localMouse.y *= t.mouseYSign * 2;
-
- if (localMouse.y < 0) {
- t.mouseYSign = -t.mouseYSign;
- }
- }
-
- // adjust the mouse coordinates when dealing with padding
- if (abs(localMouse.x) > target.padding) {
- if (localMouse.x < 0) {
- localMouse.x += target.padding;
- }
- else {
- localMouse.x -= target.padding;
- }
- }
- else { // mouse is within the padding, set to 0
- localMouse.x = 0;
- }
-
- if (abs(localMouse.y) > target.padding) {
- if (localMouse.y < 0) {
- localMouse.y += target.padding;
- }
- else {
- localMouse.y -= target.padding;
- }
- }
- else {
- localMouse.y = 0;
- }
- },
-
- /**
- * Rotates object by invoking its rotate method
- * @private
- * @param {Number} x pointer's x coordinate
- * @param {Number} y pointer's y coordinate
- */
- _rotateObject: function (x, y) {
-
- var t = this._currentTransform;
-
- if (t.target.get('lockRotation')) {
- return;
- }
-
- var lastAngle = atan2(t.ey - t.top, t.ex - t.left),
- curAngle = atan2(y - t.top, x - t.left),
- angle = radiansToDegrees(curAngle - lastAngle + t.theta);
-
- // normalize angle to positive value
- if (angle < 0) {
- angle = 360 + angle;
- }
-
- t.target.angle = angle;
- },
-
- /**
- * Set the cursor type of the canvas element
- * @param {String} value Cursor type of the canvas element.
- * @see http://www.w3.org/TR/css3-ui/#cursor
- */
- setCursor: function (value) {
- this.upperCanvasEl.style.cursor = value;
- },
-
- /**
- * @private
- */
- _resetObjectTransform: function (target) {
- target.scaleX = 1;
- target.scaleY = 1;
- target.setAngle(0);
- },
-
- /**
- * @private
- */
- _drawSelection: function () {
- var ctx = this.contextTop,
- groupSelector = this._groupSelector,
- left = groupSelector.left,
- top = groupSelector.top,
- aleft = abs(left),
- atop = abs(top);
-
- ctx.fillStyle = this.selectionColor;
-
- ctx.fillRect(
- groupSelector.ex - ((left > 0) ? 0 : -left),
- groupSelector.ey - ((top > 0) ? 0 : -top),
- aleft,
- atop
- );
-
- ctx.lineWidth = this.selectionLineWidth;
- ctx.strokeStyle = this.selectionBorderColor;
-
- // selection border
- if (this.selectionDashArray.length > 1) {
-
- var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft),
- py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop);
-
- ctx.beginPath();
-
- fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray);
- fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray);
- fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray);
- fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray);
-
- ctx.closePath();
- ctx.stroke();
- }
- else {
- ctx.strokeRect(
- groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
- groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
- aleft,
- atop
- );
- }
- },
-
- /**
- * @private
- */
- _isLastRenderedObject: function(e) {
- return (
- this.controlsAboveOverlay &&
- this.lastRenderedObjectWithControlsAboveOverlay &&
- this.lastRenderedObjectWithControlsAboveOverlay.visible &&
- this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) &&
- this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true)));
- },
-
- /**
- * Method that determines what object we are clicking on
- * @param {Event} e mouse event
- * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
- */
- findTarget: function (e, skipGroup) {
- if (this.skipTargetFind) {
- return;
- }
-
- if (this._isLastRenderedObject(e)) {
- return this.lastRenderedObjectWithControlsAboveOverlay;
- }
-
- // first check current group (if one exists)
- var activeGroup = this.getActiveGroup();
- if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
- return activeGroup;
- }
-
- var target = this._searchPossibleTargets(e);
- this._fireOverOutEvents(target);
-
- return target;
- },
-
- /**
- * @private
- */
- _fireOverOutEvents: function(target) {
- if (target) {
- if (this._hoveredTarget !== target) {
- this.fire('mouse:over', { target: target });
- target.fire('mouseover');
- if (this._hoveredTarget) {
- this.fire('mouse:out', { target: this._hoveredTarget });
- this._hoveredTarget.fire('mouseout');
- }
- this._hoveredTarget = target;
- }
- }
- else if (this._hoveredTarget) {
- this.fire('mouse:out', { target: this._hoveredTarget });
- this._hoveredTarget.fire('mouseout');
- this._hoveredTarget = null;
- }
- },
-
- /**
- * @private
- */
- _checkTarget: function(e, obj, pointer) {
- if (obj &&
- obj.visible &&
- obj.evented &&
- this.containsPoint(e, obj)){
- if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
- var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);
- if (!isTransparent) {
- return true;
- }
- }
- else {
- return true;
- }
- }
- },
-
- /**
- * @private
- */
- _searchPossibleTargets: function(e) {
-
- // Cache all targets where their bounding box contains point.
- var target,
- pointer = this.getPointer(e, true),
- i = this._objects.length;
-
- while (i--) {
- if (this._checkTarget(e, this._objects[i], pointer)){
- this.relatedTarget = this._objects[i];
- target = this._objects[i];
- break;
- }
- }
-
- return target;
- },
-
- /**
- * Returns pointer coordinates relative to canvas.
- * @param {Event} e
- * @return {Object} object with "x" and "y" number values
- */
- getPointer: function (e, ignoreZoom, upperCanvasEl) {
- if (!upperCanvasEl) {
- upperCanvasEl = this.upperCanvasEl;
- }
- var pointer = getPointer(e, upperCanvasEl),
- bounds = upperCanvasEl.getBoundingClientRect(),
- cssScale;
-
- this.calcOffset();
-
- pointer.x = pointer.x - this._offset.left;
- pointer.y = pointer.y - this._offset.top;
- if (!ignoreZoom) {
- pointer = fabric.util.transformPoint(
- pointer,
- fabric.util.invertTransform(this.viewportTransform)
- );
- }
-
- if (bounds.width === 0 || bounds.height === 0) {
- // If bounds are not available (i.e. not visible), do not apply scale.
- cssScale = { width: 1, height: 1 };
- }
- else {
- cssScale = {
- width: upperCanvasEl.width / bounds.width,
- height: upperCanvasEl.height / bounds.height
- };
- }
-
- return {
- x: pointer.x * cssScale.width,
- y: pointer.y * cssScale.height
- };
- },
-
- /**
- * @private
- * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
- */
- _createUpperCanvas: function () {
- var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, '');
-
- this.upperCanvasEl = this._createCanvasElement();
- fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);
-
- this.wrapperEl.appendChild(this.upperCanvasEl);
-
- this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
- this._applyCanvasStyle(this.upperCanvasEl);
- this.contextTop = this.upperCanvasEl.getContext('2d');
- },
-
- /**
- * @private
- */
- _createCacheCanvas: function () {
- this.cacheCanvasEl = this._createCanvasElement();
- this.cacheCanvasEl.setAttribute('width', this.width);
- this.cacheCanvasEl.setAttribute('height', this.height);
- this.contextCache = this.cacheCanvasEl.getContext('2d');
- },
-
- /**
- * @private
- */
- _initWrapperElement: function () {
- this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {
- 'class': this.containerClass
- });
- fabric.util.setStyle(this.wrapperEl, {
- width: this.getWidth() + 'px',
- height: this.getHeight() + 'px',
- position: 'relative'
- });
- fabric.util.makeElementUnselectable(this.wrapperEl);
- },
-
- /**
- * @private
- * @param {HTMLElement} element canvas element to apply styles on
- */
- _applyCanvasStyle: function (element) {
- var width = this.getWidth() || element.width,
- height = this.getHeight() || element.height;
-
- fabric.util.setStyle(element, {
- position: 'absolute',
- width: width + 'px',
- height: height + 'px',
- left: 0,
- top: 0
- });
- element.width = width;
- element.height = height;
- fabric.util.makeElementUnselectable(element);
- },
-
- /**
- * Copys the the entire inline style from one element (fromEl) to another (toEl)
- * @private
- * @param {Element} fromEl Element style is copied from
- * @param {Element} toEl Element copied style is applied to
- */
- _copyCanvasStyle: function (fromEl, toEl) {
- toEl.style.cssText = fromEl.style.cssText;
- },
-
- /**
- * Returns context of canvas where object selection is drawn
- * @return {CanvasRenderingContext2D}
- */
- getSelectionContext: function() {
- return this.contextTop;
- },
-
- /**
- * Returns <canvas> element on which object selection is drawn
- * @return {HTMLCanvasElement}
- */
- getSelectionElement: function () {
- return this.upperCanvasEl;
- },
-
- /**
- * @private
- * @param {Object} object
- */
- _setActiveObject: function(object) {
- if (this._activeObject) {
- this._activeObject.set('active', false);
- }
- this._activeObject = object;
- object.set('active', true);
- },
-
- /**
- * Sets given object as the only active object on canvas
- * @param {fabric.Object} object Object to set as an active one
- * @param {Event} [e] Event (passed along when firing "object:selected")
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- setActiveObject: function (object, e) {
- this._setActiveObject(object);
- this.renderAll();
- this.fire('object:selected', { target: object, e: e });
- object.fire('selected', { e: e });
- return this;
- },
-
- /**
- * Returns currently active object
- * @return {fabric.Object} active object
- */
- getActiveObject: function () {
- return this._activeObject;
- },
-
- /**
- * @private
- */
- _discardActiveObject: function() {
- if (this._activeObject) {
- this._activeObject.set('active', false);
- }
- this._activeObject = null;
- },
-
- /**
- * Discards currently active object
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- discardActiveObject: function (e) {
- this._discardActiveObject();
- this.renderAll();
- this.fire('selection:cleared', { e: e });
- return this;
- },
-
- /**
- * @private
- * @param {fabric.Group} group
- */
- _setActiveGroup: function(group) {
- this._activeGroup = group;
- if (group) {
- group.set('active', true);
- }
- },
-
- /**
- * Sets active group to a speicified one
- * @param {fabric.Group} group Group to set as a current one
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- setActiveGroup: function (group, e) {
- this._setActiveGroup(group);
- if (group) {
- this.fire('object:selected', { target: group, e: e });
- group.fire('selected', { e: e });
- }
- return this;
- },
-
- /**
- * Returns currently active group
- * @return {fabric.Group} Current group
- */
- getActiveGroup: function () {
- return this._activeGroup;
- },
-
- /**
- * @private
- */
- _discardActiveGroup: function() {
- var g = this.getActiveGroup();
- if (g) {
- g.destroy();
- }
- this.setActiveGroup(null);
- },
-
- /**
- * Discards currently active group
- * @return {fabric.Canvas} thisArg
- */
- discardActiveGroup: function (e) {
- this._discardActiveGroup();
- this.fire('selection:cleared', { e: e });
- return this;
- },
-
- /**
- * Deactivates all objects on canvas, removing any active group or object
- * @return {fabric.Canvas} thisArg
- */
- deactivateAll: function () {
- var allObjects = this.getObjects(),
- i = 0,
- len = allObjects.length;
- for ( ; i < len; i++) {
- allObjects[i].set('active', false);
- }
- this._discardActiveGroup();
- this._discardActiveObject();
- return this;
- },
-
- /**
- * Deactivates all objects and dispatches appropriate events
- * @return {fabric.Canvas} thisArg
- */
- deactivateAllWithDispatch: function (e) {
- var activeObject = this.getActiveGroup() || this.getActiveObject();
- if (activeObject) {
- this.fire('before:selection:cleared', { target: activeObject, e: e });
- }
- this.deactivateAll();
- if (activeObject) {
- this.fire('selection:cleared', { e: e });
- }
- return this;
- },
-
- /**
- * Draws objects' controls (borders/controls)
- * @param {CanvasRenderingContext2D} ctx Context to render controls on
- */
- drawControls: function(ctx) {
- var activeGroup = this.getActiveGroup();
- if (activeGroup) {
- this._drawGroupControls(ctx, activeGroup);
- }
- else {
- this._drawObjectsControls(ctx);
- }
- },
-
- /**
- * @private
- */
- _drawGroupControls: function(ctx, activeGroup) {
- activeGroup._renderControls(ctx);
- },
-
- /**
- * @private
- */
- _drawObjectsControls: function(ctx) {
- for (var i = 0, len = this._objects.length; i < len; ++i) {
- if (!this._objects[i] || !this._objects[i].active) {
- continue;
- }
- this._objects[i]._renderControls(ctx);
- this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
- }
- }
- });
-
- // copying static properties manually to work around Opera's bug,
- // where "prototype" property is enumerable and overrides existing prototype
- for (var prop in fabric.StaticCanvas) {
- if (prop !== 'prototype') {
- fabric.Canvas[prop] = fabric.StaticCanvas[prop];
- }
- }
-
- if (fabric.isTouchSupported) {
- /** @ignore */
- fabric.Canvas.prototype._setCursorFromEvent = function() { };
- }
-
- /**
- * @class fabric.Element
- * @alias fabric.Canvas
- * @deprecated Use {@link fabric.Canvas} instead.
- * @constructor
- */
- fabric.Element = fabric.Canvas;
-})();
-
-
-(function(){
-
- var cursorOffset = {
- mt: 0, // n
- tr: 1, // ne
- mr: 2, // e
- br: 3, // se
- mb: 4, // s
- bl: 5, // sw
- ml: 6, // w
- tl: 7 // nw
- },
- addListener = fabric.util.addListener,
- removeListener = fabric.util.removeListener;
-
- fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
-
- /**
- * Map of cursor style values for each of the object controls
- * @private
- */
- cursorMap: [
- 'n-resize',
- 'ne-resize',
- 'e-resize',
- 'se-resize',
- 's-resize',
- 'sw-resize',
- 'w-resize',
- 'nw-resize'
- ],
-
- /**
- * Adds mouse listeners to canvas
- * @private
- */
- _initEventListeners: function () {
-
- this._bindEvents();
-
- addListener(fabric.window, 'resize', this._onResize);
-
- // mouse events
- addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
- addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
- addListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel);
-
- // touch events
- addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
- addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
-
- if (typeof Event !== 'undefined' && 'add' in Event) {
- Event.add(this.upperCanvasEl, 'gesture', this._onGesture);
- Event.add(this.upperCanvasEl, 'drag', this._onDrag);
- Event.add(this.upperCanvasEl, 'orientation', this._onOrientationChange);
- Event.add(this.upperCanvasEl, 'shake', this._onShake);
- }
- },
-
- /**
- * @private
- */
- _bindEvents: function() {
- this._onMouseDown = this._onMouseDown.bind(this);
- this._onMouseMove = this._onMouseMove.bind(this);
- this._onMouseUp = this._onMouseUp.bind(this);
- this._onResize = this._onResize.bind(this);
- this._onGesture = this._onGesture.bind(this);
- this._onDrag = this._onDrag.bind(this);
- this._onShake = this._onShake.bind(this);
- this._onOrientationChange = this._onOrientationChange.bind(this);
- this._onMouseWheel = this._onMouseWheel.bind(this);
- },
-
- /**
- * Removes all event listeners
- */
- removeListeners: function() {
- removeListener(fabric.window, 'resize', this._onResize);
-
- removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
- removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
- removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel);
-
- removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
- removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
-
- if (typeof Event !== 'undefined' && 'remove' in Event) {
- Event.remove(this.upperCanvasEl, 'gesture', this._onGesture);
- Event.remove(this.upperCanvasEl, 'drag', this._onDrag);
- Event.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange);
- Event.remove(this.upperCanvasEl, 'shake', this._onShake);
- }
- },
-
- /**
- * @private
- * @param {Event} [e] Event object fired on Event.js gesture
- * @param {Event} [self] Inner Event object
- */
- _onGesture: function(e, self) {
- this.__onTransformGesture && this.__onTransformGesture(e, self);
- },
-
- /**
- * @private
- * @param {Event} [e] Event object fired on Event.js drag
- * @param {Event} [self] Inner Event object
- */
- _onDrag: function(e, self) {
- this.__onDrag && this.__onDrag(e, self);
- },
-
- /**
- * @private
- * @param {Event} [e] Event object fired on Event.js wheel event
- * @param {Event} [self] Inner Event object
- */
- _onMouseWheel: function(e, self) {
- this.__onMouseWheel && this.__onMouseWheel(e, self);
- },
-
- /**
- * @private
- * @param {Event} [e] Event object fired on Event.js orientation change
- * @param {Event} [self] Inner Event object
- */
- _onOrientationChange: function(e,self) {
- this.__onOrientationChange && this.__onOrientationChange(e,self);
- },
-
- /**
- * @private
- * @param {Event} [e] Event object fired on Event.js shake
- * @param {Event} [self] Inner Event object
- */
- _onShake: function(e, self) {
- this.__onShake && this.__onShake(e,self);
- },
-
- /**
- * @private
- * @param {Event} e Event object fired on mousedown
- */
- _onMouseDown: function (e) {
- this.__onMouseDown(e);
-
- addListener(fabric.document, 'touchend', this._onMouseUp);
- addListener(fabric.document, 'touchmove', this._onMouseMove);
-
- removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
- removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
-
- if (e.type === 'touchstart') {
- // Unbind mousedown to prevent double triggers from touch devices
- removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
- }
- else {
- addListener(fabric.document, 'mouseup', this._onMouseUp);
- addListener(fabric.document, 'mousemove', this._onMouseMove);
- }
- },
-
- /**
- * @private
- * @param {Event} e Event object fired on mouseup
- */
- _onMouseUp: function (e) {
- this.__onMouseUp(e);
-
- removeListener(fabric.document, 'mouseup', this._onMouseUp);
- removeListener(fabric.document, 'touchend', this._onMouseUp);
-
- removeListener(fabric.document, 'mousemove', this._onMouseMove);
- removeListener(fabric.document, 'touchmove', this._onMouseMove);
-
- addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
- addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
-
- if (e.type === 'touchend') {
- // Wait 400ms before rebinding mousedown to prevent double triggers
- // from touch devices
- var _this = this;
- setTimeout(function() {
- addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown);
- }, 400);
- }
- },
-
- /**
- * @private
- * @param {Event} e Event object fired on mousemove
- */
- _onMouseMove: function (e) {
- !this.allowTouchScrolling && e.preventDefault && e.preventDefault();
- this.__onMouseMove(e);
- },
-
- /**
- * @private
- */
- _onResize: function () {
- this.calcOffset();
- },
-
- /**
- * Decides whether the canvas should be redrawn in mouseup and mousedown events.
- * @private
- * @param {Object} target
- * @param {Object} pointer
- */
- _shouldRender: function(target, pointer) {
- var activeObject = this.getActiveGroup() || this.getActiveObject();
-
- return !!(
- (target && (
- target.isMoving ||
- target !== activeObject))
- ||
- (!target && !!activeObject)
- ||
- (!target && !activeObject && !this._groupSelector)
- ||
- (pointer &&
- this._previousPointer &&
- this.selection && (
- pointer.x !== this._previousPointer.x ||
- pointer.y !== this._previousPointer.y))
- );
- },
-
- /**
- * Method that defines the actions when mouse is released on canvas.
- * The method resets the currentTransform parameters, store the image corner
- * position in the image object and render the canvas on top.
- * @private
- * @param {Event} e Event object fired on mouseup
- */
- __onMouseUp: function (e) {
- var target;
-
- if (this.isDrawingMode && this._isCurrentlyDrawing) {
- this._onMouseUpInDrawingMode(e);
- return;
- }
-
- if (this._currentTransform) {
- this._finalizeCurrentTransform();
- target = this._currentTransform.target;
- }
- else {
- target = this.findTarget(e, true);
- }
-
- var shouldRender = this._shouldRender(target, this.getPointer(e));
-
- this._maybeGroupObjects(e);
-
- if (target) {
- target.isMoving = false;
- }
-
- shouldRender && this.renderAll();
-
- this._handleCursorAndEvent(e, target);
- },
-
- _handleCursorAndEvent: function(e, target) {
- this._setCursorFromEvent(e, target);
-
- // TODO: why are we doing this?
- var _this = this;
- setTimeout(function () {
- _this._setCursorFromEvent(e, target);
- }, 50);
-
- this.fire('mouse:up', { target: target, e: e });
- target && target.fire('mouseup', { e: e });
- },
-
- /**
- * @private
- */
- _finalizeCurrentTransform: function() {
-
- var transform = this._currentTransform,
- target = transform.target;
-
- if (target._scaling) {
- target._scaling = false;
- }
-
- target.setCoords();
-
- // only fire :modified event if target coordinates were changed during mousedown-mouseup
- if (this.stateful && target.hasStateChanged()) {
- this.fire('object:modified', { target: target });
- target.fire('modified');
- }
-
- this._restoreOriginXY(target);
- },
-
- /**
- * @private
- * @param {Object} target Object to restore
- */
- _restoreOriginXY: function(target) {
- if (this._previousOriginX && this._previousOriginY) {
-
- var originPoint = target.translateToOriginPoint(
- target.getCenterPoint(),
- this._previousOriginX,
- this._previousOriginY);
-
- target.originX = this._previousOriginX;
- target.originY = this._previousOriginY;
-
- target.left = originPoint.x;
- target.top = originPoint.y;
-
- this._previousOriginX = null;
- this._previousOriginY = null;
- }
- },
-
- /**
- * @private
- * @param {Event} e Event object fired on mousedown
- */
- _onMouseDownInDrawingMode: function(e) {
- this._isCurrentlyDrawing = true;
- this.discardActiveObject(e).renderAll();
- if (this.clipTo) {
- fabric.util.clipContext(this, this.contextTop);
- }
- var ivt = fabric.util.invertTransform(this.viewportTransform),
- pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);
- this.freeDrawingBrush.onMouseDown(pointer);
- this.fire('mouse:down', { e: e });
- },
-
- /**
- * @private
- * @param {Event} e Event object fired on mousemove
- */
- _onMouseMoveInDrawingMode: function(e) {
- if (this._isCurrentlyDrawing) {
- var ivt = fabric.util.invertTransform(this.viewportTransform),
- pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);
- this.freeDrawingBrush.onMouseMove(pointer);
- }
- this.setCursor(this.freeDrawingCursor);
- this.fire('mouse:move', { e: e });
- },
-
- /**
- * @private
- * @param {Event} e Event object fired on mouseup
- */
- _onMouseUpInDrawingMode: function(e) {
- this._isCurrentlyDrawing = false;
- if (this.clipTo) {
- this.contextTop.restore();
- }
- this.freeDrawingBrush.onMouseUp();
- this.fire('mouse:up', { e: e });
- },
-
- /**
- * Method that defines the actions when mouse is clic ked on canvas.
- * The method inits the currentTransform parameters and renders all the
- * canvas so the current image can be placed on the top canvas and the rest
- * in on the container one.
- * @private
- * @param {Event} e Event object fired on mousedown
- */
- __onMouseDown: function (e) {
-
- // accept only left clicks
- var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
- if (!isLeftClick && !fabric.isTouchSupported) {
- return;
- }
-
- if (this.isDrawingMode) {
- this._onMouseDownInDrawingMode(e);
- return;
- }
-
- // ignore if some object is being transformed at this moment
- if (this._currentTransform) {
- return;
- }
-
- var target = this.findTarget(e),
- pointer = this.getPointer(e, true);
-
- // save pointer for check in __onMouseUp event
- this._previousPointer = pointer;
-
- var shouldRender = this._shouldRender(target, pointer),
- shouldGroup = this._shouldGroup(e, target);
-
- if (this._shouldClearSelection(e, target)) {
- this._clearSelection(e, target, pointer);
- }
- else if (shouldGroup) {
- this._handleGrouping(e, target);
- target = this.getActiveGroup();
- }
-
- if (target && target.selectable && !shouldGroup) {
- this._beforeTransform(e, target);
- this._setupCurrentTransform(e, target);
- }
- // we must renderAll so that active image is placed on the top canvas
- shouldRender && this.renderAll();
-
- this.fire('mouse:down', { target: target, e: e });
- target && target.fire('mousedown', { e: e });
- },
-
- /**
- * @private
- */
- _beforeTransform: function(e, target) {
- var corner;
-
- this.stateful && target.saveState();
-
- // determine if it's a drag or rotate case
- if ((corner = target._findTargetCorner(this.getPointer(e)))) {
- this.onBeforeScaleRotate(target);
- }
-
- if (target !== this.getActiveGroup() && target !== this.getActiveObject()) {
- this.deactivateAll();
- this.setActiveObject(target, e);
- }
- },
-
- /**
- * @private
- */
- _clearSelection: function(e, target, pointer) {
- this.deactivateAllWithDispatch(e);
-
- if (target && target.selectable) {
- this.setActiveObject(target, e);
- }
- else if (this.selection) {
- this._groupSelector = {
- ex: pointer.x,
- ey: pointer.y,
- top: 0,
- left: 0
- };
- }
- },
-
- /**
- * @private
- * @param {Object} target Object for that origin is set to center
- */
- _setOriginToCenter: function(target) {
- this._previousOriginX = this._currentTransform.target.originX;
- this._previousOriginY = this._currentTransform.target.originY;
-
- var center = target.getCenterPoint();
-
- target.originX = 'center';
- target.originY = 'center';
-
- target.left = center.x;
- target.top = center.y;
-
- this._currentTransform.left = target.left;
- this._currentTransform.top = target.top;
- },
-
- /**
- * @private
- * @param {Object} target Object for that center is set to origin
- */
- _setCenterToOrigin: function(target) {
- var originPoint = target.translateToOriginPoint(
- target.getCenterPoint(),
- this._previousOriginX,
- this._previousOriginY);
-
- target.originX = this._previousOriginX;
- target.originY = this._previousOriginY;
-
- target.left = originPoint.x;
- target.top = originPoint.y;
-
- this._previousOriginX = null;
- this._previousOriginY = null;
- },
-
- /**
- * Method that defines the actions when mouse is hovering the canvas.
- * The currentTransform parameter will definde whether the user is rotating/scaling/translating
- * an image or neither of them (only hovering). A group selection is also possible and would cancel
- * all any other type of action.
- * In case of an image transformation only the top canvas will be rendered.
- * @private
- * @param {Event} e Event object fired on mousemove
- */
- __onMouseMove: function (e) {
-
- var target, pointer;
-
- if (this.isDrawingMode) {
- this._onMouseMoveInDrawingMode(e);
- return;
- }
-
- var groupSelector = this._groupSelector;
-
- // We initially clicked in an empty area, so we draw a box for multiple selection
- if (groupSelector) {
- pointer = this.getPointer(e, true);
-
- groupSelector.left = pointer.x - groupSelector.ex;
- groupSelector.top = pointer.y - groupSelector.ey;
-
- this.renderTop();
- }
- else if (!this._currentTransform) {
-
- target = this.findTarget(e);
-
- if (!target || target && !target.selectable) {
- this.setCursor(this.defaultCursor);
- }
- else {
- this._setCursorFromEvent(e, target);
- }
- }
- else {
- this._transformObject(e);
- }
-
- this.fire('mouse:move', { target: target, e: e });
- target && target.fire('mousemove', { e: e });
- },
-
- /**
- * @private
- * @param {Event} e Event fired on mousemove
- */
- _transformObject: function(e) {
- var pointer = this.getPointer(e),
- transform = this._currentTransform;
-
- transform.reset = false,
- transform.target.isMoving = true;
-
- this._beforeScaleTransform(e, transform);
- this._performTransformAction(e, transform, pointer);
-
- this.renderAll();
- },
-
- /**
- * @private
- */
- _performTransformAction: function(e, transform, pointer) {
- var x = pointer.x,
- y = pointer.y,
- target = transform.target,
- action = transform.action;
-
- if (action === 'rotate') {
- this._rotateObject(x, y);
- this._fire('rotating', target, e);
- }
- else if (action === 'scale') {
- this._onScale(e, transform, x, y);
- this._fire('scaling', target, e);
- }
- else if (action === 'scaleX') {
- this._scaleObject(x, y, 'x');
- this._fire('scaling', target, e);
- }
- else if (action === 'scaleY') {
- this._scaleObject(x, y, 'y');
- this._fire('scaling', target, e);
- }
- else {
- this._translateObject(x, y);
- this._fire('moving', target, e);
- this.setCursor(this.moveCursor);
- }
- },
-
- /**
- * @private
- */
- _fire: function(eventName, target, e) {
- this.fire('object:' + eventName, { target: target, e: e });
- target.fire(eventName, { e: e });
- },
-
- /**
- * @private
- */
- _beforeScaleTransform: function(e, transform) {
- if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') {
- var centerTransform = this._shouldCenterTransform(e, transform.target);
-
- // Switch from a normal resize to center-based
- if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) ||
- // Switch from center-based resize to normal one
- (!centerTransform && transform.originX === 'center' && transform.originY === 'center')
- ) {
- this._resetCurrentTransform(e);
- transform.reset = true;
- }
- }
- },
-
- /**
- * @private
- */
- _onScale: function(e, transform, x, y) {
- // rotate object only if shift key is not pressed
- // and if it is not a group we are transforming
- if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) {
- transform.currentAction = 'scale';
- this._scaleObject(x, y);
- }
- else {
- // Switch from a normal resize to proportional
- if (!transform.reset && transform.currentAction === 'scale') {
- this._resetCurrentTransform(e, transform.target);
- }
-
- transform.currentAction = 'scaleEqually';
- this._scaleObject(x, y, 'equally');
- }
- },
-
- /**
- * Sets the cursor depending on where the canvas is being hovered.
- * Note: very buggy in Opera
- * @param {Event} e Event object
- * @param {Object} target Object that the mouse is hovering, if so.
- */
- _setCursorFromEvent: function (e, target) {
- if (!target || !target.selectable) {
- this.setCursor(this.defaultCursor);
- return false;
- }
- else {
- var activeGroup = this.getActiveGroup(),
- // only show proper corner when group selection is not active
- corner = target._findTargetCorner
- && (!activeGroup || !activeGroup.contains(target))
- && target._findTargetCorner(this.getPointer(e, true));
-
- if (!corner) {
- this.setCursor(target.hoverCursor || this.hoverCursor);
- }
- else {
- this._setCornerCursor(corner, target);
- }
- }
- return true;
- },
-
- /**
- * @private
- */
- _setCornerCursor: function(corner, target) {
- if (corner in cursorOffset) {
- this.setCursor(this._getRotatedCornerCursor(corner, target));
- }
- else if (corner === 'mtr' && target.hasRotatingPoint) {
- this.setCursor(this.rotationCursor);
- }
- else {
- this.setCursor(this.defaultCursor);
- return false;
- }
- },
-
- /**
- * @private
- */
- _getRotatedCornerCursor: function(corner, target) {
- var n = Math.round((target.getAngle() % 360) / 45);
-
- if (n < 0) {
- n += 8; // full circle ahead
- }
- n += cursorOffset[corner];
- // normalize n to be from 0 to 7
- n %= 8;
-
- return this.cursorMap[n];
- }
- });
-})();
-
-
-(function(){
-
- var min = Math.min,
- max = Math.max;
-
- fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
-
- /**
- * @private
- * @param {Event} e Event object
- * @param {fabric.Object} target
- * @return {Boolean}
- */
- _shouldGroup: function(e, target) {
- var activeObject = this.getActiveObject();
- return e.shiftKey &&
- (this.getActiveGroup() || (activeObject && activeObject !== target))
- && this.selection;
- },
-
- /**
- * @private
- * @param {Event} e Event object
- * @param {fabric.Object} target
- */
- _handleGrouping: function (e, target) {
-
- if (target === this.getActiveGroup()) {
-
- // if it's a group, find target again, this time skipping group
- target = this.findTarget(e, true);
-
- // if even object is not found, bail out
- if (!target || target.isType('group')) {
- return;
- }
- }
- if (this.getActiveGroup()) {
- this._updateActiveGroup(target, e);
- }
- else {
- this._createActiveGroup(target, e);
- }
-
- if (this._activeGroup) {
- this._activeGroup.saveCoords();
- }
- },
-
- /**
- * @private
- */
- _updateActiveGroup: function(target, e) {
- var activeGroup = this.getActiveGroup();
-
- if (activeGroup.contains(target)) {
-
- activeGroup.removeWithUpdate(target);
- this._resetObjectTransform(activeGroup);
- target.set('active', false);
-
- if (activeGroup.size() === 1) {
- // remove group alltogether if after removal it only contains 1 object
- this.discardActiveGroup(e);
- // activate last remaining object
- this.setActiveObject(activeGroup.item(0));
- return;
- }
- }
- else {
- activeGroup.addWithUpdate(target);
- this._resetObjectTransform(activeGroup);
- }
- this.fire('selection:created', { target: activeGroup, e: e });
- activeGroup.set('active', true);
- },
-
- /**
- * @private
- */
- _createActiveGroup: function(target, e) {
-
- if (this._activeObject && target !== this._activeObject) {
-
- var group = this._createGroup(target);
- group.addWithUpdate();
-
- this.setActiveGroup(group);
- this._activeObject = null;
-
- this.fire('selection:created', { target: group, e: e });
- }
-
- target.set('active', true);
- },
-
- /**
- * @private
- * @param {Object} target
- */
- _createGroup: function(target) {
-
- var objects = this.getObjects(),
- isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target),
- groupObjects = isActiveLower
- ? [ this._activeObject, target ]
- : [ target, this._activeObject ];
-
- return new fabric.Group(groupObjects, {
- originX: 'center',
- originY: 'center',
- canvas: this
- });
- },
-
- /**
- * @private
- * @param {Event} e mouse event
- */
- _groupSelectedObjects: function (e) {
-
- var group = this._collectObjects();
-
- // do not create group for 1 element only
- if (group.length === 1) {
- this.setActiveObject(group[0], e);
- }
- else if (group.length > 1) {
- group = new fabric.Group(group.reverse(), {
- originX: 'center',
- originY: 'center',
- canvas: this
- });
- group.addWithUpdate();
- this.setActiveGroup(group, e);
- group.saveCoords();
- this.fire('selection:created', { target: group });
- this.renderAll();
- }
- },
-
- /**
- * @private
- */
- _collectObjects: function() {
- var group = [ ],
- currentObject,
- x1 = this._groupSelector.ex,
- y1 = this._groupSelector.ey,
- x2 = x1 + this._groupSelector.left,
- y2 = y1 + this._groupSelector.top,
- selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
- selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),
- isClick = x1 === x2 && y1 === y2;
-
- for (var i = this._objects.length; i--; ) {
- currentObject = this._objects[i];
-
- if (!currentObject || !currentObject.selectable || !currentObject.visible) {
- continue;
- }
-
- if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
- currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
- currentObject.containsPoint(selectionX1Y1) ||
- currentObject.containsPoint(selectionX2Y2)
- ) {
- currentObject.set('active', true);
- group.push(currentObject);
-
- // only add one object if it's a click
- if (isClick) {
- break;
- }
- }
- }
-
- return group;
- },
-
- /**
- * @private
- */
- _maybeGroupObjects: function(e) {
- if (this.selection && this._groupSelector) {
- this._groupSelectedObjects(e);
- }
-
- var activeGroup = this.getActiveGroup();
- if (activeGroup) {
- activeGroup.setObjectsCoords().setCoords();
- activeGroup.isMoving = false;
- this.setCursor(this.defaultCursor);
- }
-
- // clear selection and current transformation
- this._groupSelector = null;
- this._currentTransform = null;
- }
- });
-
-})();
-
-
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
/**
@@ -12665,762 +11643,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
});
-(function(){
-
- var degreesToRadians = fabric.util.degreesToRadians,
- //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
- isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; };
- //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
-
- fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
-
- /**
- * The object interactivity controls.
- * @private
- */
- _controlsVisibility: null,
-
- /**
- * Determines which corner has been clicked
- * @private
- * @param {Object} pointer The pointer indicating the mouse position
- * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
- */
- _findTargetCorner: function(pointer) {
- if (!this.hasControls || !this.active) {
- return false;
- }
-
- var ex = pointer.x,
- ey = pointer.y,
- xPoints,
- lines;
-
- for (var i in this.oCoords) {
-
- if (!this.isControlVisible(i)) {
- continue;
- }
-
- if (i === 'mtr' && !this.hasRotatingPoint) {
- continue;
- }
-
- if (this.get('lockUniScaling') &&
- (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
- continue;
- }
-
- lines = this._getImageLines(this.oCoords[i].corner);
-
- // debugging
-
- // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
- // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
-
- // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
- // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
-
- // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
- // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
-
- // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
- // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
-
- xPoints = this._findCrossPoints({ x: ex, y: ey }, lines);
- if (xPoints !== 0 && xPoints % 2 === 1) {
- this.__corner = i;
- return i;
- }
- }
- return false;
- },
-
- /**
- * Sets the coordinates of the draggable boxes in the corners of
- * the image used to scale/rotate it.
- * @private
- */
- _setCornerCoords: function() {
- var coords = this.oCoords,
- theta = degreesToRadians(this.angle),
- newTheta = degreesToRadians(45 - this.angle),
- cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
- cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
- sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
- sinTh = Math.sin(theta),
- cosTh = Math.cos(theta);
-
- coords.tl.corner = {
- tl: {
- x: coords.tl.x - sinHalfOffset,
- y: coords.tl.y - cosHalfOffset
- },
- tr: {
- x: coords.tl.x + cosHalfOffset,
- y: coords.tl.y - sinHalfOffset
- },
- bl: {
- x: coords.tl.x - cosHalfOffset,
- y: coords.tl.y + sinHalfOffset
- },
- br: {
- x: coords.tl.x + sinHalfOffset,
- y: coords.tl.y + cosHalfOffset
- }
- };
-
- coords.tr.corner = {
- tl: {
- x: coords.tr.x - sinHalfOffset,
- y: coords.tr.y - cosHalfOffset
- },
- tr: {
- x: coords.tr.x + cosHalfOffset,
- y: coords.tr.y - sinHalfOffset
- },
- br: {
- x: coords.tr.x + sinHalfOffset,
- y: coords.tr.y + cosHalfOffset
- },
- bl: {
- x: coords.tr.x - cosHalfOffset,
- y: coords.tr.y + sinHalfOffset
- }
- };
-
- coords.bl.corner = {
- tl: {
- x: coords.bl.x - sinHalfOffset,
- y: coords.bl.y - cosHalfOffset
- },
- bl: {
- x: coords.bl.x - cosHalfOffset,
- y: coords.bl.y + sinHalfOffset
- },
- br: {
- x: coords.bl.x + sinHalfOffset,
- y: coords.bl.y + cosHalfOffset
- },
- tr: {
- x: coords.bl.x + cosHalfOffset,
- y: coords.bl.y - sinHalfOffset
- }
- };
-
- coords.br.corner = {
- tr: {
- x: coords.br.x + cosHalfOffset,
- y: coords.br.y - sinHalfOffset
- },
- bl: {
- x: coords.br.x - cosHalfOffset,
- y: coords.br.y + sinHalfOffset
- },
- br: {
- x: coords.br.x + sinHalfOffset,
- y: coords.br.y + cosHalfOffset
- },
- tl: {
- x: coords.br.x - sinHalfOffset,
- y: coords.br.y - cosHalfOffset
- }
- };
-
- coords.ml.corner = {
- tl: {
- x: coords.ml.x - sinHalfOffset,
- y: coords.ml.y - cosHalfOffset
- },
- tr: {
- x: coords.ml.x + cosHalfOffset,
- y: coords.ml.y - sinHalfOffset
- },
- bl: {
- x: coords.ml.x - cosHalfOffset,
- y: coords.ml.y + sinHalfOffset
- },
- br: {
- x: coords.ml.x + sinHalfOffset,
- y: coords.ml.y + cosHalfOffset
- }
- };
-
- coords.mt.corner = {
- tl: {
- x: coords.mt.x - sinHalfOffset,
- y: coords.mt.y - cosHalfOffset
- },
- tr: {
- x: coords.mt.x + cosHalfOffset,
- y: coords.mt.y - sinHalfOffset
- },
- bl: {
- x: coords.mt.x - cosHalfOffset,
- y: coords.mt.y + sinHalfOffset
- },
- br: {
- x: coords.mt.x + sinHalfOffset,
- y: coords.mt.y + cosHalfOffset
- }
- };
-
- coords.mr.corner = {
- tl: {
- x: coords.mr.x - sinHalfOffset,
- y: coords.mr.y - cosHalfOffset
- },
- tr: {
- x: coords.mr.x + cosHalfOffset,
- y: coords.mr.y - sinHalfOffset
- },
- bl: {
- x: coords.mr.x - cosHalfOffset,
- y: coords.mr.y + sinHalfOffset
- },
- br: {
- x: coords.mr.x + sinHalfOffset,
- y: coords.mr.y + cosHalfOffset
- }
- };
-
- coords.mb.corner = {
- tl: {
- x: coords.mb.x - sinHalfOffset,
- y: coords.mb.y - cosHalfOffset
- },
- tr: {
- x: coords.mb.x + cosHalfOffset,
- y: coords.mb.y - sinHalfOffset
- },
- bl: {
- x: coords.mb.x - cosHalfOffset,
- y: coords.mb.y + sinHalfOffset
- },
- br: {
- x: coords.mb.x + sinHalfOffset,
- y: coords.mb.y + cosHalfOffset
- }
- };
-
- coords.mtr.corner = {
- tl: {
- x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset),
- y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset)
- },
- tr: {
- x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset),
- y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset)
- },
- bl: {
- x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset),
- y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset)
- },
- br: {
- x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset),
- y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset)
- }
- };
- },
- /**
- * Draws borders of an object's bounding box.
- * Requires public properties: width, height
- * Requires public options: padding, borderColor
- * @param {CanvasRenderingContext2D} ctx Context to draw on
- * @return {fabric.Object} thisArg
- * @chainable
- */
- drawBorders: function(ctx) {
- if (!this.hasBorders) {
- return this;
- }
-
- var padding = this.padding,
- padding2 = padding * 2,
- vpt = this.getViewportTransform();
-
- ctx.save();
-
- ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
- ctx.strokeStyle = this.borderColor;
-
- var scaleX = 1 / this._constrainScale(this.scaleX),
- scaleY = 1 / this._constrainScale(this.scaleY);
-
- ctx.lineWidth = 1 / this.borderScaleFactor;
-
- var w = this.getWidth(),
- h = this.getHeight(),
- strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0,
- 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 / scaleX;
- }
- else if (hLine) {
- h = strokeWidth / scaleY;
- }
- if (strokeW) {
- w += strokeWidth / scaleX;
- }
- if (strokeH) {
- h += strokeWidth / scaleY;
- }
- var wh = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true),
- width = wh.x,
- height = wh.y;
- if (this.group) {
- width = width * this.group.scaleX;
- height = height * this.group.scaleY;
- }
-
- ctx.strokeRect(
- ~~(-(width / 2) - padding) - 0.5, // offset needed to make lines look sharper
- ~~(-(height / 2) - padding) - 0.5,
- ~~(width + padding2) + 1, // double offset needed to make lines look sharper
- ~~(height + padding2) + 1
- );
-
- if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) {
-
- var rotateHeight = ( -height - (padding * 2)) / 2;
-
- ctx.beginPath();
- ctx.moveTo(0, rotateHeight);
- ctx.lineTo(0, rotateHeight - this.rotatingPointOffset);
- ctx.closePath();
- ctx.stroke();
- }
-
- ctx.restore();
- return this;
- },
-
- /**
- * Draws corners of an object's bounding box.
- * Requires public properties: width, height
- * Requires public options: cornerSize, padding
- * @param {CanvasRenderingContext2D} ctx Context to draw on
- * @return {fabric.Object} thisArg
- * @chainable
- */
- drawControls: function(ctx) {
- if (!this.hasControls) {
- return this;
- }
-
- var size = this.cornerSize,
- size2 = size / 2,
- vpt = this.getViewportTransform(),
- strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0,
- 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;
- }
- w *= this.scaleX;
- h *= this.scaleY;
-
- var wh = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true),
- width = wh.x,
- height = wh.y,
- left = -(width / 2),
- top = -(height / 2),
- padding = this.padding,
- scaleOffset = size2,
- scaleOffsetSize = size2 - size,
- methodName = this.transparentCorners ? 'strokeRect' : 'fillRect';
-
- ctx.save();
-
- ctx.lineWidth = 1;
-
- ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
- ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
-
- // top-left
- this._drawControl('tl', ctx, methodName,
- left - scaleOffset - padding,
- top - scaleOffset - padding);
-
- // top-right
- this._drawControl('tr', ctx, methodName,
- left + width - scaleOffset + padding,
- top - scaleOffset - padding);
-
- // bottom-left
- this._drawControl('bl', ctx, methodName,
- left - scaleOffset - padding,
- top + height + scaleOffsetSize + padding);
-
- // bottom-right
- this._drawControl('br', ctx, methodName,
- left + width + scaleOffsetSize + padding,
- top + height + scaleOffsetSize + padding);
-
- if (!this.get('lockUniScaling')) {
-
- // middle-top
- this._drawControl('mt', ctx, methodName,
- left + width/2 - scaleOffset,
- top - scaleOffset - padding);
-
- // middle-bottom
- this._drawControl('mb', ctx, methodName,
- left + width/2 - scaleOffset,
- top + height + scaleOffsetSize + padding);
-
- // middle-right
- this._drawControl('mr', ctx, methodName,
- left + width + scaleOffsetSize + padding,
- top + height/2 - scaleOffset);
-
- // middle-left
- this._drawControl('ml', ctx, methodName,
- left - scaleOffset - padding,
- top + height/2 - scaleOffset);
- }
-
- // middle-top-rotate
- if (this.hasRotatingPoint) {
- this._drawControl('mtr', ctx, methodName,
- left + width/2 - scaleOffset,
- top - this.rotatingPointOffset - this.cornerSize/2 - padding);
- }
-
- ctx.restore();
-
- return this;
- },
-
- /**
- * @private
- */
- _drawControl: function(control, ctx, methodName, left, top) {
- var size = this.cornerSize;
-
- if (this.isControlVisible(control)) {
- isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size);
- ctx[methodName](left, top, size, size);
- }
- },
-
- /**
- * Returns true if the specified control is visible, false otherwise.
- * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
- * @returns {Boolean} true if the specified control is visible, false otherwise
- */
- isControlVisible: function(controlName) {
- return this._getControlsVisibility()[controlName];
- },
-
- /**
- * Sets the visibility of the specified control.
- * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
- * @param {Boolean} visible true to set the specified control visible, false otherwise
- * @return {fabric.Object} thisArg
- * @chainable
- */
- setControlVisible: function(controlName, visible) {
- this._getControlsVisibility()[controlName] = visible;
- return this;
- },
-
- /**
- * Sets the visibility state of object controls.
- * @param {Object} [options] Options object
- * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it
- * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it
- * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it
- * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it
- * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it
- * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it
- * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it
- * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it
- * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it
- * @return {fabric.Object} thisArg
- * @chainable
- */
- setControlsVisibility: function(options) {
- options || (options = { });
-
- for (var p in options) {
- this.setControlVisible(p, options[p]);
- }
- return this;
- },
-
- /**
- * Returns the instance of the control visibility set for this object.
- * @private
- * @returns {Object}
- */
- _getControlsVisibility: function() {
- if (!this._controlsVisibility) {
- this._controlsVisibility = {
- tl: true,
- tr: true,
- br: true,
- bl: true,
- ml: true,
- mt: true,
- mr: true,
- mb: true,
- mtr: true
- };
- }
- return this._controlsVisibility;
- }
- });
-})();
-
-
-fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
-
- /**
- * Animation duration (in ms) for fx* methods
- * @type Number
- * @default
- */
- FX_DURATION: 500,
-
- /**
- * Centers object horizontally with animation.
- * @param {fabric.Object} object Object to center
- * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
- * @param {Function} [callbacks.onComplete] Invoked on completion
- * @param {Function} [callbacks.onChange] Invoked on every step of animation
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- fxCenterObjectH: function (object, callbacks) {
- callbacks = callbacks || { };
-
- var empty = function() { },
- onComplete = callbacks.onComplete || empty,
- onChange = callbacks.onChange || empty,
- _this = this;
-
- fabric.util.animate({
- startValue: object.get('left'),
- endValue: this.getCenter().left,
- duration: this.FX_DURATION,
- onChange: function(value) {
- object.set('left', value);
- _this.renderAll();
- onChange();
- },
- onComplete: function() {
- object.setCoords();
- onComplete();
- }
- });
-
- return this;
- },
-
- /**
- * Centers object vertically with animation.
- * @param {fabric.Object} object Object to center
- * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
- * @param {Function} [callbacks.onComplete] Invoked on completion
- * @param {Function} [callbacks.onChange] Invoked on every step of animation
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- fxCenterObjectV: function (object, callbacks) {
- callbacks = callbacks || { };
-
- var empty = function() { },
- onComplete = callbacks.onComplete || empty,
- onChange = callbacks.onChange || empty,
- _this = this;
-
- fabric.util.animate({
- startValue: object.get('top'),
- endValue: this.getCenter().top,
- duration: this.FX_DURATION,
- onChange: function(value) {
- object.set('top', value);
- _this.renderAll();
- onChange();
- },
- onComplete: function() {
- object.setCoords();
- onComplete();
- }
- });
-
- return this;
- },
-
- /**
- * Same as `fabric.Canvas#remove` but animated
- * @param {fabric.Object} object Object to remove
- * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
- * @param {Function} [callbacks.onComplete] Invoked on completion
- * @param {Function} [callbacks.onChange] Invoked on every step of animation
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- fxRemove: function (object, callbacks) {
- callbacks = callbacks || { };
-
- var empty = function() { },
- onComplete = callbacks.onComplete || empty,
- onChange = callbacks.onChange || empty,
- _this = this;
-
- fabric.util.animate({
- startValue: object.get('opacity'),
- endValue: 0,
- duration: this.FX_DURATION,
- onStart: function() {
- object.set('active', false);
- },
- onChange: function(value) {
- object.set('opacity', value);
- _this.renderAll();
- onChange();
- },
- onComplete: function () {
- _this.remove(object);
- onComplete();
- }
- });
-
- return this;
- }
-});
-
-fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
- /**
- * Animates object's properties
- * @param {String|Object} property Property to animate (if string) or properties to animate (if object)
- * @param {Number|Object} value Value to animate property to (if string was given first) or options object
- * @return {fabric.Object} thisArg
- * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation}
- * @chainable
- *
- * As object — multiple properties
- *
- * object.animate({ left: ..., top: ... });
- * object.animate({ left: ..., top: ... }, { duration: ... });
- *
- * As string — one property
- *
- * object.animate('left', ...);
- * object.animate('left', { duration: ... });
- *
- */
- animate: function() {
- if (arguments[0] && typeof arguments[0] === 'object') {
- var propsToAnimate = [ ], prop, skipCallbacks;
- for (prop in arguments[0]) {
- propsToAnimate.push(prop);
- }
- for (var i = 0, len = propsToAnimate.length; i < len; i++) {
- prop = propsToAnimate[i];
- skipCallbacks = i !== len - 1;
- this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
- }
- }
- else {
- this._animate.apply(this, arguments);
- }
- return this;
- },
-
- /**
- * @private
- * @param {String} property Property to animate
- * @param {String} to Value to animate to
- * @param {Object} [options] Options object
- * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked
- */
- _animate: function(property, to, options, skipCallbacks) {
- var _this = this, propPair;
-
- to = to.toString();
-
- if (!options) {
- options = { };
- }
- else {
- options = fabric.util.object.clone(options);
- }
-
- if (~property.indexOf('.')) {
- propPair = property.split('.');
- }
-
- var currentValue = propPair
- ? this.get(propPair[0])[propPair[1]]
- : this.get(property);
-
- if (!('from' in options)) {
- options.from = currentValue;
- }
-
- if (~to.indexOf('=')) {
- to = currentValue + parseFloat(to.replace('=', ''));
- }
- else {
- to = parseFloat(to);
- }
-
- fabric.util.animate({
- startValue: options.from,
- endValue: to,
- byValue: options.by,
- easing: options.easing,
- duration: options.duration,
- abort: options.abort && function() {
- return options.abort.call(_this);
- },
- onChange: function(value) {
- if (propPair) {
- _this[propPair[0]][propPair[1]] = value;
- }
- else {
- _this.set(property, value);
- }
- if (skipCallbacks) {
- return;
- }
- options.onChange && options.onChange();
- },
- onComplete: function() {
- if (skipCallbacks) {
- return;
- }
-
- _this.setCoords();
- options.onComplete && options.onComplete();
- }
- });
- }
-});
-
-
(function(global) {
'use strict';
@@ -14354,17 +12576,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
ry = this.ry ? Math.min(this.ry, this.height / 2) : 0,
w = this.width,
h = this.height,
- x = noTransform ? this.left : 0,
- y = noTransform ? this.top : 0,
+ 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();
- if (!noTransform) {
- ctx.translate(-this.width / 2, -this.height / 2);
- }
-
ctx.moveTo(x + rx, y);
ctx.lineTo(x + w - rx, y);
@@ -14554,19 +12772,25 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* top: 100
* });
*/
- initialize: function(points, options, skipOffset) {
+ initialize: function(points, options) {
options = options || { };
this.set('points', points);
this.callSuper('initialize', options);
- this._calcDimensions(skipOffset);
+ this._calcDimensions();
},
/**
* @private
- * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
*/
- _calcDimensions: function(skipOffset) {
- return fabric.Polygon.prototype._calcDimensions.call(this, skipOffset);
+ _calcDimensions: function() {
+ return fabric.Polygon.prototype._calcDimensions.call(this);
+ },
+
+ /**
+ * @private
+ */
+ _applyPointOffset: function() {
+ return fabric.Polygon.prototype._applyPointOffset.call(this);
},
/**
@@ -14612,6 +12836,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
_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];
@@ -14676,7 +12908,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
return null;
}
- return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options), true);
+ return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));
};
/* _FROM_SVG_END_ */
@@ -14736,21 +12968,19 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* Constructor
* @param {Array} points Array of points
* @param {Object} [options] Options object
- * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
* @return {fabric.Polygon} thisArg
*/
- initialize: function(points, options, skipOffset) {
+ initialize: function(points, options) {
options = options || { };
this.points = points;
this.callSuper('initialize', options);
- this._calcDimensions(skipOffset);
+ this._calcDimensions();
},
/**
* @private
- * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
*/
- _calcDimensions: function(skipOffset) {
+ _calcDimensions: function() {
var points = this.points,
minX = min(points, 'x'),
@@ -14761,20 +12991,19 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
this.width = (maxX - minX) || 1;
this.height = (maxY - minY) || 1;
- this.minX = minX;
- this.minY = minY;
-
- if (skipOffset) {
- return;
- }
-
- var halfWidth = this.width / 2 + this.minX,
- halfHeight = this.height / 2 + this.minY;
+ this.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 -= halfWidth;
- p.y -= halfHeight;
+ p.x -= (this.left + this.width / 2);
+ p.y -= (this.top + this.height / 2);
}, this);
},
@@ -14823,6 +13052,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
_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];
@@ -14891,7 +13128,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
return null;
}
- return new fabric.Polygon(points, extend(parsedAttributes, options), true);
+ return new fabric.Polygon(points, extend(parsedAttributes, options));
};
/* _FROM_SVG_END_ */
@@ -16941,96 +15178,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
})(typeof exports !== 'undefined' ? exports : this);
-fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
-
- /**
- * @private
- * @return {Number} angle value
- */
- _getAngleValueForStraighten: function() {
- var angle = this.getAngle() % 360;
- if (angle > 0) {
- return Math.round((angle - 1) / 90) * 90;
- }
- return Math.round(angle / 90) * 90;
- },
-
- /**
- * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)
- * @return {fabric.Object} thisArg
- * @chainable
- */
- straighten: function() {
- this.setAngle(this._getAngleValueForStraighten());
- return this;
- },
-
- /**
- * Same as {@link fabric.Object.prototype.straighten} but with animation
- * @param {Object} callbacks Object with callback functions
- * @param {Function} [callbacks.onComplete] Invoked on completion
- * @param {Function} [callbacks.onChange] Invoked on every step of animation
- * @return {fabric.Object} thisArg
- * @chainable
- */
- fxStraighten: function(callbacks) {
- callbacks = callbacks || { };
-
- var empty = function() { },
- onComplete = callbacks.onComplete || empty,
- onChange = callbacks.onChange || empty,
- _this = this;
-
- fabric.util.animate({
- startValue: this.get('angle'),
- endValue: this._getAngleValueForStraighten(),
- duration: this.FX_DURATION,
- onChange: function(value) {
- _this.setAngle(value);
- onChange();
- },
- onComplete: function() {
- _this.setCoords();
- onComplete();
- },
- onStart: function() {
- _this.set('active', false);
- }
- });
-
- return this;
- }
-});
-
-fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
-
- /**
- * Straightens object, then rerenders canvas
- * @param {fabric.Object} object Object to straighten
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- straightenObject: function (object) {
- object.straighten();
- this.renderAll();
- return this;
- },
-
- /**
- * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
- * @param {fabric.Object} object Object to straighten
- * @return {fabric.Canvas} thisArg
- * @chainable
- */
- fxStraightenObject: function (object) {
- object.fxStraighten({
- onChange: this.renderAll.bind(this)
- });
- return this;
- }
-});
-
-
/**
* @namespace fabric.Image.filters
* @memberOf fabric.Image
@@ -19258,8 +17405,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
: (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight;
return {
- textLeft: textLeft + (this.group ? this.left : 0),
- textTop: textTop + (this.group ? this.top : 0),
+ 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
};
},
@@ -19552,3162 +17699,4 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
})(typeof exports !== 'undefined' ? exports : this);
-(function() {
-
- var clone = fabric.util.object.clone;
-
- /**
- * IText class (introduced in v1.4) Events are also fired with "text:"
- * prefix when observing canvas.
- * @class fabric.IText
- * @extends fabric.Text
- * @mixes fabric.Observable
- *
- * @fires changed
- * @fires selection:changed
- * @fires editing:entered
- * @fires editing:exited
- *
- * @return {fabric.IText} thisArg
- * @see {@link fabric.IText#initialize} for constructor definition
- *
- * Supported key combinations:
- *
- * Move cursor: left, right, up, down
- * Select character: shift + left, shift + right
- * Select text vertically: shift + up, shift + down
- * Move cursor by word: alt + left, alt + right
- * Select words: shift + alt + left, shift + alt + right
- * Move cursor to line start/end: cmd + left, cmd + right
- * Select till start/end of line: cmd + shift + left, cmd + shift + right
- * Jump to start/end of text: cmd + up, cmd + down
- * Select till start/end of text: cmd + shift + up, cmd + shift + down
- * Delete character: backspace
- * Delete word: alt + backspace
- * Delete line: cmd + backspace
- * Forward delete: delete
- * Copy text: ctrl/cmd + c
- * Paste text: ctrl/cmd + v
- * Cut text: ctrl/cmd + x
- * Select entire text: ctrl/cmd + a
- *
- *
- * Supported mouse/touch combination
- *
- * Position cursor: click/touch
- * Create selection: click/touch & drag
- * Create selection: click & shift + click
- * Select word: double click
- * Select line: triple click
- *
- */
- fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ {
-
- /**
- * Type of an object
- * @type String
- * @default
- */
- type: 'i-text',
-
- /**
- * Index where text selection starts (or where cursor is when there is no selection)
- * @type Nubmer
- * @default
- */
- selectionStart: 0,
-
- /**
- * Index where text selection ends
- * @type Nubmer
- * @default
- */
- selectionEnd: 0,
-
- /**
- * Color of text selection
- * @type String
- * @default
- */
- selectionColor: 'rgba(17,119,255,0.3)',
-
- /**
- * Indicates whether text is in editing mode
- * @type Boolean
- * @default
- */
- isEditing: false,
-
- /**
- * Indicates whether a text can be edited
- * @type Boolean
- * @default
- */
- editable: true,
-
- /**
- * Border color of text object while it's in editing mode
- * @type String
- * @default
- */
- editingBorderColor: 'rgba(102,153,255,0.25)',
-
- /**
- * Width of cursor (in px)
- * @type Number
- * @default
- */
- cursorWidth: 2,
-
- /**
- * Color of default cursor (when not overwritten by character style)
- * @type String
- * @default
- */
- cursorColor: '#333',
-
- /**
- * Delay between cursor blink (in ms)
- * @type Number
- * @default
- */
- cursorDelay: 1000,
-
- /**
- * Duration of cursor fadein (in ms)
- * @type Number
- * @default
- */
- cursorDuration: 600,
-
- /**
- * Object containing character styles
- * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line)
- * @type Object
- * @default
- */
- styles: null,
-
- /**
- * Indicates whether internal text char widths can be cached
- * @type Boolean
- * @default
- */
- caching: true,
-
- /**
- * @private
- * @type Boolean
- * @default
- */
- _skipFillStrokeCheck: true,
-
- /**
- * @private
- */
- _reSpace: /\s|\n/,
-
- /**
- * @private
- */
- _fontSizeFraction: 4,
-
- /**
- * @private
- */
- _currentCursorOpacity: 0,
-
- /**
- * @private
- */
- _selectionDirection: null,
-
- /**
- * @private
- */
- _abortCursorAnimation: false,
-
- /**
- * @private
- */
- _charWidthsCache: { },
-
- /**
- * Constructor
- * @param {String} text Text string
- * @param {Object} [options] Options object
- * @return {fabric.IText} thisArg
- */
- initialize: function(text, options) {
- this.styles = options ? (options.styles || { }) : { };
- this.callSuper('initialize', text, options);
- this.initBehavior();
-
- fabric.IText.instances.push(this);
-
- // caching
- this.__lineWidths = { };
- this.__lineHeights = { };
- this.__lineOffsets = { };
- },
-
- /**
- * Returns true if object has no styling
- */
- isEmptyStyles: function() {
- if (!this.styles) {
- return true;
- }
- var obj = this.styles;
-
- for (var p1 in obj) {
- for (var p2 in obj[p1]) {
- /*jshint unused:false */
- for (var p3 in obj[p1][p2]) {
- return false;
- }
- }
- }
- return true;
- },
-
- /**
- * Sets selection start (left boundary of a selection)
- * @param {Number} index Index to set selection start to
- */
- setSelectionStart: function(index) {
- if (this.selectionStart !== index) {
- this.fire('selection:changed');
- this.canvas && this.canvas.fire('text:selection:changed', { target: this });
- }
- this.selectionStart = index;
- this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index);
- },
-
- /**
- * Sets selection end (right boundary of a selection)
- * @param {Number} index Index to set selection end to
- */
- setSelectionEnd: function(index) {
- if (this.selectionEnd !== index) {
- this.fire('selection:changed');
- this.canvas && this.canvas.fire('text:selection:changed', { target: this });
- }
- this.selectionEnd = index;
- this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index);
- },
-
- /**
- * Gets style of a current selection/cursor (at the start position)
- * @param {Number} [startIndex] Start index to get styles at
- * @param {Number} [endIndex] End index to get styles at
- * @return {Object} styles Style object at a specified (or current) index
- */
- getSelectionStyles: function(startIndex, endIndex) {
-
- if (arguments.length === 2) {
- var styles = [ ];
- for (var i = startIndex; i < endIndex; i++) {
- styles.push(this.getSelectionStyles(i));
- }
- return styles;
- }
-
- var loc = this.get2DCursorLocation(startIndex);
- if (this.styles[loc.lineIndex]) {
- return this.styles[loc.lineIndex][loc.charIndex] || { };
- }
-
- return { };
- },
-
- /**
- * Sets style of a current selection
- * @param {Object} [styles] Styles object
- * @return {fabric.IText} thisArg
- * @chainable
- */
- setSelectionStyles: function(styles) {
- if (this.selectionStart === this.selectionEnd) {
- this._extendStyles(this.selectionStart, styles);
- }
- else {
- for (var i = this.selectionStart; i < this.selectionEnd; i++) {
- this._extendStyles(i, styles);
- }
- }
- return this;
- },
-
- /**
- * @private
- */
- _extendStyles: function(index, styles) {
- var loc = this.get2DCursorLocation(index);
-
- if (!this.styles[loc.lineIndex]) {
- this.styles[loc.lineIndex] = { };
- }
- if (!this.styles[loc.lineIndex][loc.charIndex]) {
- this.styles[loc.lineIndex][loc.charIndex] = { };
- }
-
- fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles);
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _render: function(ctx) {
- this.callSuper('_render', ctx);
- this.ctx = ctx;
- this.isEditing && this.renderCursorOrSelection();
- },
-
- /**
- * Renders cursor or selection (depending on what exists)
- */
- renderCursorOrSelection: function() {
- if (!this.active) {
- return;
- }
-
- var chars = this.text.split(''),
- boundaries;
-
- if (this.selectionStart === this.selectionEnd) {
- boundaries = this._getCursorBoundaries(chars, 'cursor');
- this.renderCursor(boundaries);
- }
- else {
- boundaries = this._getCursorBoundaries(chars, 'selection');
- this.renderSelection(chars, boundaries);
- }
- },
-
- /**
- * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start)
- * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
- */
- get2DCursorLocation: function(selectionStart) {
- if (typeof selectionStart === 'undefined') {
- selectionStart = this.selectionStart;
- }
- var textBeforeCursor = this.text.slice(0, selectionStart),
- linesBeforeCursor = textBeforeCursor.split(this._reNewline);
-
- return {
- lineIndex: linesBeforeCursor.length - 1,
- charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length
- };
- },
-
- /**
- * Returns complete style of char at the current cursor
- * @param {Number} lineIndex Line index
- * @param {Number} charIndex Char index
- * @return {Object} Character style
- */
- getCurrentCharStyle: function(lineIndex, charIndex) {
- var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)];
-
- return {
- fontSize: style && style.fontSize || this.fontSize,
- fill: style && style.fill || this.fill,
- textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor,
- textDecoration: style && style.textDecoration || this.textDecoration,
- fontFamily: style && style.fontFamily || this.fontFamily,
- fontWeight: style && style.fontWeight || this.fontWeight,
- fontStyle: style && style.fontStyle || this.fontStyle,
- stroke: style && style.stroke || this.stroke,
- strokeWidth: style && style.strokeWidth || this.strokeWidth
- };
- },
-
- /**
- * Returns fontSize of char at the current cursor
- * @param {Number} lineIndex Line index
- * @param {Number} charIndex Char index
- * @return {Number} Character font size
- */
- getCurrentCharFontSize: function(lineIndex, charIndex) {
- return (
- this.styles[lineIndex] &&
- this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&
- this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize;
- },
-
- /**
- * Returns color (fill) of char at the current cursor
- * @param {Number} lineIndex Line index
- * @param {Number} charIndex Char index
- * @return {String} Character color (fill)
- */
- getCurrentCharColor: function(lineIndex, charIndex) {
- return (
- this.styles[lineIndex] &&
- this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&
- this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor;
- },
-
- /**
- * Returns cursor boundaries (left, top, leftOffset, topOffset)
- * @private
- * @param {Array} chars Array of characters
- * @param {String} typeOfBoundaries
- */
- _getCursorBoundaries: function(chars, typeOfBoundaries) {
-
- var cursorLocation = this.get2DCursorLocation(),
-
- textLines = this.text.split(this._reNewline),
-
- // left/top are left/top of entire text box
- // leftOffset/topOffset are offset from that left/top point of a text box
-
- left = Math.round(this._getLeftOffset()),
- top = this._getTopOffset(),
-
- offsets = this._getCursorBoundariesOffsets(
- chars, typeOfBoundaries, cursorLocation, textLines);
-
- return {
- left: left,
- top: top,
- leftOffset: offsets.left + offsets.lineLeft,
- topOffset: offsets.top
- };
- },
-
- /**
- * @private
- */
- _getCursorBoundariesOffsets: function(chars, typeOfBoundaries, cursorLocation, textLines) {
-
- var lineLeftOffset = 0,
-
- lineIndex = 0,
- charIndex = 0,
-
- leftOffset = 0,
- topOffset = typeOfBoundaries === 'cursor'
- // selection starts at the very top of the line,
- // whereas cursor starts at the padding created by line height
- ? (this._getHeightOfLine(this.ctx, 0) -
- this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex))
- : 0;
-
- for (var i = 0; i < this.selectionStart; i++) {
- if (chars[i] === '\n') {
- leftOffset = 0;
- var index = lineIndex + (typeOfBoundaries === 'cursor' ? 1 : 0);
- topOffset += this._getCachedLineHeight(index);
-
- lineIndex++;
- charIndex = 0;
- }
- else {
- leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex);
- charIndex++;
- }
-
- lineLeftOffset = this._getCachedLineOffset(lineIndex, textLines);
- }
-
- this._clearCache();
-
- return {
- top: topOffset,
- left: leftOffset,
- lineLeft: lineLeftOffset
- };
- },
-
- /**
- * @private
- */
- _clearCache: function() {
- this.__lineWidths = { };
- this.__lineHeights = { };
- this.__lineOffsets = { };
- },
-
- /**
- * @private
- */
- _getCachedLineHeight: function(index) {
- return this.__lineHeights[index] ||
- (this.__lineHeights[index] = this._getHeightOfLine(this.ctx, index));
- },
-
- /**
- * @private
- */
- _getCachedLineWidth: function(lineIndex, textLines) {
- return this.__lineWidths[lineIndex] ||
- (this.__lineWidths[lineIndex] = this._getWidthOfLine(this.ctx, lineIndex, textLines));
- },
-
- /**
- * @private
- */
- _getCachedLineOffset: function(lineIndex, textLines) {
- var widthOfLine = this._getCachedLineWidth(lineIndex, textLines);
-
- return this.__lineOffsets[lineIndex] ||
- (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine));
- },
-
- /**
- * Renders cursor
- * @param {Object} boundaries
- */
- renderCursor: function(boundaries) {
- var ctx = this.ctx;
-
- ctx.save();
-
- var cursorLocation = this.get2DCursorLocation(),
- lineIndex = cursorLocation.lineIndex,
- charIndex = cursorLocation.charIndex,
- charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
- leftOffset = (lineIndex === 0 && charIndex === 0)
- ? this._getCachedLineOffset(lineIndex, this.text.split(this._reNewline))
- : boundaries.leftOffset;
-
- ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);
- ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity;
-
- ctx.fillRect(
- boundaries.left + leftOffset,
- boundaries.top + boundaries.topOffset,
- this.cursorWidth / this.scaleX,
- charHeight);
-
- ctx.restore();
- },
-
- /**
- * Renders text selection
- * @param {Array} chars Array of characters
- * @param {Object} boundaries Object with left/top/leftOffset/topOffset
- */
- renderSelection: function(chars, boundaries) {
- var ctx = this.ctx;
-
- ctx.save();
-
- ctx.fillStyle = this.selectionColor;
-
- var start = this.get2DCursorLocation(this.selectionStart),
- end = this.get2DCursorLocation(this.selectionEnd),
- startLine = start.lineIndex,
- endLine = end.lineIndex,
- textLines = this.text.split(this._reNewline);
-
- for (var i = startLine; i <= endLine; i++) {
- var lineOffset = this._getCachedLineOffset(i, textLines) || 0,
- lineHeight = this._getCachedLineHeight(i),
- boxWidth = 0;
-
- if (i === startLine) {
- for (var j = 0, len = textLines[i].length; j < len; j++) {
- if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) {
- boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j);
- }
- if (j < start.charIndex) {
- lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j);
- }
- }
- }
- else if (i > startLine && i < endLine) {
- boxWidth += this._getCachedLineWidth(i, textLines) || 5;
- }
- else if (i === endLine) {
- for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {
- boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2);
- }
- }
-
- ctx.fillRect(
- boundaries.left + lineOffset,
- boundaries.top + boundaries.topOffset,
- boxWidth,
- lineHeight);
-
- boundaries.topOffset += lineHeight;
- }
- ctx.restore();
- },
-
- /**
- * @private
- * @param {String} method
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _renderChars: function(method, ctx, line, left, top, lineIndex) {
-
- if (this.isEmptyStyles()) {
- return this._renderCharsFast(method, ctx, line, left, top);
- }
-
- this.skipTextAlign = true;
-
- // set proper box offset
- left -= this.textAlign === 'center'
- ? (this.width / 2)
- : (this.textAlign === 'right')
- ? this.width
- : 0;
-
- // set proper line offset
- var textLines = this.text.split(this._reNewline),
- lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines),
- lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines),
- lineLeftOffset = this._getLineLeftOffset(lineWidth),
- chars = line.split(''),
- prevStyle,
- charsToRender = '';
-
- left += lineLeftOffset || 0;
-
- ctx.save();
-
- for (var i = 0, len = chars.length; i <= len; i++) {
- prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);
- var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1);
-
- if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) {
- this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight);
- charsToRender = '';
- prevStyle = thisStyle;
- }
- charsToRender += chars[i];
- }
-
- ctx.restore();
- },
-
- /**
- * @private
- * @param {String} method
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {String} line Content of the line
- * @param {Number} left Left coordinate
- * @param {Number} top Top coordinate
- */
- _renderCharsFast: function(method, ctx, line, left, top) {
- this.skipTextAlign = false;
-
- if (method === 'fillText' && this.fill) {
- this.callSuper('_renderChars', method, ctx, line, left, top);
- }
- if (method === 'strokeText' && this.stroke) {
- this.callSuper('_renderChars', method, ctx, line, left, top);
- }
- },
-
- /**
- * @private
- * @param {String} method
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {Number} lineIndex
- * @param {Number} i
- * @param {String} _char
- * @param {Number} left Left coordinate
- * @param {Number} top Top coordinate
- * @param {Number} lineHeight Height of the line
- */
- _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
- var decl, charWidth, charHeight;
-
- if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {
-
- var shouldStroke = decl.stroke || this.stroke,
- shouldFill = decl.fill || this.fill;
-
- ctx.save();
- charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl);
- charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);
-
- if (shouldFill) {
- ctx.fillText(_char, left, top);
- }
- if (shouldStroke) {
- ctx.strokeText(_char, left, top);
- }
-
- this._renderCharDecoration(ctx, decl, left, top, charWidth, lineHeight, charHeight);
- ctx.restore();
-
- ctx.translate(charWidth, 0);
- }
- else {
- if (method === 'strokeText' && this.stroke) {
- ctx[method](_char, left, top);
- }
- if (method === 'fillText' && this.fill) {
- ctx[method](_char, left, top);
- }
- charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i);
- this._renderCharDecoration(ctx, null, left, top, charWidth, lineHeight);
-
- ctx.translate(ctx.measureText(_char).width, 0);
- }
- },
-
- /**
- * @private
- * @param {Object} prevStyle
- * @param {Object} thisStyle
- */
- _hasStyleChanged: function(prevStyle, thisStyle) {
- return (prevStyle.fill !== thisStyle.fill ||
- prevStyle.fontSize !== thisStyle.fontSize ||
- prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor ||
- prevStyle.textDecoration !== thisStyle.textDecoration ||
- prevStyle.fontFamily !== thisStyle.fontFamily ||
- prevStyle.fontWeight !== thisStyle.fontWeight ||
- prevStyle.fontStyle !== thisStyle.fontStyle ||
- prevStyle.stroke !== thisStyle.stroke ||
- prevStyle.strokeWidth !== thisStyle.strokeWidth
- );
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) {
-
- var textDecoration = styleDeclaration
- ? (styleDeclaration.textDecoration || this.textDecoration)
- : this.textDecoration,
-
- fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize;
-
- if (!textDecoration) {
- return;
- }
-
- if (textDecoration.indexOf('underline') > -1) {
- this._renderCharDecorationAtOffset(
- ctx,
- left,
- top + (this.fontSize / this._fontSizeFraction),
- charWidth,
- 0,
- this.fontSize / 20
- );
- }
- if (textDecoration.indexOf('line-through') > -1) {
- this._renderCharDecorationAtOffset(
- ctx,
- left,
- top + (this.fontSize / this._fontSizeFraction),
- charWidth,
- charHeight / 2,
- fontSize / 20
- );
- }
- if (textDecoration.indexOf('overline') > -1) {
- this._renderCharDecorationAtOffset(
- ctx,
- left,
- top,
- charWidth,
- lineHeight - (this.fontSize / this._fontSizeFraction),
- this.fontSize / 20
- );
- }
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _renderCharDecorationAtOffset: function(ctx, left, top, charWidth, offset, thickness) {
- ctx.fillRect(left, top - offset, charWidth, thickness);
- },
-
- /**
- * @private
- * @param {String} method
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {String} line
- */
- _renderTextLine: function(method, ctx, line, left, top, lineIndex) {
- // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
- top += this.fontSize / 4;
- this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {Array} textLines
- */
- _renderTextDecoration: function(ctx, textLines) {
- if (this.isEmptyStyles()) {
- return this.callSuper('_renderTextDecoration', ctx, textLines);
- }
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {Array} textLines Array of all text lines
- */
- _renderTextLinesBackground: function(ctx, textLines) {
- if (!this.textBackgroundColor && !this.styles) {
- return;
- }
-
- ctx.save();
-
- if (this.textBackgroundColor) {
- ctx.fillStyle = this.textBackgroundColor;
- }
-
- var lineHeights = 0,
- fractionOfFontSize = this.fontSize / this._fontSizeFraction;
-
- for (var i = 0, len = textLines.length; i < len; i++) {
-
- var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
- if (textLines[i] === '') {
- lineHeights += heightOfLine;
- continue;
- }
-
- var lineWidth = this._getWidthOfLine(ctx, i, textLines),
- lineLeftOffset = this._getLineLeftOffset(lineWidth);
-
- if (this.textBackgroundColor) {
- ctx.fillStyle = this.textBackgroundColor;
-
- ctx.fillRect(
- this._getLeftOffset() + lineLeftOffset,
- this._getTopOffset() + lineHeights + fractionOfFontSize,
- lineWidth,
- heightOfLine
- );
- }
- if (this.styles[i]) {
- for (var j = 0, jlen = textLines[i].length; j < jlen; j++) {
- if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) {
-
- var _char = textLines[i][j];
-
- ctx.fillStyle = this.styles[i][j].textBackgroundColor;
-
- ctx.fillRect(
- this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j, textLines),
- this._getTopOffset() + lineHeights + fractionOfFontSize,
- this._getWidthOfChar(ctx, _char, i, j, textLines) + 1,
- heightOfLine
- );
- }
- }
- }
- lineHeights += heightOfLine;
- }
- ctx.restore();
- },
-
- /**
- * @private
- */
- _getCacheProp: function(_char, styleDeclaration) {
- return _char +
-
- styleDeclaration.fontFamily +
- styleDeclaration.fontSize +
- styleDeclaration.fontWeight +
- styleDeclaration.fontStyle +
-
- styleDeclaration.shadow;
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {String} _char
- * @param {Number} lineIndex
- * @param {Number} charIndex
- * @param {Object} [decl]
- */
- _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) {
- var styleDeclaration = decl ||
- (this.styles[lineIndex] &&
- this.styles[lineIndex][charIndex]);
-
- if (styleDeclaration) {
- // cloning so that original style object is not polluted with following font declarations
- styleDeclaration = clone(styleDeclaration);
- }
- else {
- styleDeclaration = { };
- }
-
- this._applyFontStyles(styleDeclaration);
-
- var cacheProp = this._getCacheProp(_char, styleDeclaration);
-
- // short-circuit if no styles
- if (this.isEmptyStyles() && this._charWidthsCache[cacheProp] && this.caching) {
- return this._charWidthsCache[cacheProp];
- }
-
- if (typeof styleDeclaration.shadow === 'string') {
- styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow);
- }
-
- var fill = styleDeclaration.fill || this.fill;
- ctx.fillStyle = fill.toLive
- ? fill.toLive(ctx)
- : fill;
-
- if (styleDeclaration.stroke) {
- ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive)
- ? styleDeclaration.stroke.toLive(ctx)
- : styleDeclaration.stroke;
- }
-
- ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;
- ctx.font = this._getFontDeclaration.call(styleDeclaration);
- this._setShadow.call(styleDeclaration, ctx);
-
- if (!this.caching) {
- return ctx.measureText(_char).width;
- }
-
- if (!this._charWidthsCache[cacheProp]) {
- this._charWidthsCache[cacheProp] = ctx.measureText(_char).width;
- }
-
- return this._charWidthsCache[cacheProp];
- },
-
- /**
- * @private
- * @param {Object} styleDeclaration
- */
- _applyFontStyles: function(styleDeclaration) {
- if (!styleDeclaration.fontFamily) {
- styleDeclaration.fontFamily = this.fontFamily;
- }
- if (!styleDeclaration.fontSize) {
- styleDeclaration.fontSize = this.fontSize;
- }
- if (!styleDeclaration.fontWeight) {
- styleDeclaration.fontWeight = this.fontWeight;
- }
- if (!styleDeclaration.fontStyle) {
- styleDeclaration.fontStyle = this.fontStyle;
- }
- },
-
- /**
- * @private
- * @param {Number} lineIndex
- * @param {Number} charIndex
- */
- _getStyleDeclaration: function(lineIndex, charIndex) {
- return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
- ? clone(this.styles[lineIndex][charIndex])
- : { };
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) {
- if (this.textAlign === 'justify' && /\s/.test(_char)) {
- return this._getWidthOfSpace(ctx, lineIndex);
- }
-
- var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex);
- this._applyFontStyles(styleDeclaration);
- var cacheProp = this._getCacheProp(_char, styleDeclaration);
-
- if (this._charWidthsCache[cacheProp] && this.caching) {
- return this._charWidthsCache[cacheProp];
- }
- else if (ctx) {
- ctx.save();
- var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);
- ctx.restore();
- return width;
- }
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getHeightOfChar: function(ctx, _char, lineIndex, charIndex) {
- if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) {
- return this.styles[lineIndex][charIndex].fontSize || this.fontSize;
- }
- return this.fontSize;
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getWidthOfCharAt: function(ctx, lineIndex, charIndex, lines) {
- lines = lines || this.text.split(this._reNewline);
- var _char = lines[lineIndex].split('')[charIndex];
- return this._getWidthOfChar(ctx, _char, lineIndex, charIndex);
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getHeightOfCharAt: function(ctx, lineIndex, charIndex, lines) {
- lines = lines || this.text.split(this._reNewline);
- var _char = lines[lineIndex].split('')[charIndex];
- return this._getHeightOfChar(ctx, _char, lineIndex, charIndex);
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getWidthOfCharsAt: function(ctx, lineIndex, charIndex, lines) {
- var width = 0;
- for (var i = 0; i < charIndex; i++) {
- width += this._getWidthOfCharAt(ctx, lineIndex, i, lines);
- }
- return width;
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getWidthOfLine: function(ctx, lineIndex, textLines) {
- // if (!this.styles[lineIndex]) {
- // return this.callSuper('_getLineWidth', ctx, textLines[lineIndex]);
- // }
- return this._getWidthOfCharsAt(ctx, lineIndex, textLines[lineIndex].length, textLines);
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {Number} lineIndex
- */
- _getWidthOfSpace: function (ctx, lineIndex) {
- var lines = this.text.split(this._reNewline),
- line = lines[lineIndex],
- words = line.split(/\s+/),
- wordsWidth = this._getWidthOfWords(ctx, line, lineIndex),
- widthDiff = this.width - wordsWidth,
- numSpaces = words.length - 1,
- width = widthDiff / numSpaces;
-
- return width;
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {Number} line
- * @param {Number} lineIndex
- */
- _getWidthOfWords: function (ctx, line, lineIndex) {
- var width = 0;
-
- for (var charIndex = 0; charIndex < line.length; charIndex++) {
- var _char = line[charIndex];
-
- if (!_char.match(/\s/)) {
- width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex);
- }
- }
-
- return width;
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getTextWidth: function(ctx, textLines) {
-
- if (this.isEmptyStyles()) {
- return this.callSuper('_getTextWidth', ctx, textLines);
- }
-
- var maxWidth = this._getWidthOfLine(ctx, 0, textLines);
-
- for (var i = 1, len = textLines.length; i < len; i++) {
- var currentLineWidth = this._getWidthOfLine(ctx, i, textLines);
- if (currentLineWidth > maxWidth) {
- maxWidth = currentLineWidth;
- }
- }
- return maxWidth;
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- */
- _getHeightOfLine: function(ctx, lineIndex, textLines) {
-
- textLines = textLines || this.text.split(this._reNewline);
-
- var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0),
- line = textLines[lineIndex],
- chars = line.split('');
-
- for (var i = 1, len = chars.length; i < len; i++) {
- var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i);
- if (currentCharHeight > maxHeight) {
- maxHeight = currentCharHeight;
- }
- }
-
- return maxHeight * this.lineHeight;
- },
-
- /**
- * @private
- * @param {CanvasRenderingContext2D} ctx Context to render on
- * @param {Array} textLines Array of all text lines
- */
- _getTextHeight: function(ctx, textLines) {
- var height = 0;
- for (var i = 0, len = textLines.length; i < len; i++) {
- height += this._getHeightOfLine(ctx, i, textLines);
- }
- return height;
- },
-
- /**
- * @private
- */
- _getTopOffset: function() {
- var topOffset = fabric.Text.prototype._getTopOffset.call(this);
- return topOffset - (this.fontSize / this._fontSizeFraction);
- },
-
- /**
- * This method is overwritten to account for different top offset
- * @private
- */
- _renderTextBoxBackground: function(ctx) {
- if (!this.backgroundColor) {
- return;
- }
-
- ctx.save();
- ctx.fillStyle = this.backgroundColor;
-
- ctx.fillRect(
- this._getLeftOffset(),
- this._getTopOffset() + (this.fontSize / this._fontSizeFraction),
- this.width,
- this.height
- );
-
- ctx.restore();
- },
-
- /**
- * Returns object representation of an instance
- * @method toObject
- * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
- * @return {Object} object representation of an instance
- */
- toObject: function(propertiesToInclude) {
- return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
- styles: clone(this.styles)
- });
- }
- });
-
- /**
- * Returns fabric.IText instance from an object representation
- * @static
- * @memberOf fabric.IText
- * @param {Object} object Object to create an instance from
- * @return {fabric.IText} instance of fabric.IText
- */
- fabric.IText.fromObject = function(object) {
- return new fabric.IText(object.text, clone(object));
- };
-
- /**
- * Contains all fabric.IText objects that have been created
- * @static
- * @memberof fabric.IText
- * @type Array
- */
- fabric.IText.instances = [ ];
-
-})();
-
-
-(function() {
-
- var clone = fabric.util.object.clone;
-
- fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
-
- /**
- * Initializes all the interactive behavior of IText
- */
- initBehavior: function() {
- this.initAddedHandler();
- this.initCursorSelectionHandlers();
- this.initDoubleClickSimulation();
- },
-
- /**
- * Initializes "selected" event handler
- */
- initSelectedHandler: function() {
- this.on('selected', function() {
-
- var _this = this;
- setTimeout(function() {
- _this.selected = true;
- }, 100);
- });
- },
-
- /**
- * Initializes "added" event handler
- */
- initAddedHandler: function() {
- this.on('added', function() {
- if (this.canvas && !this.canvas._hasITextHandlers) {
- this.canvas._hasITextHandlers = true;
- this._initCanvasHandlers();
- }
- });
- },
-
- /**
- * @private
- */
- _initCanvasHandlers: function() {
- this.canvas.on('selection:cleared', function() {
- fabric.IText.prototype.exitEditingOnOthers.call();
- });
-
- this.canvas.on('mouse:up', function() {
- fabric.IText.instances.forEach(function(obj) {
- obj.__isMousedown = false;
- });
- });
-
- this.canvas.on('object:selected', function(options) {
- fabric.IText.prototype.exitEditingOnOthers.call(options.target);
- });
- },
-
- /**
- * @private
- */
- _tick: function() {
- if (this._abortCursorAnimation) {
- return;
- }
-
- var _this = this;
-
- this.animate('_currentCursorOpacity', 1, {
-
- duration: this.cursorDuration,
-
- onComplete: function() {
- _this._onTickComplete();
- },
-
- onChange: function() {
- _this.canvas && _this.canvas.renderAll();
- },
-
- abort: function() {
- return _this._abortCursorAnimation;
- }
- });
- },
-
- /**
- * @private
- */
- _onTickComplete: function() {
- if (this._abortCursorAnimation) {
- return;
- }
-
- var _this = this;
- if (this._cursorTimeout1) {
- clearTimeout(this._cursorTimeout1);
- }
- this._cursorTimeout1 = setTimeout(function() {
- _this.animate('_currentCursorOpacity', 0, {
- duration: this.cursorDuration / 2,
- onComplete: function() {
- _this._tick();
- },
- onChange: function() {
- _this.canvas && _this.canvas.renderAll();
- },
- abort: function() {
- return _this._abortCursorAnimation;
- }
- });
- }, 100);
- },
-
- /**
- * Initializes delayed cursor
- */
- initDelayedCursor: function(restart) {
- var _this = this,
- delay = restart ? 0 : this.cursorDelay;
-
- if (restart) {
- this._abortCursorAnimation = true;
- clearTimeout(this._cursorTimeout1);
- this._currentCursorOpacity = 1;
- this.canvas && this.canvas.renderAll();
- }
- if (this._cursorTimeout2) {
- clearTimeout(this._cursorTimeout2);
- }
- this._cursorTimeout2 = setTimeout(function() {
- _this._abortCursorAnimation = false;
- _this._tick();
- }, delay);
- },
-
- /**
- * Aborts cursor animation and clears all timeouts
- */
- abortCursorAnimation: function() {
- this._abortCursorAnimation = true;
-
- clearTimeout(this._cursorTimeout1);
- clearTimeout(this._cursorTimeout2);
-
- this._currentCursorOpacity = 0;
- this.canvas && this.canvas.renderAll();
-
- var _this = this;
- setTimeout(function() {
- _this._abortCursorAnimation = false;
- }, 10);
- },
-
- /**
- * Selects entire text
- */
- selectAll: function() {
- this.selectionStart = 0;
- this.selectionEnd = this.text.length;
- this.fire('selection:changed');
- this.canvas && this.canvas.fire('text:selection:changed', { target: this });
- },
-
- /**
- * Returns selected text
- * @return {String}
- */
- getSelectedText: function() {
- return this.text.slice(this.selectionStart, this.selectionEnd);
- },
-
- /**
- * Find new selection index representing start of current word according to current selection index
- * @param {Number} startFrom Surrent selection index
- * @return {Number} New selection index
- */
- findWordBoundaryLeft: function(startFrom) {
- var offset = 0, index = startFrom - 1;
-
- // remove space before cursor first
- if (this._reSpace.test(this.text.charAt(index))) {
- while (this._reSpace.test(this.text.charAt(index))) {
- offset++;
- index--;
- }
- }
- while (/\S/.test(this.text.charAt(index)) && index > -1) {
- offset++;
- index--;
- }
-
- return startFrom - offset;
- },
-
- /**
- * Find new selection index representing end of current word according to current selection index
- * @param {Number} startFrom Current selection index
- * @return {Number} New selection index
- */
- findWordBoundaryRight: function(startFrom) {
- var offset = 0, index = startFrom;
-
- // remove space after cursor first
- if (this._reSpace.test(this.text.charAt(index))) {
- while (this._reSpace.test(this.text.charAt(index))) {
- offset++;
- index++;
- }
- }
- while (/\S/.test(this.text.charAt(index)) && index < this.text.length) {
- offset++;
- index++;
- }
-
- return startFrom + offset;
- },
-
- /**
- * Find new selection index representing start of current line according to current selection index
- * @param {Number} startFrom Current selection index
- * @return {Number} New selection index
- */
- findLineBoundaryLeft: function(startFrom) {
- var offset = 0, index = startFrom - 1;
-
- while (!/\n/.test(this.text.charAt(index)) && index > -1) {
- offset++;
- index--;
- }
-
- return startFrom - offset;
- },
-
- /**
- * Find new selection index representing end of current line according to current selection index
- * @param {Number} startFrom Current selection index
- * @return {Number} New selection index
- */
- findLineBoundaryRight: function(startFrom) {
- var offset = 0, index = startFrom;
-
- while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) {
- offset++;
- index++;
- }
-
- return startFrom + offset;
- },
-
- /**
- * Returns number of newlines in selected text
- * @return {Number} Number of newlines in selected text
- */
- getNumNewLinesInSelectedText: function() {
- var selectedText = this.getSelectedText(),
- numNewLines = 0;
-
- for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) {
- if (chars[i] === '\n') {
- numNewLines++;
- }
- }
- return numNewLines;
- },
-
- /**
- * Finds index corresponding to beginning or end of a word
- * @param {Number} selectionStart Index of a character
- * @param {Number} direction: 1 or -1
- * @return {Number} Index of the beginning or end of a word
- */
- searchWordBoundary: function(selectionStart, direction) {
- var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart,
- _char = this.text.charAt(index),
- reNonWord = /[ \n\.,;!\?\-]/;
-
- while (!reNonWord.test(_char) && index > 0 && index < this.text.length) {
- index += direction;
- _char = this.text.charAt(index);
- }
- if (reNonWord.test(_char) && _char !== '\n') {
- index += direction === 1 ? 0 : 1;
- }
- return index;
- },
-
- /**
- * Selects a word based on the index
- * @param {Number} selectionStart Index of a character
- */
- selectWord: function(selectionStart) {
- var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */
- newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */
-
- this.setSelectionStart(newSelectionStart);
- this.setSelectionEnd(newSelectionEnd);
- this.initDelayedCursor(true);
- },
-
- /**
- * Selects a line based on the index
- * @param {Number} selectionStart Index of a character
- */
- selectLine: function(selectionStart) {
- var newSelectionStart = this.findLineBoundaryLeft(selectionStart),
- newSelectionEnd = this.findLineBoundaryRight(selectionStart);
-
- this.setSelectionStart(newSelectionStart);
- this.setSelectionEnd(newSelectionEnd);
- this.initDelayedCursor(true);
- },
-
- /**
- * Enters editing state
- * @return {fabric.IText} thisArg
- * @chainable
- */
- enterEditing: function() {
- if (this.isEditing || !this.editable) {
- return;
- }
-
- this.exitEditingOnOthers();
-
- this.isEditing = true;
-
- this.initHiddenTextarea();
- this._updateTextarea();
- this._saveEditingProps();
- this._setEditingProps();
-
- this._tick();
- this.canvas && this.canvas.renderAll();
-
- this.fire('editing:entered');
- this.canvas && this.canvas.fire('text:editing:entered', { target: this });
-
- return this;
- },
-
- exitEditingOnOthers: function() {
- fabric.IText.instances.forEach(function(obj) {
- obj.selected = false;
- if (obj.isEditing) {
- obj.exitEditing();
- }
- }, this);
- },
-
- /**
- * @private
- */
- _setEditingProps: function() {
- this.hoverCursor = 'text';
-
- if (this.canvas) {
- this.canvas.defaultCursor = this.canvas.moveCursor = 'text';
- }
-
- this.borderColor = this.editingBorderColor;
-
- this.hasControls = this.selectable = false;
- this.lockMovementX = this.lockMovementY = true;
- },
-
- /**
- * @private
- */
- _updateTextarea: function() {
- if (!this.hiddenTextarea) {
- return;
- }
-
- this.hiddenTextarea.value = this.text;
- this.hiddenTextarea.selectionStart = this.selectionStart;
- },
-
- /**
- * @private
- */
- _saveEditingProps: function() {
- this._savedProps = {
- hasControls: this.hasControls,
- borderColor: this.borderColor,
- lockMovementX: this.lockMovementX,
- lockMovementY: this.lockMovementY,
- hoverCursor: this.hoverCursor,
- defaultCursor: this.canvas && this.canvas.defaultCursor,
- moveCursor: this.canvas && this.canvas.moveCursor
- };
- },
-
- /**
- * @private
- */
- _restoreEditingProps: function() {
- if (!this._savedProps) {
- return;
- }
-
- this.hoverCursor = this._savedProps.overCursor;
- this.hasControls = this._savedProps.hasControls;
- this.borderColor = this._savedProps.borderColor;
- this.lockMovementX = this._savedProps.lockMovementX;
- this.lockMovementY = this._savedProps.lockMovementY;
-
- if (this.canvas) {
- this.canvas.defaultCursor = this._savedProps.defaultCursor;
- this.canvas.moveCursor = this._savedProps.moveCursor;
- }
- },
-
- /**
- * Exits from editing state
- * @return {fabric.IText} thisArg
- * @chainable
- */
- exitEditing: function() {
-
- this.selected = false;
- this.isEditing = false;
- this.selectable = true;
-
- this.selectionEnd = this.selectionStart;
- this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea);
- this.hiddenTextarea = null;
-
- this.abortCursorAnimation();
- this._restoreEditingProps();
- this._currentCursorOpacity = 0;
-
- this.fire('editing:exited');
- this.canvas && this.canvas.fire('text:editing:exited', { target: this });
-
- return this;
- },
-
- /**
- * @private
- */
- _removeExtraneousStyles: function() {
- var textLines = this.text.split(this._reNewline);
- for (var prop in this.styles) {
- if (!textLines[prop]) {
- delete this.styles[prop];
- }
- }
- },
-
- /**
- * @private
- */
- _removeCharsFromTo: function(start, end) {
-
- var i = end;
- while (i !== start) {
-
- var prevIndex = this.get2DCursorLocation(i).charIndex;
- i--;
-
- var index = this.get2DCursorLocation(i).charIndex,
- isNewline = index > prevIndex;
-
- if (isNewline) {
- this.removeStyleObject(isNewline, i + 1);
- }
- else {
- this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i);
- }
-
- }
-
- this.text = this.text.slice(0, start) +
- this.text.slice(end);
- },
-
- /**
- * Inserts a character where cursor is (replacing selection if one exists)
- * @param {String} _chars Characters to insert
- */
- insertChars: function(_chars) {
- var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n';
-
- this.text = this.text.slice(0, this.selectionStart) +
- _chars +
- this.text.slice(this.selectionEnd);
-
- if (this.selectionStart === this.selectionEnd) {
- this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles);
- }
- // else if (this.selectionEnd - this.selectionStart > 1) {
- // TODO: replace styles properly
- // console.log('replacing MORE than 1 char');
- // }
-
- this.selectionStart += _chars.length;
- this.selectionEnd = this.selectionStart;
-
- if (this.canvas) {
- // TODO: double renderAll gets rid of text box shift happenning sometimes
- // need to find out what exactly causes it and fix it
- this.canvas.renderAll().renderAll();
- }
-
- this.setCoords();
- this.fire('changed');
- this.canvas && this.canvas.fire('text:changed', { target: this });
- },
-
- /**
- * Inserts new style object
- * @param {Number} lineIndex Index of a line
- * @param {Number} charIndex Index of a char
- * @param {Boolean} isEndOfLine True if it's end of line
- */
- insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {
-
- this.shiftLineStyles(lineIndex, +1);
-
- if (!this.styles[lineIndex + 1]) {
- this.styles[lineIndex + 1] = { };
- }
-
- var currentCharStyle = this.styles[lineIndex][charIndex - 1],
- newLineStyles = { };
-
- // if there's nothing after cursor,
- // we clone current char style onto the next (otherwise empty) line
- if (isEndOfLine) {
- newLineStyles[0] = clone(currentCharStyle);
- this.styles[lineIndex + 1] = newLineStyles;
- }
- // otherwise we clone styles of all chars
- // after cursor onto the next line, from the beginning
- else {
- for (var index in this.styles[lineIndex]) {
- if (parseInt(index, 10) >= charIndex) {
- newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index];
- // remove lines from the previous line since they're on a new line now
- delete this.styles[lineIndex][index];
- }
- }
- this.styles[lineIndex + 1] = newLineStyles;
- }
- },
-
- /**
- * Inserts style object for a given line/char index
- * @param {Number} lineIndex Index of a line
- * @param {Number} charIndex Index of a char
- * @param {Object} [style] Style object to insert, if given
- */
- insertCharStyleObject: function(lineIndex, charIndex, style) {
-
- var currentLineStyles = this.styles[lineIndex],
- currentLineStylesCloned = clone(currentLineStyles);
-
- if (charIndex === 0 && !style) {
- charIndex = 1;
- }
-
- // shift all char styles by 1 forward
- // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4
- for (var index in currentLineStylesCloned) {
- var numericIndex = parseInt(index, 10);
- if (numericIndex >= charIndex) {
- currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex];
- //delete currentLineStyles[index];
- }
- }
-
- this.styles[lineIndex][charIndex] =
- style || clone(currentLineStyles[charIndex - 1]);
- },
-
- /**
- * Inserts style object(s)
- * @param {String} _chars Characters at the location where style is inserted
- * @param {Boolean} isEndOfLine True if it's end of line
- * @param {Array} [styles] Styles to insert
- */
- insertStyleObjects: function(_chars, isEndOfLine, styles) {
-
- // short-circuit
- if (this.isEmptyStyles()) {
- return;
- }
-
- var cursorLocation = this.get2DCursorLocation(),
- lineIndex = cursorLocation.lineIndex,
- charIndex = cursorLocation.charIndex;
-
- if (!this.styles[lineIndex]) {
- this.styles[lineIndex] = { };
- }
-
- if (_chars === '\n') {
- this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);
- }
- else {
- if (styles) {
- this._insertStyles(styles);
- }
- else {
- // TODO: support multiple style insertion if _chars.length > 1
- this.insertCharStyleObject(lineIndex, charIndex);
- }
- }
- },
-
- /**
- * @private
- */
- _insertStyles: function(styles) {
- for (var i = 0, len = styles.length; i < len; i++) {
-
- var cursorLocation = this.get2DCursorLocation(this.selectionStart + i),
- lineIndex = cursorLocation.lineIndex,
- charIndex = cursorLocation.charIndex;
-
- this.insertCharStyleObject(lineIndex, charIndex, styles[i]);
- }
- },
-
- /**
- * Shifts line styles up or down
- * @param {Number} lineIndex Index of a line
- * @param {Number} offset Can be -1 or +1
- */
- shiftLineStyles: function(lineIndex, offset) {
- // shift all line styles by 1 upward
- var clonedStyles = clone(this.styles);
- for (var line in this.styles) {
- var numericLine = parseInt(line, 10);
- if (numericLine > lineIndex) {
- this.styles[numericLine + offset] = clonedStyles[numericLine];
- }
- }
- },
-
- /**
- * Removes style object
- * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
- * @param {Number} [index] Optional index. When not given, current selectionStart is used.
- */
- removeStyleObject: function(isBeginningOfLine, index) {
-
- var cursorLocation = this.get2DCursorLocation(index),
- lineIndex = cursorLocation.lineIndex,
- charIndex = cursorLocation.charIndex;
-
- if (isBeginningOfLine) {
-
- var textLines = this.text.split(this._reNewline),
- textOnPreviousLine = textLines[lineIndex - 1],
- newCharIndexOnPrevLine = textOnPreviousLine
- ? textOnPreviousLine.length
- : 0;
-
- if (!this.styles[lineIndex - 1]) {
- this.styles[lineIndex - 1] = { };
- }
-
- for (charIndex in this.styles[lineIndex]) {
- this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine]
- = this.styles[lineIndex][charIndex];
- }
-
- this.shiftLineStyles(lineIndex, -1);
- }
- else {
- var currentLineStyles = this.styles[lineIndex];
-
- if (currentLineStyles) {
- var offset = this.selectionStart === this.selectionEnd ? -1 : 0;
- delete currentLineStyles[charIndex + offset];
- // console.log('deleting', lineIndex, charIndex + offset);
- }
-
- var currentLineStylesCloned = clone(currentLineStyles);
-
- // shift all styles by 1 backwards
- for (var i in currentLineStylesCloned) {
- var numericIndex = parseInt(i, 10);
- if (numericIndex >= charIndex && numericIndex !== 0) {
- currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex];
- delete currentLineStyles[numericIndex];
- }
- }
- }
- },
-
- /**
- * Inserts new line
- */
- insertNewline: function() {
- this.insertChars('\n');
- }
- });
-})();
-
-
-fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
- /**
- * Initializes "dbclick" event handler
- */
- initDoubleClickSimulation: function() {
-
- // for double click
- this.__lastClickTime = +new Date();
-
- // for triple click
- this.__lastLastClickTime = +new Date();
-
- this.__lastPointer = { };
-
- this.on('mousedown', this.onMouseDown.bind(this));
- },
-
- onMouseDown: function(options) {
-
- this.__newClickTime = +new Date();
- var newPointer = this.canvas.getPointer(options.e);
-
- if (this.isTripleClick(newPointer)) {
- this.fire('tripleclick', options);
- this._stopEvent(options.e);
- }
- else if (this.isDoubleClick(newPointer)) {
- this.fire('dblclick', options);
- this._stopEvent(options.e);
- }
-
- this.__lastLastClickTime = this.__lastClickTime;
- this.__lastClickTime = this.__newClickTime;
- this.__lastPointer = newPointer;
- this.__lastIsEditing = this.isEditing;
- this.__lastSelected = this.selected;
- },
-
- isDoubleClick: function(newPointer) {
- return this.__newClickTime - this.__lastClickTime < 500 &&
- this.__lastPointer.x === newPointer.x &&
- this.__lastPointer.y === newPointer.y && this.__lastIsEditing;
- },
-
- isTripleClick: function(newPointer) {
- return this.__newClickTime - this.__lastClickTime < 500 &&
- this.__lastClickTime - this.__lastLastClickTime < 500 &&
- this.__lastPointer.x === newPointer.x &&
- this.__lastPointer.y === newPointer.y;
- },
-
- /**
- * @private
- */
- _stopEvent: function(e) {
- e.preventDefault && e.preventDefault();
- e.stopPropagation && e.stopPropagation();
- },
-
- /**
- * Initializes event handlers related to cursor or selection
- */
- initCursorSelectionHandlers: function() {
- this.initSelectedHandler();
- this.initMousedownHandler();
- this.initMousemoveHandler();
- this.initMouseupHandler();
- this.initClicks();
- },
-
- /**
- * Initializes double and triple click event handlers
- */
- initClicks: function() {
- this.on('dblclick', function(options) {
- this.selectWord(this.getSelectionStartFromPointer(options.e));
- });
- this.on('tripleclick', function(options) {
- this.selectLine(this.getSelectionStartFromPointer(options.e));
- });
- },
-
- /**
- * Initializes "mousedown" event handler
- */
- initMousedownHandler: function() {
- this.on('mousedown', function(options) {
-
- var pointer = this.canvas.getPointer(options.e);
-
- this.__mousedownX = pointer.x;
- this.__mousedownY = pointer.y;
- this.__isMousedown = true;
-
- if (this.hiddenTextarea && this.canvas) {
- this.canvas.wrapperEl.appendChild(this.hiddenTextarea);
- }
-
- if (this.selected) {
- this.setCursorByClick(options.e);
- }
-
- if (this.isEditing) {
- this.__selectionStartOnMouseDown = this.selectionStart;
- this.initDelayedCursor(true);
- }
- });
- },
-
- /**
- * Initializes "mousemove" event handler
- */
- initMousemoveHandler: function() {
- this.on('mousemove', function(options) {
- if (!this.__isMousedown || !this.isEditing) {
- return;
- }
-
- var newSelectionStart = this.getSelectionStartFromPointer(options.e);
-
- if (newSelectionStart >= this.__selectionStartOnMouseDown) {
- this.setSelectionStart(this.__selectionStartOnMouseDown);
- this.setSelectionEnd(newSelectionStart);
- }
- else {
- this.setSelectionStart(newSelectionStart);
- this.setSelectionEnd(this.__selectionStartOnMouseDown);
- }
- });
- },
-
- /**
- * @private
- */
- _isObjectMoved: function(e) {
- var pointer = this.canvas.getPointer(e);
-
- return this.__mousedownX !== pointer.x ||
- this.__mousedownY !== pointer.y;
- },
-
- /**
- * Initializes "mouseup" event handler
- */
- initMouseupHandler: function() {
- this.on('mouseup', function(options) {
- this.__isMousedown = false;
- if (this._isObjectMoved(options.e)) {
- return;
- }
-
- if (this.__lastSelected) {
- this.enterEditing();
- this.initDelayedCursor(true);
- }
- this.selected = true;
- });
- },
-
- /**
- * Changes cursor location in a text depending on passed pointer (x/y) object
- * @param {Event} e Event object
- */
- setCursorByClick: function(e) {
- var newSelectionStart = this.getSelectionStartFromPointer(e);
-
- if (e.shiftKey) {
- if (newSelectionStart < this.selectionStart) {
- this.setSelectionEnd(this.selectionStart);
- this.setSelectionStart(newSelectionStart);
- }
- else {
- this.setSelectionEnd(newSelectionStart);
- }
- }
- else {
- this.setSelectionStart(newSelectionStart);
- this.setSelectionEnd(newSelectionStart);
- }
- },
-
- /**
- * @private
- * @param {Event} e Event object
- * @return {Object} Coordinates of a pointer (x, y)
- */
- _getLocalRotatedPointer: function(e) {
- var pointer = this.canvas.getPointer(e),
-
- pClicked = new fabric.Point(pointer.x, pointer.y),
- pLeftTop = new fabric.Point(this.left, this.top),
-
- rotated = fabric.util.rotatePoint(
- pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle));
-
- return this.getLocalPointer(e, rotated);
- },
-
- /**
- * Returns index of a character corresponding to where an object was clicked
- * @param {Event} e Event object
- * @return {Number} Index of a character
- */
- getSelectionStartFromPointer: function(e) {
- var mouseOffset = this._getLocalRotatedPointer(e),
- textLines = this.text.split(this._reNewline),
- prevWidth = 0,
- width = 0,
- height = 0,
- charIndex = 0,
- newSelectionStart;
-
- for (var i = 0, len = textLines.length; i < len; i++) {
-
- height += this._getHeightOfLine(this.ctx, i) * this.scaleY;
-
- var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines),
- lineLeftOffset = this._getLineLeftOffset(widthOfLine);
-
- width = lineLeftOffset * this.scaleX;
-
- if (this.flipX) {
- // when oject is horizontally flipped we reverse chars
- textLines[i] = textLines[i].split('').reverse().join('');
- }
-
- for (var j = 0, jlen = textLines[i].length; j < jlen; j++) {
-
- var _char = textLines[i][j];
- prevWidth = width;
-
- width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) *
- this.scaleX;
-
- if (height <= mouseOffset.y || width <= mouseOffset.x) {
- charIndex++;
- continue;
- }
-
- return this._getNewSelectionStartFromOffset(
- mouseOffset, prevWidth, width, charIndex + i, jlen);
- }
-
- if (mouseOffset.y < height) {
- return this._getNewSelectionStartFromOffset(
- mouseOffset, prevWidth, width, charIndex + i, jlen);
- }
- }
-
- // clicked somewhere after all chars, so set at the end
- if (typeof newSelectionStart === 'undefined') {
- return this.text.length;
- }
- },
-
- /**
- * @private
- */
- _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) {
-
- var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth,
- distanceBtwNextCharAndCursor = width - mouseOffset.x,
- offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1,
- newSelectionStart = index + offset;
-
- // if object is horizontally flipped, mirror cursor location from the end
- if (this.flipX) {
- newSelectionStart = jlen - newSelectionStart;
- }
-
- if (newSelectionStart > this.text.length) {
- newSelectionStart = this.text.length;
- }
-
- return newSelectionStart;
- }
-});
-
-
-fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
-
- /**
- * Initializes hidden textarea (needed to bring up keyboard in iOS)
- */
- initHiddenTextarea: function() {
- this.hiddenTextarea = fabric.document.createElement('textarea');
-
- this.hiddenTextarea.setAttribute('autocapitalize', 'off');
- this.hiddenTextarea.style.cssText = 'position: absolute; top: 0; left: -9999px';
-
- fabric.document.body.appendChild(this.hiddenTextarea);
-
- fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this));
- fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this));
- fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this));
- fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this));
-
- if (!this._clickHandlerInitialized && this.canvas) {
- fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this));
- this._clickHandlerInitialized = true;
- }
- },
-
- /**
- * @private
- */
- _keysMap: {
- 8: 'removeChars',
- 13: 'insertNewline',
- 37: 'moveCursorLeft',
- 38: 'moveCursorUp',
- 39: 'moveCursorRight',
- 40: 'moveCursorDown',
- 46: 'forwardDelete'
- },
-
- /**
- * @private
- */
- _ctrlKeysMap: {
- 65: 'selectAll',
- 88: 'cut'
- },
-
- onClick: function() {
- // No need to trigger click event here, focus is enough to have the keyboard appear on Android
- this.hiddenTextarea && this.hiddenTextarea.focus();
- },
-
- /**
- * Handles keyup event
- * @param {Event} e Event object
- */
- onKeyDown: function(e) {
- if (!this.isEditing) {
- return;
- }
-
- if (e.keyCode in this._keysMap) {
- this[this._keysMap[e.keyCode]](e);
- }
- else if ((e.keyCode in this._ctrlKeysMap) && (e.ctrlKey || e.metaKey)) {
- this[this._ctrlKeysMap[e.keyCode]](e);
- }
- else {
- return;
- }
-
- e.stopImmediatePropagation();
- e.preventDefault();
-
- this.canvas && this.canvas.renderAll();
- },
-
- /**
- * Forward delete
- */
- forwardDelete: function(e) {
- if (this.selectionStart === this.selectionEnd) {
- this.moveCursorRight(e);
- }
- this.removeChars(e);
- },
-
- /**
- * Copies selected text
- * @param {Event} e Event object
- */
- copy: function(e) {
- var selectedText = this.getSelectedText(),
- clipboardData = this._getClipboardData(e);
-
- // Check for backward compatibility with old browsers
- if (clipboardData) {
- clipboardData.setData('text', selectedText);
- }
-
- this.copiedText = selectedText;
- this.copiedStyles = this.getSelectionStyles(
- this.selectionStart,
- this.selectionEnd);
- },
-
- /**
- * Pastes text
- * @param {Event} e Event object
- */
- paste: function(e) {
- var copiedText = null,
- clipboardData = this._getClipboardData(e);
-
- // Check for backward compatibility with old browsers
- if (clipboardData) {
- copiedText = clipboardData.getData('text');
- }
- else {
- copiedText = this.copiedText;
- }
-
- if (copiedText) {
- this.insertChars(copiedText);
- }
- },
-
- /**
- * Cuts text
- * @param {Event} e Event object
- */
- cut: function(e) {
- if (this.selectionStart === this.selectionEnd) {
- return;
- }
-
- this.copy();
- this.removeChars(e);
- },
-
- /**
- * @private
- * @param {Event} e Event object
- * @return {Object} Clipboard data object
- */
- _getClipboardData: function(e) {
- return e && (e.clipboardData || fabric.window.clipboardData);
- },
-
- /**
- * Handles keypress event
- * @param {Event} e Event object
- */
- onKeyPress: function(e) {
- if (!this.isEditing || e.metaKey || e.ctrlKey) {
- return;
- }
-
- this.insertChars(String.fromCharCode(e.which));
-
- e.stopPropagation();
- },
-
- /**
- * Gets start offset of a selection
- * @param {Event} e Event object
- * @param {Boolean} isRight
- * @return {Number}
- */
- getDownCursorOffset: function(e, isRight) {
- var selectionProp = isRight ? this.selectionEnd : this.selectionStart,
- textLines = this.text.split(this._reNewline),
- _char,
- lineLeftOffset,
-
- textBeforeCursor = this.text.slice(0, selectionProp),
- textAfterCursor = this.text.slice(selectionProp),
-
- textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1),
- textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1],
- textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || { })[1] || '',
-
- cursorLocation = this.get2DCursorLocation(selectionProp);
-
- // if on last line, down cursor goes to end of line
- if (cursorLocation.lineIndex === textLines.length - 1 || e.metaKey) {
-
- // move to the end of a text
- return this.text.length - selectionProp;
- }
-
- var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines);
- lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor);
-
- var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
- lineIndex = cursorLocation.lineIndex;
-
- for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {
- _char = textOnSameLineBeforeCursor[i];
- widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);
- }
-
- var indexOnNextLine = this._getIndexOnNextLine(
- cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines);
-
- return textOnSameLineAfterCursor.length + 1 + indexOnNextLine;
- },
-
- /**
- * @private
- */
- _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) {
- var lineIndex = cursorLocation.lineIndex + 1,
- widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines),
- lineLeftOffset = this._getLineLeftOffset(widthOfNextLine),
- widthOfCharsOnNextLine = lineLeftOffset,
- indexOnNextLine = 0,
- foundMatch;
-
- for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) {
-
- var _char = textOnNextLine[j],
- widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);
-
- widthOfCharsOnNextLine += widthOfChar;
-
- if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) {
-
- foundMatch = true;
-
- var leftEdge = widthOfCharsOnNextLine - widthOfChar,
- rightEdge = widthOfCharsOnNextLine,
- offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor),
- offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor);
-
- indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j;
-
- break;
- }
- }
-
- // reached end
- if (!foundMatch) {
- indexOnNextLine = textOnNextLine.length;
- }
-
- return indexOnNextLine;
- },
-
- /**
- * Moves cursor down
- * @param {Event} e Event object
- */
- moveCursorDown: function(e) {
- this.abortCursorAnimation();
- this._currentCursorOpacity = 1;
-
- var offset = this.getDownCursorOffset(e, this._selectionDirection === 'right');
-
- if (e.shiftKey) {
- this.moveCursorDownWithShift(offset);
- }
- else {
- this.moveCursorDownWithoutShift(offset);
- }
-
- this.initDelayedCursor();
- },
-
- /**
- * Moves cursor down without keeping selection
- * @param {Number} offset
- */
- moveCursorDownWithoutShift: function(offset) {
- this._selectionDirection = 'right';
- this.selectionStart += offset;
-
- if (this.selectionStart > this.text.length) {
- this.selectionStart = this.text.length;
- }
- this.selectionEnd = this.selectionStart;
- },
-
- /**
- * Moves cursor down while keeping selection
- * @param {Number} offset
- */
- moveCursorDownWithShift: function(offset) {
- if (this._selectionDirection === 'left' && (this.selectionStart !== this.selectionEnd)) {
- this.selectionStart += offset;
- this._selectionDirection = 'left';
- return;
- }
- else {
- this._selectionDirection = 'right';
- this.selectionEnd += offset;
-
- if (this.selectionEnd > this.text.length) {
- this.selectionEnd = this.text.length;
- }
- }
- },
-
- /**
- * @param {Event} e Event object
- * @param {Boolean} isRight
- * @return {Number}
- */
- getUpCursorOffset: function(e, isRight) {
- var selectionProp = isRight ? this.selectionEnd : this.selectionStart,
- cursorLocation = this.get2DCursorLocation(selectionProp);
-
- // if on first line, up cursor goes to start of line
- if (cursorLocation.lineIndex === 0 || e.metaKey) {
- return selectionProp;
- }
-
- var textBeforeCursor = this.text.slice(0, selectionProp),
- textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1),
- textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '',
- textLines = this.text.split(this._reNewline),
- _char,
- widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines),
- lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor),
- widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
- lineIndex = cursorLocation.lineIndex;
-
- for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {
- _char = textOnSameLineBeforeCursor[i];
- widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);
- }
-
- var indexOnPrevLine = this._getIndexOnPrevLine(
- cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines);
-
- return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length;
- },
-
- /**
- * @private
- */
- _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) {
-
- var lineIndex = cursorLocation.lineIndex - 1,
- widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines),
- lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine),
- widthOfCharsOnPreviousLine = lineLeftOffset,
- indexOnPrevLine = 0,
- foundMatch;
-
- for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) {
-
- var _char = textOnPreviousLine[j],
- widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);
-
- widthOfCharsOnPreviousLine += widthOfChar;
-
- if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) {
-
- foundMatch = true;
-
- var leftEdge = widthOfCharsOnPreviousLine - widthOfChar,
- rightEdge = widthOfCharsOnPreviousLine,
- offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor),
- offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor);
-
- indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1);
-
- break;
- }
- }
-
- // reached end
- if (!foundMatch) {
- indexOnPrevLine = textOnPreviousLine.length - 1;
- }
-
- return indexOnPrevLine;
- },
-
- /**
- * Moves cursor up
- * @param {Event} e Event object
- */
- moveCursorUp: function(e) {
-
- this.abortCursorAnimation();
- this._currentCursorOpacity = 1;
-
- var offset = this.getUpCursorOffset(e, this._selectionDirection === 'right');
-
- if (e.shiftKey) {
- this.moveCursorUpWithShift(offset);
- }
- else {
- this.moveCursorUpWithoutShift(offset);
- }
-
- this.initDelayedCursor();
- },
-
- /**
- * Moves cursor up with shift
- * @param {Number} offset
- */
- moveCursorUpWithShift: function(offset) {
-
- if (this.selectionStart === this.selectionEnd) {
- this.selectionStart -= offset;
- }
- else {
- if (this._selectionDirection === 'right') {
- this.selectionEnd -= offset;
- this._selectionDirection = 'right';
- return;
- }
- else {
- this.selectionStart -= offset;
- }
- }
-
- if (this.selectionStart < 0) {
- this.selectionStart = 0;
- }
-
- this._selectionDirection = 'left';
- },
-
- /**
- * Moves cursor up without shift
- * @param {Number} offset
- */
- moveCursorUpWithoutShift: function(offset) {
- if (this.selectionStart === this.selectionEnd) {
- this.selectionStart -= offset;
- }
- if (this.selectionStart < 0) {
- this.selectionStart = 0;
- }
- this.selectionEnd = this.selectionStart;
-
- this._selectionDirection = 'left';
- },
-
- /**
- * Moves cursor left
- * @param {Event} e Event object
- */
- moveCursorLeft: function(e) {
- if (this.selectionStart === 0 && this.selectionEnd === 0) {
- return;
- }
-
- this.abortCursorAnimation();
- this._currentCursorOpacity = 1;
-
- if (e.shiftKey) {
- this.moveCursorLeftWithShift(e);
- }
- else {
- this.moveCursorLeftWithoutShift(e);
- }
-
- this.initDelayedCursor();
- },
-
- /**
- * @private
- */
- _move: function(e, prop, direction) {
- if (e.altKey) {
- this[prop] = this['findWordBoundary' + direction](this[prop]);
- }
- else if (e.metaKey) {
- this[prop] = this['findLineBoundary' + direction](this[prop]);
- }
- else {
- this[prop] += (direction === 'Left' ? -1 : 1);
- }
- },
-
- /**
- * @private
- */
- _moveLeft: function(e, prop) {
- this._move(e, prop, 'Left');
- },
-
- /**
- * @private
- */
- _moveRight: function(e, prop) {
- this._move(e, prop, 'Right');
- },
-
- /**
- * Moves cursor left without keeping selection
- * @param {Event} e
- */
- moveCursorLeftWithoutShift: function(e) {
- this._selectionDirection = 'left';
-
- // only move cursor when there is no selection,
- // otherwise we discard it, and leave cursor on same place
- if (this.selectionEnd === this.selectionStart) {
- this._moveLeft(e, 'selectionStart');
- }
- this.selectionEnd = this.selectionStart;
- },
-
- /**
- * Moves cursor left while keeping selection
- * @param {Event} e
- */
- moveCursorLeftWithShift: function(e) {
- if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) {
- this._moveLeft(e, 'selectionEnd');
- }
- else {
- this._selectionDirection = 'left';
- this._moveLeft(e, 'selectionStart');
-
- // increase selection by one if it's a newline
- if (this.text.charAt(this.selectionStart) === '\n') {
- this.selectionStart--;
- }
- if (this.selectionStart < 0) {
- this.selectionStart = 0;
- }
- }
- },
-
- /**
- * Moves cursor right
- * @param {Event} e Event object
- */
- moveCursorRight: function(e) {
- if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
- return;
- }
-
- this.abortCursorAnimation();
- this._currentCursorOpacity = 1;
-
- if (e.shiftKey) {
- this.moveCursorRightWithShift(e);
- }
- else {
- this.moveCursorRightWithoutShift(e);
- }
-
- this.initDelayedCursor();
- },
-
- /**
- * Moves cursor right while keeping selection
- * @param {Event} e
- */
- moveCursorRightWithShift: function(e) {
- if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) {
- this._moveRight(e, 'selectionStart');
- }
- else {
- this._selectionDirection = 'right';
- this._moveRight(e, 'selectionEnd');
-
- // increase selection by one if it's a newline
- if (this.text.charAt(this.selectionEnd - 1) === '\n') {
- this.selectionEnd++;
- }
- if (this.selectionEnd > this.text.length) {
- this.selectionEnd = this.text.length;
- }
- }
- },
-
- /**
- * Moves cursor right without keeping selection
- * @param {Event} e Event object
- */
- moveCursorRightWithoutShift: function(e) {
- this._selectionDirection = 'right';
-
- if (this.selectionStart === this.selectionEnd) {
- this._moveRight(e, 'selectionStart');
- this.selectionEnd = this.selectionStart;
- }
- else {
- this.selectionEnd += this.getNumNewLinesInSelectedText();
- if (this.selectionEnd > this.text.length) {
- this.selectionEnd = this.text.length;
- }
- this.selectionStart = this.selectionEnd;
- }
- },
-
- /**
- * Inserts a character where cursor is (replacing selection if one exists)
- * @param {Event} e Event object
- */
- removeChars: function(e) {
- if (this.selectionStart === this.selectionEnd) {
- this._removeCharsNearCursor(e);
- }
- else {
- this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
- }
-
- this.selectionEnd = this.selectionStart;
-
- this._removeExtraneousStyles();
-
- if (this.canvas) {
- // TODO: double renderAll gets rid of text box shift happenning sometimes
- // need to find out what exactly causes it and fix it
- this.canvas.renderAll().renderAll();
- }
-
- this.setCoords();
- this.fire('changed');
- this.canvas && this.canvas.fire('text:changed', { target: this });
- },
-
- /**
- * @private
- * @param {Event} e Event object
- */
- _removeCharsNearCursor: function(e) {
- if (this.selectionStart !== 0) {
-
- if (e.metaKey) {
- // remove all till the start of current line
- var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);
-
- this._removeCharsFromTo(leftLineBoundary, this.selectionStart);
- this.selectionStart = leftLineBoundary;
- }
- else if (e.altKey) {
- // remove all till the start of current word
- var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart);
-
- this._removeCharsFromTo(leftWordBoundary, this.selectionStart);
- this.selectionStart = leftWordBoundary;
- }
- else {
- var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n';
- this.removeStyleObject(isBeginningOfLine);
-
- this.selectionStart--;
- this.text = this.text.slice(0, this.selectionStart) +
- this.text.slice(this.selectionStart + 1);
- }
- }
- }
-});
-
-
-/* _TO_SVG_START_ */
-fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
-
- /**
- * @private
- */
- _setSVGTextLineText: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) {
- if (!this.styles[lineIndex]) {
- this.callSuper('_setSVGTextLineText',
- textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier);
- }
- else {
- this._setSVGTextLineChars(
- textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects);
- }
- },
-
- /**
- * @private
- */
- _setSVGTextLineChars: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) {
-
- var yProp = lineIndex === 0 || this.useNative ? 'y' : 'dy',
- chars = textLine.split(''),
- charOffset = 0,
- lineLeftOffset = this._getSVGLineLeftOffset(lineIndex),
- lineTopOffset = this._getSVGLineTopOffset(lineIndex),
- heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);
-
- for (var i = 0, len = chars.length; i < len; i++) {
- var styleDecl = this.styles[lineIndex][i] || { };
-
- textSpans.push(
- this._createTextCharSpan(
- chars[i], styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset));
-
- var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);
-
- if (styleDecl.textBackgroundColor) {
- textBgRects.push(
- this._createTextCharBg(
- styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset));
- }
-
- charOffset += charWidth;
- }
- },
-
- /**
- * @private
- */
- _getSVGLineLeftOffset: function(lineIndex) {
- return (this._boundaries && this._boundaries[lineIndex])
- ? fabric.util.toFixed(this._boundaries[lineIndex].left, 2)
- : 0;
- },
-
- /**
- * @private
- */
- _getSVGLineTopOffset: function(lineIndex) {
- var lineTopOffset = 0;
- for (var j = 0; j <= lineIndex; j++) {
- lineTopOffset += this._getHeightOfLine(this.ctx, j);
- }
- return lineTopOffset - this.height / 2;
- },
-
- /**
- * @private
- */
- _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) {
- return [
- //jscs:disable validateIndentation
- ''
- //jscs:enable validateIndentation
- ].join('');
- },
-
- /**
- * @private
- */
- _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset) {
-
- var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({
- visible: true,
- fill: this.fill,
- stroke: this.stroke,
- type: 'text'
- }, styleDecl));
-
- return [
- //jscs:disable validateIndentation
- '',
-
- fabric.util.string.escapeXml(_char),
- ''
- //jscs:enable validateIndentation
- ].join('');
- }
-});
-/* _TO_SVG_END_ */
-
-
-(function() {
-
- if (typeof document !== 'undefined' && typeof window !== 'undefined') {
- return;
- }
-
- var DOMParser = require('xmldom').DOMParser,
- URL = require('url'),
- HTTP = require('http'),
- HTTPS = require('https'),
-
- Canvas = require('canvas'),
- Image = require('canvas').Image;
-
- /** @private */
- function request(url, encoding, callback) {
- var oURL = URL.parse(url);
-
- // detect if http or https is used
- if ( !oURL.port ) {
- oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80;
- }
-
- // assign request handler based on protocol
- var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP,
- req = reqHandler.request({
- hostname: oURL.hostname,
- port: oURL.port,
- path: oURL.path,
- method: 'GET'
- }, function(response) {
- var body = '';
- if (encoding) {
- response.setEncoding(encoding);
- }
- response.on('end', function () {
- callback(body);
- });
- response.on('data', function (chunk) {
- if (response.statusCode === 200) {
- body += chunk;
- }
- });
- });
-
- req.on('error', function(err) {
- if (err.errno === process.ECONNREFUSED) {
- fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port);
- }
- else {
- fabric.log(err.message);
- }
- });
-
- req.end();
- }
-
- /** @private */
- function requestFs(path, callback){
- var fs = require('fs');
- fs.readFile(path, function (err, data) {
- if (err) {
- fabric.log(err);
- throw err;
- }
- else {
- callback(data);
- }
- });
- }
-
- fabric.util.loadImage = function(url, callback, context) {
- function createImageAndCallBack(data) {
- img.src = new Buffer(data, 'binary');
- // preserving original url, which seems to be lost in node-canvas
- img._src = url;
- callback && callback.call(context, img);
- }
- var img = new Image();
- if (url && (url instanceof Buffer || url.indexOf('data') === 0)) {
- img.src = img._src = url;
- callback && callback.call(context, img);
- }
- else if (url && url.indexOf('http') !== 0) {
- requestFs(url, createImageAndCallBack);
- }
- else if (url) {
- request(url, 'binary', createImageAndCallBack);
- }
- else {
- callback && callback.call(context, url);
- }
- };
-
- fabric.loadSVGFromURL = function(url, callback, reviver) {
- url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();
- if (url.indexOf('http') !== 0) {
- requestFs(url, function(body) {
- fabric.loadSVGFromString(body.toString(), callback, reviver);
- });
- }
- else {
- request(url, '', function(body) {
- fabric.loadSVGFromString(body, callback, reviver);
- });
- }
- };
-
- fabric.loadSVGFromString = function(string, callback, reviver) {
- var doc = new DOMParser().parseFromString(string);
- fabric.parseSVGDocument(doc.documentElement, function(results, options) {
- callback && callback(results, options);
- }, reviver);
- };
-
- fabric.util.getScript = function(url, callback) {
- request(url, '', function(body) {
- eval(body);
- callback && callback();
- });
- };
-
- fabric.Image.fromObject = function(object, callback) {
- fabric.util.loadImage(object.src, function(img) {
- var oImg = new fabric.Image(img);
-
- oImg._initConfig(object);
- oImg._initFilters(object, function(filters) {
- oImg.filters = filters || [ ];
- callback && callback(oImg);
- });
- });
- };
-
- /**
- * Only available when running fabric on node.js
- * @param {Number} width Canvas width
- * @param {Number} height Canvas height
- * @param {Object} [options] Options to pass to FabricCanvas.
- * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas.
- * @return {Object} wrapped canvas instance
- */
- fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) {
- nodeCanvasOptions = nodeCanvasOptions || options;
-
- var canvasEl = fabric.document.createElement('canvas'),
- nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions);
-
- // jsdom doesn't create style on canvas element, so here be temp. workaround
- canvasEl.style = { };
-
- canvasEl.width = nodeCanvas.width;
- canvasEl.height = nodeCanvas.height;
-
- var FabricCanvas = fabric.Canvas || fabric.StaticCanvas,
- fabricCanvas = new FabricCanvas(canvasEl, options);
-
- fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
- fabricCanvas.nodeCanvas = nodeCanvas;
- fabricCanvas.Font = Canvas.Font;
-
- return fabricCanvas;
- };
-
- /** @ignore */
- fabric.StaticCanvas.prototype.createPNGStream = function() {
- return this.nodeCanvas.createPNGStream();
- };
-
- fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {
- return this.nodeCanvas.createJPEGStream(opts);
- };
-
- var origSetWidth = fabric.StaticCanvas.prototype.setWidth;
- fabric.StaticCanvas.prototype.setWidth = function(width, options) {
- origSetWidth.call(this, width, options);
- this.nodeCanvas.width = width;
- return this;
- };
- if (fabric.Canvas) {
- fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth;
- }
-
- var origSetHeight = fabric.StaticCanvas.prototype.setHeight;
- fabric.StaticCanvas.prototype.setHeight = function(height, options) {
- origSetHeight.call(this, height, options);
- this.nodeCanvas.height = height;
- return this;
- };
- if (fabric.Canvas) {
- fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;
- }
-
-})();
-
-
})(window, document, html2canvas);
\ No newline at end of file
diff --git a/dist/html2canvas.svg.min.js b/dist/html2canvas.svg.min.js
index c760de8..eb01782 100644
--- a/dist/html2canvas.svg.min.js
+++ b/dist/html2canvas.svg.min.js
@@ -4,10 +4,9 @@
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,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;Array.prototype.indexOf||(Array.prototype.indexOf=function(a){if(void 0===this||null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>0&&(d=Number(arguments[1]),d!==d?d=0:0!==d&&d!==Number.POSITIVE_INFINITY&&d!==Number.NEGATIVE_INFINITY&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),Array.prototype.forEach||(Array.prototype.forEach=function(a,b){for(var c=0,d=this.length>>>0;d>c;c++)c in this&&a.call(b,this[c],c,this)}),Array.prototype.map||(Array.prototype.map=function(a,b){for(var c=[],d=0,e=this.length>>>0;e>d;d++)d in this&&(c[d]=a.call(b,this[d],d,this));return c}),Array.prototype.every||(Array.prototype.every=function(a,b){for(var c=0,d=this.length>>>0;d>c;c++)if(c in this&&!a.call(b,this[c],c,this))return!1;return!0}),Array.prototype.some||(Array.prototype.some=function(a,b){for(var c=0,d=this.length>>>0;d>c;c++)if(c in this&&a.call(b,this[c],c,this))return!0;return!1}),Array.prototype.filter||(Array.prototype.filter=function(a,b){for(var c,d=[],e=0,f=this.length>>>0;f>e;e++)e in this&&(c=this[e],a.call(b,c,e,this)&&d.push(c));return d}),Array.prototype.reduce||(Array.prototype.reduce=function(a){var b,c=this.length>>>0,d=0;if(arguments.length>1)b=arguments[1];else for(;;){if(d in this){b=this[d++];break}if(++d>=c)throw new TypeError}for(;c>d;d++)d in this&&(b=a.call(null,b,this[d],d,this));return b}),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,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:a,capitalize:b,escapeXml:c}}(),function(){var a=Array.prototype.slice,b=Function.prototype.apply,c=function(){};Function.prototype.bind||(Function.prototype.bind=function(d){var e,f=this,g=a.call(arguments,1);return e=g.length?function(){return b.call(f,this instanceof c?this:d,g.concat(a.call(arguments)))}:function(){return b.call(f,this instanceof c?this:d,arguments)},c.prototype=this.prototype,e.prototype=new c,e})}(),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(){function a(a){b(function(c){a||(a={});var d,e=c||+new Date,f=a.duration||500,g=e+f,h=a.onChange||function(){},i=a.abort||function(){return!1},j=a.easing||function(a,b,c,d){return-c*Math.cos(a/d*(Math.PI/2))+c+b},k="startValue"in a?a.startValue:0,l="endValue"in a?a.endValue:100,m=a.byValue||l-k;a.onStart&&a.onStart(),function n(c){d=c||+new Date;var l=d>g?f:d-e;return i()?void(a.onComplete&&a.onComplete()):(h(j(l,k,m,f)),d>g?void(a.onComplete&&a.onComplete()):void b(n))}(e)})}function b(){return c.apply(fabric.window,arguments)}var c=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(a){fabric.window.setTimeout(a,1e3/60)};fabric.util.animate=a,fabric.util.requestAnimFrame=b}(),function(){function a(a,b,c,d){return aa?c/2*a*a*a+b:c/2*((a-=2)*a*a+2)+b}function e(a,b,c,d){return c*(a/=d)*a*a*a+b}function f(a,b,c,d){return-c*((a=a/d-1)*a*a*a-1)+b}function g(a,b,c,d){return a/=d/2,1>a?c/2*a*a*a*a+b:-c/2*((a-=2)*a*a*a-2)+b}function h(a,b,c,d){return c*(a/=d)*a*a*a*a+b}function i(a,b,c,d){return c*((a=a/d-1)*a*a*a*a+1)+b}function j(a,b,c,d){return a/=d/2,1>a?c/2*a*a*a*a*a+b:c/2*((a-=2)*a*a*a*a+2)+b}function k(a,b,c,d){return-c*Math.cos(a/d*(Math.PI/2))+c+b}function l(a,b,c,d){return c*Math.sin(a/d*(Math.PI/2))+b}function m(a,b,c,d){return-c/2*(Math.cos(Math.PI*a/d)-1)+b}function n(a,b,c,d){return 0===a?b:c*Math.pow(2,10*(a/d-1))+b}function o(a,b,c,d){return a===d?b+c:c*(-Math.pow(2,-10*a/d)+1)+b}function p(a,b,c,d){return 0===a?b:a===d?b+c:(a/=d/2,1>a?c/2*Math.pow(2,10*(a-1))+b:c/2*(-Math.pow(2,-10*--a)+2)+b)}function q(a,b,c,d){return-c*(Math.sqrt(1-(a/=d)*a)-1)+b}function r(a,b,c,d){return c*Math.sqrt(1-(a=a/d-1)*a)+b}function s(a,b,c,d){return a/=d/2,1>a?-c/2*(Math.sqrt(1-a*a)-1)+b:c/2*(Math.sqrt(1-(a-=2)*a)+1)+b}function t(c,d,e,f){var g=1.70158,h=0,i=e;if(0===c)return d;if(c/=f,1===c)return d+e;h||(h=.3*f);var j=a(i,e,h,g);return-b(j,c,f)+d}function u(b,c,d,e){var f=1.70158,g=0,h=d;if(0===b)return c;if(b/=e,1===b)return c+d;g||(g=.3*e);var i=a(h,d,g,f);return i.a*Math.pow(2,-10*b)*Math.sin(2*(b*e-i.s)*Math.PI/i.p)+i.c+c}function v(c,d,e,f){var g=1.70158,h=0,i=e;if(0===c)return d;if(c/=f/2,2===c)return d+e;h||(h=.3*f*1.5);var j=a(i,e,h,g);return 1>c?-.5*b(j,c,f)+d:j.a*Math.pow(2,-10*(c-=1))*Math.sin(2*(c*f-j.s)*Math.PI/j.p)*.5+j.c+d}function w(a,b,c,d,e){return e===undefined&&(e=1.70158),c*(a/=d)*a*((e+1)*a-e)+b}function x(a,b,c,d,e){return e===undefined&&(e=1.70158),c*((a=a/d-1)*a*((e+1)*a+e)+1)+b}function y(a,b,c,d,e){return e===undefined&&(e=1.70158),a/=d/2,1>a?c/2*a*a*(((e*=1.525)+1)*a-e)+b:c/2*((a-=2)*a*(((e*=1.525)+1)*a+e)+2)+b}function z(a,b,c,d){return c-A(d-a,0,c,d)+b}function A(a,b,c,d){return(a/=d)<1/2.75?7.5625*c*a*a+b:2/2.75>a?c*(7.5625*(a-=1.5/2.75)*a+.75)+b:2.5/2.75>a?c*(7.5625*(a-=2.25/2.75)*a+.9375)+b:c*(7.5625*(a-=2.625/2.75)*a+.984375)+b}function B(a,b,c,d){return d/2>a?.5*z(2*a,0,c,d)+b:.5*A(2*a-d,0,c,d)+.5*c+b}fabric.util.ease={easeInQuad:function(a,b,c,d){return c*(a/=d)*a+b},easeOutQuad:function(a,b,c,d){return-c*(a/=d)*(a-2)+b},easeInOutQuad:function(a,b,c,d){return a/=d/2,1>a?c/2*a*a+b:-c/2*(--a*(a-2)-1)+b},easeInCubic:function(a,b,c,d){return c*(a/=d)*a*a+b},easeOutCubic:c,easeInOutCubic:d,easeInQuart:e,easeOutQuart:f,easeInOutQuart:g,easeInQuint:h,easeOutQuint:i,easeInOutQuint:j,easeInSine:k,easeOutSine:l,easeInOutSine:m,easeInExpo:n,easeOutExpo:o,easeInOutExpo:p,easeInCirc:q,easeOutCirc:r,easeInOutCirc:s,easeInElastic:t,easeOutElastic:u,easeInOutElastic:v,easeInBack:w,easeOutBack:x,easeInOutBack:y,easeInBounce:z,easeOutBounce:A,easeInOutBounce:B}}(),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),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){for(var c in b)if("string"==typeof b[c]&&/^\d+%$/.test(b[c])){var d=parseFloat(b[c],10);"x1"===c||"x2"===c||"r2"===c?b[c]=fabric.util.toFixed(a.width*d/100,2)+a.left:("y1"===c||"y2"===c)&&(b[c]=fabric.util.toFixed(a.height*d/100,2)+a.top)}}function e(a,b){for(var c in b)"x1"===c||"x2"===c||"r2"===c?b[c]=fabric.util.toFixed((b[c]-a.fill.origX)/a.width*100,2)+"%":("y1"===c||"y2"===c)&&(b[c]=fabric.util.toFixed((b[c]-a.fill.origY)/a.height*100,2)+"%")}fabric.Gradient=fabric.util.createClass({origX:0,origY: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.gradientUnits=a.gradientUnits||"objectBoundingBox",this.colorStops=a.colorStops.slice(),a.gradientTransform&&(this.gradientTransform=a.gradientTransform),this.origX=a.left||this.origX,this.origY=a.top||this.origY},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,gradientUnits:this.gradientUnits,colorStops:this.colorStops}},toSVG:function(a,b){var c,d,f=fabric.util.object.clone(this.coords);this.colorStops.sort(function(a,b){return a.offset-b.offset}),b&&"userSpaceOnUse"===this.gradientUnits?(f.x1+=a.width/2,f.y1+=a.height/2,f.x2+=a.width/2,f.y2+=a.height/2):"objectBoundingBox"===this.gradientUnits&&e(a,f),d='id="SVGID_'+this.id+'" gradientUnits="'+this.gradientUnits+'"',this.gradientTransform&&(d+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),"linear"===this.type?c=["\n']:"radial"===this.type&&(c=["\n']);for(var g=0;g\n');return c.push("linear"===this.type?"\n":"\n"),c.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=e.getElementsByTagName("stop"),h="linearGradient"===e.nodeName?"linear":"radial",i=e.getAttribute("gradientUnits")||"objectBoundingBox",j=e.getAttribute("gradientTransform"),k=[],l={};"linear"===h?l=b(e):"radial"===h&&(l=c(e));for(var m=g.length;m--;)k.push(a(g[m]));d(f,l);var n=new fabric.Gradient({type:h,coords:l,gradientUnits:i,colorStops:k});return j&&(n.gradientTransform=fabric.parseTransformAttribute(j)),n},forObject:function(a,b){return b||(b={}),d(a,b),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(""),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("