diff --git a/.jshintrc_tests b/.jshintrc_tests new file mode 100644 index 00000000..c4eca96e --- /dev/null +++ b/.jshintrc_tests @@ -0,0 +1,31 @@ +{ + "globals": { + "fabric": true, + "G_vmlCanvasManager": true + }, + "browser": true, + // TODO: change to true + "eqeqeq": false, + "eqnull": true, + "evil": true, + "expr": true, + "forin": false, + "immed": true, + "lastsemic": true, + "laxbreak": true, + "loopfunc": true, + "multistr": true, + "newcap": true, + "noarg": true, + "node": true, + "noempty": false, + "nomen": false, + "nonew": false, + "onevar": false, + "plusplus": false, + "strict": false, + "sub": true, + "undef": true, + "unused": true, + "qunit": true +} diff --git a/dist/fabric.js b/dist/fabric.js index bf614d99..a22b453e 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -1,4 +1,4 @@ -/* build: `node build.js modules=ALL exclude=gestures,json minifier=uglifyjs` */ +/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: "1.5.0" }; diff --git a/dist/fabric.min.js b/dist/fabric.min.js index 3a57236e..ddd6a411 100644 --- a/dist/fabric.min.js +++ b/dist/fabric.min.js @@ -1,7 +1,15 @@ -/* build: `node build.js modules=ALL exclude=gestures,json minifier=uglifyjs` *//*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.5.0"};typeof exports!="undefined"&&(exports.fabric=fabric),typeof document!="undefined"&&typeof window!="undefined"?(fabric.document=document,fabric.window=window,window.fabric=fabric):(fabric.document=require("jsdom").jsdom(""),fabric.document.createWindow?fabric.window=fabric.document.createWindow():fabric.window=fabric.document.parentWindow),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode=typeof Buffer!="undefined"&&typeof window=="undefined",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,fabric.reNum="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",function(){function e(e,t){if(!this.__eventListeners[e])return;t?fabric.util.removeFromArray(this.__eventListeners[e],t):this.__eventListeners[e].length=0}function t(e,t){this.__eventListeners||(this.__eventListeners={});if(arguments.length===1)for(var n in e)this.on(n,e[n]);else this.__eventListeners[e]||(this.__eventListeners[e]=[]),this.__eventListeners[e].push(t);return this}function n(t,n){if(!this.__eventListeners)return;if(arguments.length===0)this.__eventListeners={};else if(arguments.length===1&&typeof arguments[0]=="object")for(var r in t)e.call(this,r,t[r]);else e.call(this,t,n);return this}function r(e,t){if(!this.__eventListeners)return;var n=this.__eventListeners[e];if(!n)return;for(var r=0,i=n.length;r-1},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=t.complexity?t.complexity():0,e},0)}},function(e){var t=Math.sqrt,n=Math.atan2,r=Math.PI/180;fabric.util={removeFromArray:function(e,t){var n=e.indexOf(t);return n!==-1&&e.splice(n,1),e},getRandomInt:function(e,t){return Math.floor(Math.random()*(t-e+1))+e},degreesToRadians:function(e){return e*r},radiansToDegrees:function(e){return e/r},rotatePoint:function(e,t,n){var r=Math.sin(n),i=Math.cos(n);e.subtractEquals(t);var s=e.x*i-e.y*r,o=e.x*r+e.y*i;return(new fabric.Point(s,o)).addEquals(t)},transformPoint:function(e,t,n){return n?new fabric.Point(t[0]*e.x+t[2]*e.y,t[1]*e.x+t[3]*e.y):new fabric.Point(t[0]*e.x+t[2]*e.y+t[4],t[1]*e.x+t[3]*e.y+t[5])},invertTransform:function(e){var t=e.slice(),n=1/(e[0]*e[3]-e[1]*e[2]);t=[n*e[3],-n*e[1],-n*e[2],n*e[0],0,0];var r=fabric.util.transformPoint({x:e[4],y:e[5]},t);return t[4]=-r.x,t[5]=-r.y,t},toFixed:function(e,t){return parseFloat(Number(e).toFixed(t))},parseUnit:function(e,t){var n=/\D{0,2}$/.exec(e),r=parseFloat(e);t||(t=fabric.Text.DEFAULT_SVG_FONT_SIZE);switch(n[0]){case"mm":return r*fabric.DPI/25.4;case"cm":return r*fabric.DPI/2.54;case"in":return r*fabric.DPI;case"pt":return r*fabric.DPI/72;case"pc":return r*fabric.DPI/72*12;case"em":return r*t;default:return r}},falseFunction:function(){return!1},getKlass:function(e,t){return e=fabric.util.string.camelize(e.charAt(0).toUpperCase()+e.slice(1)),fabric.util.resolveNamespace(t)[e]},resolveNamespace:function(t){if(!t)return fabric;var n=t.split("."),r=n.length,i=e||fabric.window;for(var s=0;sr)r+=u[p++%h],r>l&&(r=l),e[d?"lineTo":"moveTo"](r,0),d=!d;e.restore()},createCanvasElement:function(e){return e||(e=fabric.document.createElement("canvas")),!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(e){var t=e.prototype;for(var n=t.stateProperties.length;n--;){var r=t.stateProperties[n],i=r.charAt(0).toUpperCase()+r.slice(1),s="set"+i,o="get"+i;t[o]||(t[o]=function(e){return new Function('return this.get("'+e+'")')}(r)),t[s]||(t[s]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(r))}},clipContext:function(e,t){t.save(),t.beginPath(),e.clipTo(t),t.clip()},multiplyTransformMatrices:function(e,t){return[e[0]*t[0]+e[2]*t[1],e[1]*t[0]+e[3]*t[1],e[0]*t[2]+e[2]*t[3],e[1]*t[2]+e[3]*t[3],e[0]*t[4]+e[2]*t[5]+e[4],e[1]*t[4]+e[3]*t[5]+e[5]]},getFunctionBody:function(e){return(String(e).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(e,t,n,r){r>0&&(t>r?t-=r:t=0,n>r?n-=r:n=0);var i=!0,s=e.getImageData(t,n,r*2||1,r*2||1);for(var o=3,u=s.data.length;o0?_-=2*h:f===1&&_<0&&(_+=2*h);var D=Math.ceil(Math.abs(_/h*2)),P=[],H=_/D,B=8/3*Math.sin(H/4)*Math.sin(H/4)/Math.sin(H/2),j=M+H;for(var F=0;F=i?s-i:2*Math.PI-(i-s)}function u(e,t,i,s,o,u,a,f){var l=r.call(arguments);if(n[l])return n[l];var c=Math.sqrt,h=Math.min,p=Math.max,d=Math.abs,v=[],m=[[],[]],g,y,b,w,E,S,x,T;y=6*e-12*i+6*o,g=-3*e+9*i-9*o+3*a,b=3*i-3*e;for(var N=0;N<2;++N){N>0&&(y=6*t-12*s+6*u,g=-3*t+9*s-9*u+3*f,b=3*s-3*t);if(d(g)<1e-12){if(d(y)<1e-12)continue;w=-b/y,0=t})}function r(e,t){return i(e,t,function(e,t){return e>>0;if(n===0)return-1;var r=0;arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:r!==0&&r!==Number.POSITIVE_INFINITY&&r!==Number.NEGATIVE_INFINITY&&(r=(r>0||-1)*Math.floor(Math.abs(r))));if(r>=n)return-1;var i=r>=0?r:Math.max(n-Math.abs(r),0);for(;i>>0;n>>0;r>>0;n>>0;n>>0;i>>0,n=0,r;if(arguments.length>1)r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n/g,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:e,capitalize:t,escapeXml:n}}(),function(){var e=Array.prototype.slice,t=Function.prototype.apply,n=function(){};Function.prototype.bind||(Function.prototype.bind=function(r){var i=this,s=e.call(arguments,1),o;return s.length?o=function(){return t.call(i,this instanceof n?this:r,s.concat(e.call(arguments)))}:o=function(){return t.call(i,this instanceof n?this:r,arguments)},n.prototype=this.prototype,o.prototype=new n,o})}(),function(){function i(){}function s(t){var n=this.constructor.superclass.prototype[t];return arguments.length>1?n.apply(this,e.call(arguments,1)):n.call(this)}function o(){function u(){this.initialize.apply(this,arguments)}var n=null,o=e.call(arguments,0);typeof o[0]=="function"&&(n=o.shift()),u.superclass=n,u.subclasses=[],n&&(i.prototype=n.prototype,u.prototype=new i,n.subclasses.push(u));for(var a=0,f=o.length;a-1?e.prototype[i]=function(e){return function(){var n=this.constructor.superclass;this.constructor.superclass=r;var i=t[e].apply(this,arguments);this.constructor.superclass=n;if(e!=="initialize")return i}}(i):e.prototype[i]=t[i],n&&(t.toString!==Object.prototype.toString&&(e.prototype.toString=t.toString),t.valueOf!==Object.prototype.valueOf&&(e.prototype.valueOf=t.valueOf))};fabric.util.createClass=o}(),function(){function t(e){var t=Array.prototype.slice.call(arguments,1),n,r,i=t.length;for(r=0;r-1?s(e,t.match(/opacity:\s*(\d?\.?\d*)/)[1]):e;for(var r in t)if(r==="opacity")s(e,t[r]);else{var i=r==="float"||r==="cssFloat"?typeof n.styleFloat=="undefined"?"cssFloat":"styleFloat":r;n[i]=t[r]}return e}var t=fabric.document.createElement("div"),n=typeof t.style.opacity=="string",r=typeof t.style.filter=="string",i=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,s=function(e){return e};n?s=function(e,t){return e.style.opacity=t,e}:r&&(s=function(e,t){var n=e.style;return e.currentStyle&&!e.currentStyle.hasLayout&&(n.zoom=1),i.test(n.filter)?(t=t>=.9999?"":"alpha(opacity="+t*100+")",n.filter=n.filter.replace(i,t)):n.filter+=" alpha(opacity="+t*100+")",e}),fabric.util.setStyle=e}(),function(){function t(e){return typeof e=="string"?fabric.document.getElementById(e):e}function s(e,t){var n=fabric.document.createElement(e);for(var r in t)r==="class"?n.className=t[r]:r==="for"?n.htmlFor=t[r]:n.setAttribute(r,t[r]);return n}function o(e,t){e&&(" "+e.className+" ").indexOf(" "+t+" ")===-1&&(e.className+=(e.className?" ":"")+t)}function u(e,t,n){return typeof t=="string"&&(t=s(t,n)),e.parentNode&&e.parentNode.replaceChild(t,e),t.appendChild(e),t}function a(e,t){var n,r,i=0,s=0,o=fabric.document.documentElement,u=fabric.document.body||{scrollLeft:0,scrollTop:0};r=e;while(e&&e.parentNode&&!n)e=e.parentNode,e.nodeType===1&&fabric.util.getElementStyle(e,"position")==="fixed"&&(n=e),e.nodeType===1&&r!==t&&fabric.util.getElementStyle(e,"position")==="absolute"?(i=0,s=0):e===fabric.document?(i=u.scrollLeft||o.scrollLeft||0,s=u.scrollTop||o.scrollTop||0):(i+=e.scrollLeft||0,s+=e.scrollTop||0);return{left:i,top:s}}function f(e){var t,n=e&&e.ownerDocument,r={left:0,top:0},i={left:0,top:0},s,o={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!n)return{left:0,top:0};for(var u in o)i[o[u]]+=parseInt(l(e,u),10)||0;return t=n.documentElement,typeof e.getBoundingClientRect!="undefined"&&(r=e.getBoundingClientRect()),s=fabric.util.getScrollLeftTop(e,null),{left:r.left+s.left-(t.clientLeft||0)+i.left,top:r.top+s.top-(t.clientTop||0)+i.top}}var e=Array.prototype.slice,n,r=function(t){return e.call(t,0)};try{n=r(fabric.document.childNodes)instanceof Array}catch(i){}n||(r=function(e){var t=new Array(e.length),n=e.length;while(n--)t[n]=e[n];return t});var l;fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?l=function(e,t){var n=fabric.document.defaultView.getComputedStyle(e,null);return n?n[t]:undefined}:l=function(e,t){var n=e.style[t];return!n&&e.currentStyle&&(n=e.currentStyle[t]),n},function(){function n(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=fabric.util.falseFunction),t?e.style[t]="none":typeof e.unselectable=="string"&&(e.unselectable="on"),e}function r(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=null),t?e.style[t]="":typeof e.unselectable=="string"&&(e.unselectable=""),e}var e=fabric.document.documentElement.style,t="userSelect"in e?"userSelect":"MozUserSelect"in e?"MozUserSelect":"WebkitUserSelect"in e?"WebkitUserSelect":"KhtmlUserSelect"in e?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=n,fabric.util.makeElementSelectable=r}(),function(){function e(e,t){var n=fabric.document.getElementsByTagName("head")[0],r=fabric.document.createElement("script"),i=!0;r.onload=r.onreadystatechange=function(e){if(i){if(typeof this.readyState=="string"&&this.readyState!=="loaded"&&this.readyState!=="complete")return;i=!1,t(e||fabric.window.event),r=r.onload=r.onreadystatechange=null}},r.src=e,n.appendChild(r)}fabric.util.getScript=e}(),fabric.util.getById=t,fabric.util.toArray=r,fabric.util.makeElement=s,fabric.util.addClass=o,fabric.util.wrapElement=u,fabric.util.getScrollLeftTop=a,fabric.util.getElementOffset=f,fabric.util.getElementStyle=l}(),function(){function e(e,t){return e+(/\?/.test(e)?"&":"?")+t}function n(){}function r(r,i){i||(i={});var s=i.method?i.method.toUpperCase():"GET",o=i.onComplete||function(){},u=t(),a;return u.onreadystatechange=function(){u.readyState===4&&(o(u),u.onreadystatechange=n)},s==="GET"&&(a=null,typeof i.parameters=="string"&&(r=e(r,i.parameters))),u.open(s,r,!0),(s==="POST"||s==="PUT")&&u.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),u.send(a),u}var t=function(){var e=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var t=e.length;t--;)try{var n=e[t]();if(n)return e[t]}catch(r){}}();fabric.util.request=r}(),fabric.log=function(){},fabric.warn=function(){},typeof console!="undefined"&&["log","warn"].forEach(function(e){typeof console[e]!="undefined"&&typeof console[e].apply=="function"&&(fabric[e]=function(){return console[e].apply(console,arguments)})}),function(){function e(e){n(function(t){e||(e={});var r=t||+(new Date),i=e.duration||500,s=r+i,o,u=e.onChange||function(){},a=e.abort||function(){return!1},f=e.easing||function(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t},l="startValue"in e?e.startValue:0,c="endValue"in e?e.endValue:100,h=e.byValue||c-l;e.onStart&&e.onStart(),function p(t){o=t||+(new Date);var c=o>s?i:o-r;if(a()){e.onComplete&&e.onComplete();return}u(f(c,l,h,i));if(o>s){e.onComplete&&e.onComplete();return}n(p)}(r)})}function n(){return t.apply(fabric.window,arguments)}var t=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(e){fabric.window.setTimeout(e,1e3/60)};fabric.util.animate=e,fabric.util.requestAnimFrame=n}(),function(){function e(e,t,n,r){return es?s:i;if(i===1&&s===1&&o===0&&u===0)return;l=" matrix("+i+" 0"+" 0 "+s+" "+o*i+" "+u*s+") ";if(e.tagName==="svg"){c=e.ownerDocument.createElement("g");while(e.firstChild!=null)c.appendChild(e.firstChild);e.appendChild(c)}else c=e,l=c.getAttribute("transform")+l;c.setAttribute("transform",l)}function x(e){var n=e.objects,i=e.options;return n=n.map(function(e){return t[r(e.type)].fromObject(e)}),{objects:n,options:i}}function T(e,t,n){t[n]&&t[n].toSVG&&e.push('','')}var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.string.capitalize,i=t.util.object.clone,s=t.util.toFixed,o=t.util.parseUnit,u=t.util.multiplyTransformMatrices,a={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"},f={stroke:"strokeOpacity",fill:"fillOpacity"};t.cssRules={},t.gradientDefs={},t.parseTransformAttribute=function(){function e(e,t){var n=t[0];e[0]=Math.cos(n),e[1]=Math.sin(n),e[2]=-Math.sin(n),e[3]=Math.cos(n)}function n(e,t){var n=t[0],r=t.length===2?t[1]:t[0];e[0]=n,e[3]=r}function r(e,n){e[2]=Math.tan(t.util.degreesToRadians(n[0]))}function i(e,n){e[1]=Math.tan(t.util.degreesToRadians(n[0]))}function s(e,t){e[4]=t[0],t.length===2&&(e[5]=t[1])}var o=[1,0,0,1,0,0],u=t.reNum,a="(?:\\s+,?\\s*|,\\s*)",f="(?:(skewX)\\s*\\(\\s*("+u+")\\s*\\))",l="(?:(skewY)\\s*\\(\\s*("+u+")\\s*\\))",c="(?:(rotate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+")"+a+"("+u+"))?\\s*\\))",h="(?:(scale)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",p="(?:(translate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",d="(?:(matrix)\\s*\\(\\s*("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+"\\s*\\))",v="(?:"+d+"|"+p+"|"+h+"|"+c+"|"+f+"|"+l+")",m="(?:"+v+"(?:"+a+v+")*"+")",g="^\\s*(?:"+m+"?)\\s*$",y=new RegExp(g),b=new RegExp(v,"g");return function(u){var a=o.concat(),f=[];if(!u||u&&!y.test(u))return a;u.replace(b,function(u){var l=(new RegExp(v)).exec(u).filter(function(e){return e!==""&&e!=null}),c=l[1],h=l.slice(2).map(parseFloat);switch(c){case"translate":s(a,h);break;case"rotate":h[0]=t.util.degreesToRadians(h[0]),e(a,h);break;case"scale":n(a,h);break;case"skewX":r(a,h);break;case"skewY":i(a,h);break;case"matrix":a=h}f.push(a.concat()),a=o.concat()});var l=f[0];while(f.length>1)f.shift(),l=t.util.multiplyTransformMatrices(l,f[0]);return l}}();var w=new RegExp("^\\s*("+t.reNum+"+)\\s*,?"+"\\s*("+t.reNum+"+)\\s*,?"+"\\s*("+t.reNum+"+)\\s*,?"+"\\s*("+t.reNum+"+)\\s*"+"$");t.parseSVGDocument=function(){function r(e,t){while(e&&(e=e.parentNode))if(t.test(e.nodeName)&&!e.getAttribute("instantiated_by_use"))return!0;return!1}var e=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,n=/^(symbol|image|marker|pattern|view)$/;return function(s,u,a){if(!s)return;b(s);var f=new Date,l=t.Object.__uid++,c,h,p=!1;s.getAttribute("width")&&s.getAttribute("width")!=="100%"&&(c=o(s.getAttribute("width"))),s.getAttribute("height")&&s.getAttribute("height")!=="100%"&&(h=o(s.getAttribute("height")));if(!c||!h){var d=s.getAttribute("viewBox");d&&(d=d.match(w))?(c=parseFloat(d[3]),h=parseFloat(d[4])):p=!0}E(s,c,h);var v=t.util.toArray(s.getElementsByTagName("*"));if(v.length===0&&t.isLikelyNode){v=s.selectNodes('//*[name(.)!="svg"]');var m=[];for(var g=0,y=v.length;g/i,"")));if(!s||!s.documentElement)return;t.parseSVGDocument(s.documentElement,function(r,i){S.set(e,{objects:t.util.array.invoke(r,"toObject"),options:i}),n(r,i)},r)}e=e.replace(/^\n\s*/,"").trim(),S.has(e,function(r){r?S.get(e,function(e){var t=x(e);n(t.objects,t.options)}):new t.util.request(e,{method:"get",onComplete:i})})},loadSVGFromString:function(e,n,r){e=e.trim();var i;if(typeof DOMParser!="undefined"){var s=new DOMParser;s&&s.parseFromString&&(i=s.parseFromString(e,"text/xml"))}else t.window.ActiveXObject&&(i=new ActiveXObject("Microsoft.XMLDOM"),i.async="false",i.loadXML(e.replace(//i,"")));t.parseSVGDocument(i.documentElement,function(e,t){n(e,t)},r)},createSVGFontFacesMarkup:function(e){var t="";for(var n=0,r=e.length;n',"",""].join("")),t},createSVGRefElementsMarkup:function(e){var t=[];return T(t,e,"backgroundColor"),T(t,e,"overlayColor"),t.join("")}})}(typeof exports!="undefined"?exports:this),fabric.ElementsParser=function(e,t,n,r){this.elements=e,this.callback=t,this.options=n,this.reviver=r,this.svgUid=n&&n.svgUid||0},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var e=0,t=this.elements.length;ee.x&&this.y>e.y},gte:function(e){return this.x>=e.x&&this.y>=e.y},lerp:function(e,t){return new n(this.x+(e.x-this.x)*t,this.y+(e.y-this.y)*t)},distanceFrom:function(e){var t=this.x-e.x,n=this.y-e.y;return Math.sqrt(t*t+n*n)},midPointFrom:function(e){return new n(this.x+(e.x-this.x)/2,this.y+(e.y-this.y)/2)},min:function(e){return new n(Math.min(this.x,e.x),Math.min(this.y,e.y))},max:function(e){return new n(Math.max(this.x,e.x),Math.max(this.y,e.y))},toString:function(){return this.x+","+this.y},setXY:function(e,t){this.x=e,this.y=t},setFromPoint:function(e){this.x=e.x,this.y=e.y},swap:function(e){var t=this.x,n=this.y;this.x=e.x,this.y=e.y,e.x=t,e.y=n}}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){this.status=e,this.points=[]}var t=e.fabric||(e.fabric={});if(t.Intersection){t.warn("fabric.Intersection is already defined");return}t.Intersection=n,t.Intersection.prototype={appendPoint:function(e){this.points.push(e)},appendPoints:function(e){this.points=this.points.concat(e)}},t.Intersection.intersectLineLine=function(e,r,i,s){var o,u=(s.x-i.x)*(e.y-i.y)-(s.y-i.y)*(e.x-i.x),a=(r.x-e.x)*(e.y-i.y)-(r.y-e.y)*(e.x-i.x),f=(s.y-i.y)*(r.x-e.x)-(s.x-i.x)*(r.y-e.y);if(f!==0){var l=u/f,c=a/f;0<=l&&l<=1&&0<=c&&c<=1?(o=new n("Intersection"),o.points.push(new t.Point(e.x+l*(r.x-e.x),e.y+l*(r.y-e.y)))):o=new n}else u===0||a===0?o=new n("Coincident"):o=new n("Parallel");return o},t.Intersection.intersectLinePolygon=function(e,t,r){var i=new n,s=r.length;for(var o=0;o0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n,i=e.length;for(var s=0;s0&&(r.status="Intersection"),r},t.Intersection.intersectPolygonRectangle=function(e,r,i){var s=r.min(i),o=r.max(i),u=new t.Point(o.x,s.y),a=new t.Point(s.x,o.y),f=n.intersectLinePolygon(s,u,e),l=n.intersectLinePolygon(u,o,e),c=n.intersectLinePolygon(o,a,e),h=n.intersectLinePolygon(a,s,e),p=new n;return p.appendPoints(f.points),p.appendPoints(l.points),p.appendPoints(c.points),p.appendPoints(h.points),p.points.length>0&&(p.status="Intersection"),p}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){e?this._tryParsingColor(e):this.setSource([0,0,0,1])}function r(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}var t=e.fabric||(e.fabric={});if(t.Color){t.warn("fabric.Color is already defined.");return}t.Color=n,t.Color.prototype={_tryParsingColor:function(e){var t;e in n.colorNameMap&&(e=n.colorNameMap[e]);if(e==="transparent"){this.setSource([255,255,255,0]);return}t=n.sourceFromHex(e),t||(t=n.sourceFromRgb(e)),t||(t=n.sourceFromHsl(e)),t&&this.setSource(t)},_rgbToHsl:function(e,n,r){e/=255,n/=255,r/=255;var i,s,o,u=t.util.array.max([e,n,r]),a=t.util.array.min([e,n,r]);o=(u+a)/2;if(u===a)i=s=0;else{var f=u-a;s=o>.5?f/(2-u-a):f/(u+a);switch(u){case e:i=(n-r)/f+(n1?1:n;if(t){var o=t.split(/\s*;\s*/);o[o.length-1]===""&&o.pop();for(var u=o.length;u--;){var a=o[u].split(/\s*:\s*/),f=a[0].trim(),l=a[1].trim();f==="stop-color"?r=l:f==="stop-opacity"&&(s=l)}}return r||(r=e.getAttribute("stop-color")||"rgb(0,0,0)"),s||(s=e.getAttribute("stop-opacity")),r=new fabric.Color(r),i=r.getAlpha(),s=isNaN(parseFloat(s))?1:parseFloat(s),s*=i,{offset:n,color:r.toRgb(),opacity:s}}function t(e){return{x1:e.getAttribute("x1")||0,y1:e.getAttribute("y1")||0,x2:e.getAttribute("x2")||"100%",y2:e.getAttribute("y2")||0}}function n(e){return{x1:e.getAttribute("fx")||e.getAttribute("cx")||"50%",y1:e.getAttribute("fy")||e.getAttribute("cy")||"50%",r1:0,x2:e.getAttribute("cx")||"50%",y2:e.getAttribute("cy")||"50%",r2:e.getAttribute("r")||"50%"}}function r(e,t,n){var r,i=0,s=1,o="";for(var u in t){r=parseFloat(t[u],10),typeof t[u]=="string"&&/^\d+%$/.test(t[u])?s=.01:s=1;if(u==="x1"||u==="x2"||u==="r2")s*=n==="objectBoundingBox"?e.width:1,i=n==="objectBoundingBox"?e.left||0:0;else if(u==="y1"||u==="y2")s*=n==="objectBoundingBox"?e.height:1,i=n==="objectBoundingBox"?e.top||0:0;t[u]=r*s+i}if(e.type==="ellipse"&&t.r2!==null&&n==="objectBoundingBox"&&e.rx!==e.ry){var a=e.ry/e.rx;o=" scale(1, "+a+")",t.y1&&(t.y1/=a),t.y2&&(t.y2/=a)}return o}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(e){e||(e={});var t={};this.id=fabric.Object.__uid++,this.type=e.type||"linear",t={x1:e.coords.x1||0,y1:e.coords.y1||0,x2:e.coords.x2||0,y2:e.coords.y2||0},this.type==="radial"&&(t.r1=e.coords.r1||0,t.r2=e.coords.r2||0),this.coords=t,this.colorStops=e.colorStops.slice(),e.gradientTransform&&(this.gradientTransform=e.gradientTransform),this.offsetX=e.offsetX||this.offsetX,this.offsetY=e.offsetY||this.offsetY},addColorStop:function(e){for(var t in e){var n=new fabric.Color(e[t]);this.colorStops.push({offset:t,color:n.toRgb(),opacity:n.getAlpha()})}return this},toObject:function(){return{type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(e){var t=fabric.util.object.clone(this.coords),n,r;this.colorStops.sort(function(e,t){return e.offset-t.offset});if(!e.group||e.group.type!=="path-group")for(var i in t)if(i==="x1"||i==="x2"||i==="r2")t[i]+=this.offsetX-e.width/2;else if(i==="y1"||i==="y2")t[i]+=this.offsetY-e.height/2;r='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',this.gradientTransform&&(r+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),this.type==="linear"?n=["\n']:this.type==="radial"&&(n=["\n']);for(var s=0;s\n');return n.push(this.type==="linear"?"\n":"\n"),n.join("")},toLive:function(e,t){var n,r,i=fabric.util.object.clone(this.coords);if(!this.type)return;if(t.group&&t.group.type==="path-group")for(r in i)if(r==="x1"||r==="x2")i[r]+=-this.offsetX+t.width/2;else if(r==="y1"||r==="y2")i[r]+=-this.offsetY+t.height/2;if(t.type==="text"||t.type==="i-text")for(r in i)if(r==="x1"||r==="x2")i[r]-=t.width/2;else if(r==="y1"||r==="y2")i[r]-=t.height/2;this.type==="linear"?n=e.createLinearGradient(i.x1,i.y1,i.x2,i.y2):this.type==="radial"&&(n=e.createRadialGradient(i.x1,i.y1,i.r1,i.x2,i.y2,i.r2));for(var s=0,o=this.colorStops.length;s\n'+'\n'+"\n"},toLive:function(e){var t=typeof this.source=="function"?this.source():this.source;if(!t)return"";if(typeof t.src!="undefined"){if(!t.complete)return"";if(t.naturalWidth===0||t.naturalHeight===0)return""}return e.createPattern(t,this.repeat)}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.toFixed;if(t.Shadow){t.warn("fabric.Shadow is already defined.");return}t.Shadow=t.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(e){typeof e=="string"&&(e=this._parseShadow(e));for(var n in e)this[n]=e[n];this.id=t.Object.__uid++},_parseShadow:function(e){var n=e.trim(),r=t.Shadow.reOffsetsAndBlur.exec(n)||[],i=n.replace(t.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:i.trim(),offsetX:parseInt(r[1],10)||0,offsetY:parseInt(r[2],10)||0,blur:parseInt(r[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(e){var t="SourceAlpha",r=40,i=40;return e&&(e.fill===this.color||e.stroke===this.color)&&(t="SourceGraphic"),e.width&&e.height&&(r=n(Math.abs(this.offsetX/e.getWidth()),2)*100+20,i=n(Math.abs(this.offsetY/e.getHeight()),2)*100+20),'\n"+' \n'+' \n'+' \n'+" \n"+" \n"+' \n'+" \n"+"\n"},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var e={},n=t.Shadow.prototype;return this.color!==n.color&&(e.color=this.color),this.blur!==n.blur&&(e.blur=this.blur),this.offsetX!==n.offsetX&&(e.offsetX=this.offsetX),this.offsetY!==n.offsetY&&(e.offsetY=this.offsetY),e}}),t.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/}(typeof exports!="undefined"?exports:this),function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var e=fabric.util.object.extend,t=fabric.util.getElementOffset,n=fabric.util.removeFromArray,r=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(e,t){t||(t={}),this._initStatic(e,t),fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,preserveObjectStacking:!1,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(e,t){this._objects=[],this._createLowerCanvas(e),this._initOptions(t),this._setImageSmoothing(),t.overlayImage&&this.setOverlayImage(t.overlayImage,this.renderAll.bind(this)),t.backgroundImage&&this.setBackgroundImage(t.backgroundImage,this.renderAll.bind(this)),t.backgroundColor&&this.setBackgroundColor(t.backgroundColor,this.renderAll.bind(this)),t.overlayColor&&this.setOverlayColor(t.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(e,t,n){return this.__setBgOverlayImage("overlayImage",e,t,n)},setBackgroundImage:function(e,t,n){return this.__setBgOverlayImage("backgroundImage",e,t,n)},setOverlayColor:function(e,t){return this.__setBgOverlayColor("overlayColor",e,t)},setBackgroundColor:function(e,t){return this.__setBgOverlayColor("backgroundColor",e,t)},_setImageSmoothing:function(){var e=this.getContext();e.imageSmoothingEnabled=this.imageSmoothingEnabled,e.webkitImageSmoothingEnabled=this.imageSmoothingEnabled,e.mozImageSmoothingEnabled=this.imageSmoothingEnabled,e.msImageSmoothingEnabled=this.imageSmoothingEnabled,e.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(e,t,n,r){return typeof t=="string"?fabric.util.loadImage(t,function(t){this[e]=new fabric.Image(t,r),n&&n()},this,r&&r.crossOrigin):(r&&t.setOptions(r),this[e]=t,n&&n()),this},__setBgOverlayColor:function(e,t,n){if(t&&t.source){var r=this;fabric.util.loadImage(t.source,function(i){r[e]=new fabric.Pattern({source:i,repeat:t.repeat,offsetX:t.offsetX,offsetY:t.offsetY}),n&&n()})}else this[e]=t,n&&n();return this},_createCanvasElement:function(){var e=fabric.document.createElement("canvas");e.style||(e.style={});if(!e)throw r;return this._initCanvasElement(e),e},_initCanvasElement:function(e){fabric.util.createCanvasElement(e);if(typeof e.getContext=="undefined")throw r},_initOptions:function(e){for(var t in e)this[t]=e[t];this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice()},_createLowerCanvas:function(e){this.lowerCanvasEl=fabric.util.getById(e)||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(e,t){return this.setDimensions({width:e},t)},setHeight:function(e,t){return this.setDimensions({height:e},t)},setDimensions:function(e,t){var n;t=t||{};for(var r in e)n=e[r],t.cssOnly||(this._setBackstoreDimension(r,e[r]),n+="px"),t.backstoreOnly||this._setCssDimension(r,n);return t.cssOnly||this.renderAll(),this.calcOffset(),this},_setBackstoreDimension:function(e,t){return this.lowerCanvasEl[e]=t,this.upperCanvasEl&&(this.upperCanvasEl[e]=t),this.cacheCanvasEl&&(this.cacheCanvasEl[e]=t),this[e]=t,this},_setCssDimension:function(e,t){return this.lowerCanvasEl.style[e]=t,this.upperCanvasEl&&(this.upperCanvasEl.style[e]=t),this.wrapperEl&&(this.wrapperEl.style[e]=t),this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(e){var t=this.getActiveGroup();this.viewportTransform=e,this.renderAll();for(var n=0,r=this._objects.length;n"),n.join("")},_setSVGPreamble:function(e,t){t.suppressPreamble||e.push('','\n')},_setSVGHeader:function(e,t){var n,r,i;t.viewBox?(n=t.viewBox.width,r=t.viewBox.height):(n=this.width,r=this.height,this.svgViewportTransformation||(i=this.viewportTransform,n/=i[0],r/=i[3])),e.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(e,t){for(var n=0,r=this.getObjects(),i=r.length;n"):this[t]&&t==="overlayColor"&&e.push('")},sendToBack:function(e){return n(this._objects,e),this._objects.unshift(e),this.renderAll&&this.renderAll()},bringToFront:function(e){return n(this._objects,e),this._objects.push(e),this.renderAll&&this.renderAll()},sendBackwards:function(e,t){var r=this._objects.indexOf(e);if(r!==0){var i=this._findNewLowerIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(e,t,n){var r;if(n){r=t;for(var i=t-1;i>=0;--i){var s=e.intersectsWithObject(this._objects[i])||e.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(e);if(s){r=i;break}}}else r=t-1;return r},bringForward:function(e,t){var r=this._objects.indexOf(e);if(r!==this._objects.length-1){var i=this._findNewUpperIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(e,t,n){var r;if(n){r=t;for(var i=t+1;i"}}),e(fabric.StaticCanvas.prototype,fabric.Observable),e(fabric.StaticCanvas.prototype,fabric.Collection),e(fabric.StaticCanvas.prototype,fabric.DataURLExporter),e(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(e){var t=fabric.util.createCanvasElement();if(!t||!t.getContext)return null;var n=t.getContext("2d");if(!n)return null;switch(e){case"getImageData":return typeof n.getImageData!="undefined";case"setLineDash":return typeof n.setLineDash!="undefined";case"toDataURL":return typeof t.toDataURL!="undefined";case"toDataURLWithQuality":try{return t.toDataURL("image/jpeg",0),!0}catch(r){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",strokeDashArray:null,setShadow:function(e){return this.shadow=new fabric.Shadow(e),this},_setBrushStyles:function(){var e=this.canvas.contextTop;e.strokeStyle=this.color,e.lineWidth=this.width,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin,this.strokeDashArray&&fabric.StaticCanvas.supports("setLineDash")&&e.setLineDash(this.strokeDashArray)},_setShadow:function(){if(!this.shadow)return;var e=this.canvas.contextTop;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var e=this.canvas.contextTop;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0}}),function(){fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(e){this.canvas=e,this._points=[]},onMouseDown:function(e){this._prepareForDrawing(e),this._captureDrawingPath(e),this._render()},onMouseMove:function(e){this._captureDrawingPath(e),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(e){var t=new fabric.Point(e.x,e.y);this._reset(),this._addPoint(t),this.canvas.contextTop.moveTo(t.x,t.y)},_addPoint:function(e){this._points.push(e)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(e){var t=new fabric.Point(e.x,e.y);this._addPoint(t)},_render:function(){var e=this.canvas.contextTop,t=this.canvas.viewportTransform,n=this._points[0],r=this._points[1];e.save(),e.transform(t[0],t[1],t[2],t[3],t[4],t[5]),e.beginPath(),this._points.length===2&&n.x===r.x&&n.y===r.y&&(n.x-=.5,r.x+=.5),e.moveTo(n.x,n.y);for(var i=1,s=this._points.length;in.padding?e.x<0?e.x+=n.padding:e.x-=n.padding:e.x=0,i(e.y)>n.padding?e.y<0?e.y+=n.padding:e.y-=n.padding:e.y=0},_rotateObject:function(e,t){var i=this._currentTransform;if(i.target.get("lockRotation"))return;var s=r(i.ey-i.top,i.ex-i.left),o=r(t-i.top,e-i.left),u=n(o-s+i.theta);u<0&&(u=360+u),i.target.angle=u%360},setCursor:function(e){this.upperCanvasEl.style.cursor=e},_resetObjectTransform:function(e){e.scaleX=1,e.scaleY=1,e.setAngle(0)},_drawSelection:function(){var e=this.contextTop,t=this._groupSelector,n=t.left,r=t.top,o=i(n),u=i(r);e.fillStyle=this.selectionColor,e.fillRect(t.ex-(n>0?0:-n),t.ey-(r>0?0:-r),o,u),e.lineWidth=this.selectionLineWidth,e.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var a=t.ex+s-(n>0?0:o),f=t.ey+s-(r>0?0:u);e.beginPath(),fabric.util.drawDashedLine(e,a,f,a+o,f,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f+u-1,a+o,f+u-1,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f,a,f+u,this.selectionDashArray),fabric.util.drawDashedLine(e,a+o-1,f,a+o-1,f+u,this.selectionDashArray),e.closePath(),e.stroke()}else e.strokeRect(t.ex+s-(n>0?0:o),t.ey+s-(r>0?0:u),o,u)},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e,!0))},findTarget:function(e,t){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e))return this.lastRenderedObjectWithControlsAboveOverlay;var n=this.getActiveGroup();if(n&&!t&&this.containsPoint(e,n))return n;var r=this._searchPossibleTargets(e);return this._fireOverOutEvents(r),r},_fireOverOutEvents:function(e){e?this._hoveredTarget!==e&&(this.fire("mouse:over",{target:e}),e.fire("mouseover"),this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout")),this._hoveredTarget=e):this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout"),this._hoveredTarget=null)},_checkTarget:function(e,t,n){if(t&&t.visible&&t.evented&&this.containsPoint(e,t)){if(!this.perPixelTargetFind&&!t.perPixelTargetFind||!!t.isEditing)return!0;var r=this.isTargetTransparent(t,n.x,n.y);if(!r)return!0}},_searchPossibleTargets:function(e){var t,n=this.getPointer(e,!0),r=this._objects.length;while(r--)if(!this._objects[r].group&&this._checkTarget(e,this._objects[r],n)){this.relatedTarget=this._objects[r],t=this._objects[r];break}return t},getPointer:function(t,n,r){r||(r=this.upperCanvasEl);var i=e(t,r),s=r.getBoundingClientRect(),o=s.width||0,u=s.height||0,a;if(!o||!u)"top"in s&&"bottom"in s&&(u=Math.abs(s.top-s.bottom)),"right"in s&&"left"in s&&(o=Math.abs(s.right-s.left));return this.calcOffset(),i.x=i.x-this._offset.left,i.y=i.y-this._offset.top,n||(i=fabric.util.transformPoint(i,fabric.util.invertTransform(this.viewportTransform))),o===0||u===0?a={width:1,height:1}:a={width:r.width/o,height:r.height/u},{x:i.x*a.width,y:i.y*a.height}},_createUpperCanvas:function(){var e=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement(),fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+e),this.wrapperEl.appendChild(this.upperCanvasEl),this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl),this._applyCanvasStyle(this.upperCanvasEl),this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement(),this.cacheCanvasEl.setAttribute("width",this.width),this.cacheCanvasEl.setAttribute("height",this.height),this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass}),fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"}),fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(e){var t=this.getWidth()||e.width,n=this.getHeight()||e.height;fabric.util.setStyle(e,{position:"absolute",width:t+"px",height:n+"px",left:0,top:0}),e.width=t,e.height=n,fabric.util.makeElementUnselectable(e)},_copyCanvasStyle:function(e,t){t.style.cssText=e.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(e){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=e,e.set("active",!0)},setActiveObject:function(e,t){return this._setActiveObject(e),this.renderAll(),this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t}),this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=null},discardActiveObject:function(e){return this._discardActiveObject(),this.renderAll(),this.fire("selection:cleared",{e:e}),this},_setActiveGroup:function(e){this._activeGroup=e,e&&e.set("active",!0)},setActiveGroup:function(e,t){return this._setActiveGroup(e),e&&(this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t})),this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var e=this.getActiveGroup();e&&e.destroy(),this.setActiveGroup(null)},discardActiveGroup:function(e){return this._discardActiveGroup(),this.fire("selection:cleared",{e:e}),this},deactivateAll:function(){var e=this.getObjects(),t=0,n=e.length;for(;t1)return;var r=this._groupSelector;r?(n=this.getPointer(e,!0),r.left=n.x-r.ex,r.top=n.y-r.ey,this.renderTop()):this._currentTransform?this._transformObject(e):(t=this.findTarget(e),!t||t&&!t.selectable?this.setCursor(this.defaultCursor):this._setCursorFromEvent(e,t)),this.fire("mouse:move",{target:t,e:e}),t&&t.fire("mousemove",{e:e})},_transformObject:function(e){var t=this.getPointer(e),n=this._currentTransform;n.reset=!1,n.target.isMoving=!0,this._beforeScaleTransform(e,n),this._performTransformAction(e,n,t),this.renderAll()},_performTransformAction:function(e,t,n){var r=n.x,i=n.y,s=t.target,o=t.action;o==="rotate"?(this._rotateObject(r,i),this._fire("rotating",s,e)):o==="scale"?(this._onScale(e,t,r,i),this._fire("scaling",s,e)):o==="scaleX"?(this._scaleObject(r,i,"x"),this._fire("scaling",s,e)):o==="scaleY"?(this._scaleObject(r,i,"y"),this._fire("scaling",s,e)):(this._translateObject(r,i),this._fire("moving",s,e),this.setCursor(this.moveCursor))},_fire:function(e,t,n){this.fire("object:"+e,{target:t,e:n}),t.fire(e,{e:n})},_beforeScaleTransform:function(e,t){if(t.action==="scale"||t.action==="scaleX"||t.action==="scaleY"){var n=this._shouldCenterTransform(e,t.target);if(n&&(t.originX!=="center"||t.originY!=="center")||!n&&t.originX==="center"&&t.originY==="center")this._resetCurrentTransform(e),t.reset=!0}},_onScale:function(e,t,n,r){(e.shiftKey||this.uniScaleTransform)&&!t.target.get("lockUniScaling")?(t.currentAction="scale",this._scaleObject(n,r)):(!t.reset&&t.currentAction==="scale"&&this._resetCurrentTransform(e,t.target),t.currentAction="scaleEqually",this._scaleObject(n,r,"equally"))},_setCursorFromEvent:function(e,t){if(!t||!t.selectable)return this.setCursor(this.defaultCursor),!1;var n=this.getActiveGroup(),r=t._findTargetCorner&&(!n||!n.contains(t))&&t._findTargetCorner(this.getPointer(e,!0));return r?this._setCornerCursor(r,t):this.setCursor(t.hoverCursor||this.hoverCursor),!0},_setCornerCursor:function(t,n){if(t in e)this.setCursor(this._getRotatedCornerCursor(t,n));else{if(t!=="mtr"||!n.hasRotatingPoint)return this.setCursor(this.defaultCursor),!1;this.setCursor(this.rotationCursor)}},_getRotatedCornerCursor:function(t,n){var r=Math.round(n.getAngle()%360/45);return r<0&&(r+=8),r+=e[t],r%=8,this.cursorMap[r]}})}(),function(){var e=Math.min,t=Math.max;fabric.util.object.extend(fabric.Canvas.prototype,{_shouldGroup:function(e,t){var n=this.getActiveObject();return e.shiftKey&&(this.getActiveGroup()||n&&n!==t)&&this.selection},_handleGrouping:function(e,t){if(t===this.getActiveGroup()){t=this.findTarget(e,!0);if(!t||t.isType("group"))return}this.getActiveGroup()?this._updateActiveGroup(t,e):this._createActiveGroup(t,e),this._activeGroup&&this._activeGroup.saveCoords()},_updateActiveGroup:function(e,t){var n=this.getActiveGroup();if(n.contains(e)){n.removeWithUpdate(e),this._resetObjectTransform(n),e.set("active",!1);if(n.size()===1){this.discardActiveGroup(t),this.setActiveObject(n.item(0));return}}else n.addWithUpdate(e),this._resetObjectTransform(n);this.fire("selection:created",{target:n,e:t}),n.set("active",!0)},_createActiveGroup:function(e,t){if(this._activeObject&&e!==this._activeObject){var n=this._createGroup(e -);n.addWithUpdate(),this.setActiveGroup(n),this._activeObject=null,this.fire("selection:created",{target:n,e:t})}e.set("active",!0)},_createGroup:function(e){var t=this.getObjects(),n=t.indexOf(this._activeObject)1&&(t=new fabric.Group(t.reverse(),{canvas:this}),t.addWithUpdate(),this.setActiveGroup(t,e),t.saveCoords(),this.fire("selection:created",{target:t}),this.renderAll())},_collectObjects:function(){var n=[],r,i=this._groupSelector.ex,s=this._groupSelector.ey,o=i+this._groupSelector.left,u=s+this._groupSelector.top,a=new fabric.Point(e(i,o),e(s,u)),f=new fabric.Point(t(i,o),t(s,u)),l=i===o&&s===u;for(var c=this._objects.length;c--;){r=this._objects[c];if(!r||!r.selectable||!r.visible)continue;if(r.intersectsWithRect(a,f)||r.isContainedWithinRect(a,f)||r.containsPoint(a)||r.containsPoint(f)){r.set("active",!0),n.push(r);if(l)break}}return n},_maybeGroupObjects:function(e){this.selection&&this._groupSelector&&this._groupSelectedObjects(e);var t=this.getActiveGroup();t&&(t.setObjectsCoords().setCoords(),t.isMoving=!1,this.setCursor(this.defaultCursor)),this._groupSelector=null,this._currentTransform=null}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(e){e||(e={});var t=e.format||"png",n=e.quality||1,r=e.multiplier||1,i={left:e.left,top:e.top,width:e.width,height:e.height};return r!==1?this.__toDataURLWithMultiplier(t,n,i,r):this.__toDataURL(t,n,i)},__toDataURL:function(e,t,n){this.renderAll(!0);var r=this.upperCanvasEl||this.lowerCanvasEl,i=this.__getCroppedCanvas(r,n);e==="jpg"&&(e="jpeg");var s=fabric.StaticCanvas.supports("toDataURLWithQuality")?(i||r).toDataURL("image/"+e,t):(i||r).toDataURL("image/"+e);return this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),i&&(i=null),s},__getCroppedCanvas:function(e,t){var n,r,i="left"in t||"top"in t||"width"in t||"height"in t;return i&&(n=fabric.util.createCanvasElement(),r=n.getContext("2d"),n.width=t.width||this.width,n.height=t.height||this.height,r.drawImage(e,-t.left||0,-t.top||0)),n},__toDataURLWithMultiplier:function(e,t,n,r){var i=this.getWidth(),s=this.getHeight(),o=i*r,u=s*r,a=this.getActiveObject(),f=this.getActiveGroup(),l=this.contextTop||this.contextContainer;r>1&&this.setWidth(o).setHeight(u),l.scale(r,r),n.left&&(n.left*=r),n.top&&(n.top*=r),n.width?n.width*=r:r<1&&(n.width=o),n.height?n.height*=r:r<1&&(n.height=u),f?this._tempRemoveBordersControlsFromGroup(f):a&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var c=this.__toDataURL(e,t,n);return this.width=i,this.height=s,l.scale(1/r,1/r),this.setWidth(i).setHeight(s),f?this._restoreBordersControlsOnGroup(f):a&&this.setActiveObject&&this.setActiveObject(a),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),c},toDataURLWithMultiplier:function(e,t,n){return this.toDataURL({format:e,multiplier:t,quality:n})},_tempRemoveBordersControlsFromGroup:function(e){e.origHasControls=e.hasControls,e.origBorderColor=e.borderColor,e.hasControls=!0,e.borderColor="rgba(0,0,0,0)",e.forEachObject(function(e){e.origBorderColor=e.borderColor,e.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(e){e.hideControls=e.origHideControls,e.borderColor=e.origBorderColor,e.forEachObject(function(e){e.borderColor=e.origBorderColor,delete e.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(e,t,n){return this.loadFromJSON(e,t,n)},loadFromJSON:function(e,t,n){if(!e)return;var r=typeof e=="string"?JSON.parse(e):e;this.clear();var i=this;return this._enlivenObjects(r.objects,function(){i._setBgOverlay(r,t)},n),this},_setBgOverlay:function(e,t){var n=this,r={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!e.backgroundImage&&!e.overlayImage&&!e.background&&!e.overlay){t&&t();return}var i=function(){r.backgroundImage&&r.overlayImage&&r.backgroundColor&&r.overlayColor&&(n.renderAll(),t&&t())};this.__setBgOverlay("backgroundImage",e.backgroundImage,r,i),this.__setBgOverlay("overlayImage",e.overlayImage,r,i),this.__setBgOverlay("backgroundColor",e.background,r,i),this.__setBgOverlay("overlayColor",e.overlay,r,i),i()},__setBgOverlay:function(e,t,n,r){var i=this;if(!t){n[e]=!0;return}e==="backgroundImage"||e==="overlayImage"?fabric.Image.fromObject(t,function(t){i[e]=t,n[e]=!0,r&&r()}):this["set"+fabric.util.string.capitalize(e,!0)](t,function(){n[e]=!0,r&&r()})},_enlivenObjects:function(e,t,n){var r=this;if(!e||e.length===0){t&&t();return}var i=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(e,function(e){e.forEach(function(e,t){r.insertAt(e,t,!0)}),r.renderOnAddRemove=i,t&&t()},null,n)},_toDataURL:function(e,t){this.clone(function(n){t(n.toDataURL(e))})},_toDataURLWithMultiplier:function(e,t,n){this.clone(function(r){n(r.toDataURLWithMultiplier(e,t))})},clone:function(e,t){var n=JSON.stringify(this.toJSON(t));this.cloneWithoutData(function(t){t.loadFromJSON(n,function(){e&&e(t)})})},cloneWithoutData:function(e){var t=fabric.document.createElement("canvas");t.width=this.getWidth(),t.height=this.getHeight();var n=new fabric.Canvas(t);n.clipTo=this.clipTo,this.backgroundImage?(n.setBackgroundImage(this.backgroundImage.src,function(){n.renderAll(),e&&e(n)}),n.backgroundImageOpacity=this.backgroundImageOpacity,n.backgroundImageStretch=this.backgroundImageStretch):e&&e(n)}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.toFixed,i=t.util.string.capitalize,s=t.util.degreesToRadians,o=t.StaticCanvas.supports("setLineDash");if(t.Object)return;t.Object=t.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"nonzero",globalCompositeOperation:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,lockScalingFlip:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor".split(" "),initialize:function(e){e&&this.setOptions(e)},_initGradient:function(e){e.fill&&e.fill.colorStops&&!(e.fill instanceof t.Gradient)&&this.set("fill",new t.Gradient(e.fill))},_initPattern:function(e){e.fill&&e.fill.source&&!(e.fill instanceof t.Pattern)&&this.set("fill",new t.Pattern(e.fill)),e.stroke&&e.stroke.source&&!(e.stroke instanceof t.Pattern)&&this.set("stroke",new t.Pattern(e.stroke))},_initClipping:function(e){if(!e.clipTo||typeof e.clipTo!="string")return;var n=t.util.getFunctionBody(e.clipTo);typeof n!="undefined"&&(this.clipTo=new Function("ctx",n))},setOptions:function(e){for(var t in e)this.set(t,e[t]);this._initGradient(e),this._initPattern(e),this._initClipping(e)},transform:function(e,t){var n=t?this._getLeftTopCoords():this.getCenterPoint();e.translate(n.x,n.y),e.rotate(s(this.angle)),e.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(e){var n=t.Object.NUM_FRACTION_DIGITS,i={type:this.type,originX:this.originX,originY:this.originY,left:r(this.left,n),top:r(this.top,n),width:r(this.width,n),height:r(this.height,n),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:r(this.strokeWidth,n),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:r(this.strokeMiterLimit,n),scaleX:r(this.scaleX,n),scaleY:r(this.scaleY,n),angle:r(this.getAngle(),n),flipX:this.flipX,flipY:this.flipY,opacity:r(this.opacity,n),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor,fillRule:this.fillRule,globalCompositeOperation:this.globalCompositeOperation};return this.includeDefaultValues||(i=this._removeDefaultValues(i)),t.util.populateWithProperties(this,i,e),i},toDatalessObject:function(e){return this.toObject(e)},_removeDefaultValues:function(e){var n=t.util.getKlass(e.type).prototype,r=n.stateProperties;return r.forEach(function(t){e[t]===n[t]&&delete e[t]}),e},toString:function(){return"#"},get:function(e){return this[e]},_setObject:function(e){for(var t in e)this._set(t,e[t])},set:function(e,t){return typeof e=="object"?this._setObject(e):typeof t=="function"&&e!=="clipTo"?this._set(e,t(this.get(e))):this._set(e,t),this},_set:function(e,n){var i=e==="scaleX"||e==="scaleY";return i&&(n=this._constrainScale(n)),e==="scaleX"&&n<0?(this.flipX=!this.flipX,n*=-1):e==="scaleY"&&n<0?(this.flipY=!this.flipY,n*=-1):e==="width"||e==="height"?this.minScaleLimit=r(Math.min(.1,1/Math.max(this.width,this.height)),2):e==="shadow"&&n&&!(n instanceof t.Shadow)&&(n=new t.Shadow(n)),this[e]=n,this},toggle:function(e){var t=this.get(e);return typeof t=="boolean"&&this.set(e,!t),this},setSourcePath:function(e){return this.sourcePath=e,this},getViewportTransform:function(){return this.canvas&&this.canvas.viewportTransform?this.canvas.viewportTransform:[1,0,0,1,0,0]},render:function(e,n){if(this.width===0&&this.height===0||!this.visible)return;e.save(),this._setupCompositeOperation(e),n||this.transform(e),this._setStrokeStyles(e),this._setFillStyles(e),this.transformMatrix&&e.transform.apply(e,this.transformMatrix),this._setOpacity(e),this._setShadow(e),this.clipTo&&t.util.clipContext(this,e),this._render(e,n),this.clipTo&&e.restore(),this._removeShadow(e),this._restoreCompositeOperation(e),e.restore()},_setOpacity:function(e){this.group&&this.group._setOpacity(e),e.globalAlpha*=this.opacity},_setStrokeStyles:function(e){this.stroke&&(e.lineWidth=this.strokeWidth,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin,e.miterLimit=this.strokeMiterLimit,e.strokeStyle=this.stroke.toLive?this.stroke.toLive(e,this):this.stroke)},_setFillStyles:function(e){this.fill&&(e.fillStyle=this.fill.toLive?this.fill.toLive(e,this):this.fill)},_renderControls:function(e,n){if(!this.active||n)return;var r=this.getViewportTransform();e.save();var i;this.group&&(i=t.util.transformPoint(this.group.getCenterPoint(),r),e.translate(i.x,i.y),e.rotate(s(this.group.angle))),i=t.util.transformPoint(this.getCenterPoint(),r,null!=this.group),this.group&&(i.x*=this.group.scaleX,i.y*=this.group.scaleY),e.translate(i.x,i.y),e.rotate(s(this.angle)),this.drawBorders(e),this.drawControls(e),e.restore()},_setShadow:function(e){if(!this.shadow)return;var t=this.canvas&&this.canvas.viewportTransform[0]||1,n=this.canvas&&this.canvas.viewportTransform[3]||1;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur*(t+n)*(this.scaleX+this.scaleY)/4,e.shadowOffsetX=this.shadow.offsetX*t*this.scaleX,e.shadowOffsetY=this.shadow.offsetY*n*this.scaleY},_removeShadow:function(e){if(!this.shadow)return;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0},_renderFill:function(e){if(!this.fill)return;e.save();if(this.fill.gradientTransform){var t=this.fill.gradientTransform;e.transform.apply(e,t)}this.fill.toLive&&e.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0),this.fillRule==="evenodd"?e.fill("evenodd"):e.fill(),e.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(e)},_renderStroke:function(e){if(!this.stroke||this.strokeWidth===0)return;e.save();if(this.strokeDashArray)1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),o?(e.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(e)):this._renderDashedStroke&&this._renderDashedStroke(e),e.stroke();else{if(this.stroke.gradientTransform){var t=this.stroke.gradientTransform;e.transform.apply(e,t)}this._stroke?this._stroke(e):e.stroke()}this._removeShadow(e),e.restore()},clone:function(e,n){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(n),e):new t.Object(this.toObject(n))},cloneAsImage:function(e){var n=this.toDataURL();return t.util.loadImage(n,function(n){e&&e(new t.Image(n))}),this},toDataURL:function(e){e||(e={});var n=t.util.createCanvasElement(),r=this.getBoundingRect();n.width=r.width,n.height=r.height,t.util.wrapElement(n,"div");var i=new t.StaticCanvas(n);e.format==="jpg"&&(e.format="jpeg"),e.format==="jpeg"&&(i.backgroundColor="#fff");var s={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new t.Point(n.width/2,n.height/2),"center","center");var o=this.canvas;i.add(this);var u=i.toDataURL(e);return this.set(s).setCoords(),this.canvas=o,i.dispose(),i=null,u},isType:function(e){return this.type===e},complexity:function(){return 0},toJSON:function(e){return this.toObject(e)},setGradient:function(e,n){n||(n={});var r={colorStops:[]};r.type=n.type||(n.r1||n.r2?"radial":"linear"),r.coords={x1:n.x1,y1:n.y1,x2:n.x2,y2:n.y2};if(n.r1||n.r2)r.coords.r1=n.r1,r.coords.r2=n.r2;for(var i in n.colorStops){var s=new t.Color(n.colorStops[i]);r.colorStops.push({offset:i,color:s.toRgb(),opacity:s.getAlpha()})}return this.set(e,t.Gradient.forObject(this,r))},setPatternFill:function(e){return this.set("fill",new t.Pattern(e))},setShadow:function(e){return this.set("shadow",e?new t.Shadow(e):null)},setColor:function(e){return this.set("fill",e),this},setAngle:function(e){var t=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;return t&&this._setOriginToCenter(),this.set("angle",e),t&&this._resetOrigin(),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this),this},getLocalPointer:function(e,t){t=t||this.canvas.getPointer(e);var n=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:t.x-n.x,y:t.y-n.y}},_setupCompositeOperation:function(e){this.globalCompositeOperation&&(this._prevGlobalCompositeOperation=e.globalCompositeOperation,e.globalCompositeOperation=this.globalCompositeOperation)},_restoreCompositeOperation:function(e){this.globalCompositeOperation&&this._prevGlobalCompositeOperation&&(e.globalCompositeOperation=this._prevGlobalCompositeOperation)}}),t.util.createAccessors(t.Object),t.Object.prototype.rotate=t.Object.prototype.setAngle,n(t.Object.prototype,t.Observable),t.Object.NUM_FRACTION_DIGITS=2,t.Object.__uid=0}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x+(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x-(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y+(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y-(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},translateToOriginPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x-(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x+(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y-(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y+(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},getCenterPoint:function(){var e=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(e,this.originX,this.originY)},getPointByOrigin:function(e,t){var n=this.getCenterPoint();return this.translateToOriginPoint(n,e,t)},toLocalPoint:function(t,n,r){var i=this.getCenterPoint(),s=this.stroke?this.strokeWidth:0,o,u;return n&&r?(n==="left"?o=i.x-(this.getWidth()+s*this.scaleX)/2:n==="right"?o=i.x+(this.getWidth()+s*this.scaleX)/2:o=i.x,r==="top"?u=i.y-(this.getHeight()+s*this.scaleY)/2:r==="bottom"?u=i.y+(this.getHeight()+s*this.scaleY)/2:u=i.y):(o=this.left,u=this.top),fabric.util.rotatePoint(new fabric.Point(t.x,t.y),i,-e(this.angle)).subtractEquals(new fabric.Point(o,u))},setPositionByOrigin:function(e,t,n){var r=this.translateToCenterPoint(e,t,n),i=this.translateToOriginPoint(r,this.originX,this.originY);this.set("left",i.x),this.set("top",i.y)},adjustPosition:function(t){var n=e(this.angle),r=this.getWidth()/2,i=Math.cos(n)*r,s=Math.sin(n)*r,o=this.getWidth(),u=Math.cos(n)*o,a=Math.sin(n)*o;this.originX==="center"&&t==="left"||this.originX==="right"&&t==="center"?(this.left-=i,this.top-=s):this.originX==="left"&&t==="center"||this.originX==="center"&&t==="right"?(this.left+=i,this.top+=s):this.originX==="left"&&t==="right"?(this.left+=u,this.top+=a):this.originX==="right"&&t==="left"&&(this.left-=u,this.top-=a),this.setCoords(),this.originX=t},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var e=this.getCenterPoint();this.originX="center",this.originY="center",this.left=e.x,this.top=e.y},_resetOrigin:function(){var e=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=e.x,this.top=e.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(e,t){var n=this.oCoords,r=new fabric.Point(n.tl.x,n.tl.y),i=new fabric.Point(n.tr.x,n.tr.y),s=new fabric.Point(n.bl.x,n.bl.y),o=new fabric.Point(n.br.x,n.br.y),u=fabric.Intersection.intersectPolygonRectangle([r,i,o,s],e,t);return u.status==="Intersection"},intersectsWithObject:function(e){function t(e){return{tl:new fabric.Point(e.tl.x,e.tl.y),tr:new fabric.Point(e.tr.x,e.tr.y),bl:new fabric.Point(e.bl.x,e.bl.y),br:new fabric.Point(e.br.x,e.br.y)}}var n=t(this.oCoords),r=t(e.oCoords),i=fabric.Intersection.intersectPolygonPolygon([n.tl,n.tr,n.br,n.bl],[r.tl,r.tr,r.br,r.bl]);return i.status==="Intersection"},isContainedWithinObject:function(e){var t=e.getBoundingRect(),n=new fabric.Point(t.left,t.top),r=new fabric.Point(t.left+t.width,t.top+t.height);return this.isContainedWithinRect(n,r)},isContainedWithinRect:function(e,t){var n=this.getBoundingRect();return n.left>=e.x&&n.left+n.width<=t.x&&n.top>=e.y&&n.top+n.height<=t.y},containsPoint:function(e){var t=this._getImageLines(this.oCoords),n=this._findCrossPoints(e,t);return n!==0&&n%2===1},_getImageLines:function(e){return{topline:{o:e.tl,d:e.tr},rightline:{o:e.tr,d:e.br},bottomline:{o:e.br,d:e.bl},leftline:{o:e.bl,d:e.tl}}},_findCrossPoints:function(e,t){var n,r,i,s,o,u,a=0,f;for(var l in t){f=t[l];if(f.o.y=e.y&&f.d.y>=e.y)continue;f.o.x===f.d.x&&f.o.x>=e.x?(o=f.o.x,u=e.y):(n=0,r=(f.d.y-f.o.y)/(f.d.x-f.o.x),i=e.y-n*e.x,s=f.o.y-r*f.o.x,o=-(i-s)/(n-r),u=i+n*o),o>=e.x&&(a+=1);if(a===2)break}return a},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var e=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],t=fabric.util.array.min(e),n=fabric.util.array.max(e),r=Math.abs(t-n),i=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],s=fabric.util.array.min(i),o=fabric.util.array.max(i),u=Math.abs(s-o);return{left:t,top:s,width:r,height:u}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(e){return Math.abs(e)\n'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Line.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),t.Line.fromElement=function(e,r){var i=t.parseAttributes(e,t.Line.ATTRIBUTE_NAMES),s=[i.x1||0,i.y1||0,i.x2||0,i.y2||0];return new t.Line(s,n(i,r))},t.Line.fromObject=function(e){var n=[e.x1,e.y1,e.x2,e.y2];return new t.Line(n,e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return"radius"in e&&e.radius>=0}var t=e.fabric||(e.fabric={}),n=Math.PI,r=t.util.object.extend;if(t.Circle){t.warn("fabric.Circle is already defined.");return}t.Circle=t.util.createClass(t.Object,{type:"circle",radius:0,startAngle:0,endAngle:n*2,initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("radius",e.radius||0),this.startAngle=e.startAngle||this.startAngle,this.endAngle=e.endAngle||this.endAngle},_set:function(e,t){return this.callSuper("_set",e,t),e==="radius"&&this.setRadius(t),this},toObject:function(e){return r(this.callSuper("toObject",e),{radius:this.get("radius"),startAngle:this.startAngle,endAngle:this.endAngle})},toSVG:function(e){var t=this._createBaseSVGMarkup(),r=0,i=0,s=(this.endAngle-this.startAngle)%(2*n);if(s===0)this.group&&this.group.type==="path-group"&&(r=this.left+this.radius,i=this.top+this.radius),t.push("\n');else{var o=Math.cos(this.startAngle)*this.radius,u=Math.sin(this.startAngle)*this.radius,a=Math.cos(this.endAngle)*this.radius,f=Math.sin(this.endAngle)*this.radius,l=s>n?"1":"0";t.push('\n')}return e?e(t.join("")):t.join("")},_render:function(e,t){e.beginPath(),e.arc(t?this.left+this.radius:0,t?this.top+this.radius:0,this.radius,this.startAngle,this.endAngle,!1),this._renderFill(e),this._renderStroke(e)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(e){this.radius=e,this.set("width",e*2).set("height",e*2)},complexity:function(){return 1}}),t.Circle.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),t.Circle.fromElement=function(e,n){n||(n={});var s=t.parseAttributes(e,t.Circle.ATTRIBUTE_NAMES);if(!i(s))throw new Error("value of `r` attribute is required and can not be negative");s.left=s.left||0,s.top=s.top||0;var o=new t.Circle(r(s,n));return o.left-=o.radius,o.top-=o.radius,o},t.Circle.fromObject=function(e){return new t.Circle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Triangle){t.warn("fabric.Triangle is already defined");return}t.Triangle=t.util.createClass(t.Object,{type:"triangle",initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("width",e.width||100).set("height",e.height||100)},_render:function(e){var t=this.width/2,n=this.height/2;e.beginPath(),e.moveTo(-t,n),e.lineTo(0,-n),e.lineTo(t,n),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=this.width/2,r=this.height/2;e.beginPath(),t.util.drawDashedLine(e,-n,r,0,-r,this.strokeDashArray),t.util.drawDashedLine(e,0,-r,n,r,this.strokeDashArray),t.util.drawDashedLine(e,n,r,-n,r,this.strokeDashArray),e.closePath()},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this.width/2,r=this.height/2,i=[-n+" "+r,"0 "+ -r,n+" "+r].join(",");return t.push("'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Triangle.fromObject=function(e){return new t.Triangle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Ellipse){t.warn("fabric.Ellipse is already defined.");return}t.Ellipse=t.util.createClass(t.Object,{type:"ellipse",rx:0,ry:0,initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("rx",e.rx||0),this.set("ry",e.ry||0)},_set:function(e,t){this.callSuper("_set",e,t);switch(e){case"rx":this.rx=t,this.set("width",t*2);break;case"ry":this.ry=t,this.set("height",t*2)}return this},getRx:function(){return this.get("rx")*this.get("scaleX")},getRy:function(){return this.get("ry")*this.get("scaleY")},toObject:function(e){return r(this.callSuper("toObject",e),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=0,r=0;return this.group&&this.group.type==="path-group"&&(n=this.left+this.rx,r=this.top+this.ry),t.push("\n'),e?e(t.join("")):t.join("")},_render:function(e,t){e.beginPath(),e.save(),e.transform(1,0,0,this.ry/this.rx,0,0),e.arc(t?this.left+this.rx:0,t?(this.top+this.ry)*this.rx/this.ry:0,this.rx,0,n,!1),e.restore(),this._renderFill(e),this._renderStroke(e)},complexity:function(){return 1}}),t.Ellipse.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),t.Ellipse.fromElement=function(e,n){n||(n={});var i=t.parseAttributes(e,t.Ellipse.ATTRIBUTE_NAMES);i.left=i.left||0,i.top=i.top||0;var s=new t.Ellipse(r(i,n));return s.top-=s.ry,s.left-=s.rx,s},t.Ellipse.fromObject=function(e){return new t.Ellipse(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;if(t.Rect){console.warn("fabric.Rect is already defined");return}var r=t.Object.prototype.stateProperties.concat();r.push("rx","ry","x","y"),t.Rect=t.util.createClass(t.Object,{stateProperties:r,type:"rect",rx:0,ry:0,strokeDashArray:null,initialize:function(e){e=e||{},this.callSuper("initialize",e),this._initRxRy()},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(e,t){if(this.width===1&&this.height===1){e.fillRect(0,0,1,1);return}var n=this.rx?Math.min(this.rx,this.width/2):0,r=this.ry?Math.min(this.ry,this.height/2):0,i=this.width,s=this.height,o=t?this.left:-this.width/2,u=t?this.top:-this.height/2,a=n!==0||r!==0,f=.4477152502;e.beginPath(),e.moveTo(o+n,u),e.lineTo(o+i-n,u),a&&e.bezierCurveTo(o+i-f*n,u,o+i,u+f*r,o+i,u+r),e.lineTo(o+i,u+s-r),a&&e.bezierCurveTo(o+i,u+s-f*r,o+i-f*n,u+s,o+i-n,u+s),e.lineTo(o+n,u+s),a&&e.bezierCurveTo(o+f*n,u+s,o,u+s-f*r,o,u+s-r),e.lineTo(o,u+r),a&&e.bezierCurveTo(o,u+f*r,o+f*n,u,o+n,u),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=-this.width/2,r=-this.height/2,i=this.width,s=this.height;e.beginPath(),t.util.drawDashedLine(e,n,r,n+i,r,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r,n+i,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r+s,n,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n,r+s,n,r,this.strokeDashArray),e.closePath()},toObject:function(e){var t=n(this.callSuper("toObject",e),{rx:this.get("rx")||0,ry:this.get("ry")||0});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this.left,r=this.top;if(!this.group||this.group.type!=="path-group")n=-this.width/2,r=-this.height/2;return t.push("\n'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Rect.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),t.Rect.fromElement=function(e,r){if(!e)return null;r=r||{};var i=t.parseAttributes(e,t.Rect.ATTRIBUTE_NAMES);i.left=i.left||0,i.top=i.top||0;var s=new t.Rect(n(r?t.util.object.clone(r):{},i));return s.visible=s.width>0&&s.height>0,s},t.Rect.fromObject=function(e){return new t.Rect(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Polyline){t.warn("fabric.Polyline is already defined");return}t.Polyline=t.util.createClass(t.Object,{type:"polyline",points:null,minX:0,minY:0,initialize:function(e,n){return t.Polygon.prototype.initialize.call(this,e,n)},_calcDimensions:function(){return t.Polygon.prototype._calcDimensions.call(this)},_applyPointOffset:function(){return t.Polygon.prototype._applyPointOffset.call(this)},toObject:function(e){return t.Polygon.prototype.toObject.call(this,e)},toSVG:function(e){return t.Polygon.prototype.toSVG.call(this,e)},_render:function(e){if(!t.Polygon.prototype.commonRender.call(this,e))return;this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n,r;e.beginPath();for(var i=0,s=this.points.length;i\n'),e?e(n.join("")):n.join("")},_render:function(e){if(!this.commonRender(e))return;this._renderFill(e);if(this.stroke||this.strokeDashArray)e.closePath(),this._renderStroke(e)},commonRender:function(e){var t,n=this.points.length;if(!n||isNaN(this.points[n-1].y))return!1;e.beginPath(),this._applyPointOffset&&((!this.group||this.group.type!=="path-group")&&this._applyPointOffset(),this._applyPointOffset=null),e.moveTo(this.points[0].x,this.points[0].y);for(var r=0;r"},toObject:function(e){var t=i(this.callSuper("toObject",e),{path:this.path.map(function(e){return e.slice()}),pathOffset:this.pathOffset});return this.sourcePath&&(t.sourcePath=this.sourcePath),this.transformMatrix&&(t.transformMatrix=this.transformMatrix),t},toDatalessObject:function(e){var t=this.toObject(e);return this.sourcePath&&(t.path=this.sourcePath),delete t.sourcePath,t},toSVG:function(e){var t=[],n=this._createBaseSVGMarkup(),r="";for(var i=0,s=this.path.length;i\n"),e?e(n.join("")):n.join("")},complexity:function(){return this.path.length},_parsePath:function(){var e=[],t=[],n,r,i=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,s,o;for(var f=0,l,c=this.path.length;fv)for(var g=1,y=l.length;g\n"];for(var s=0,o=t.length;s\n"),e?e(i.join("")):i.join("")},toString:function(){return"#"},isSameColor:function(){var e=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(t){return(t.get("fill")||"").toLowerCase()===e})},complexity:function(){return this.paths.reduce(function(e,t){return e+(t&&t.complexity?t.complexity():0)},0)},getObjects:function(){return this.paths}}),t.PathGroup.fromObject=function(e,n){typeof e.paths=="string"?t.loadSVGFromURL(e.paths,function(r){var i=e.paths;delete e.paths;var s=t.util.groupSVGElements(r,e,i);n(s)}):t.util.enlivenObjects(e.paths,function(r){delete e.paths,n(new t.PathGroup(r,e))})},t.PathGroup.async=!0}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.array.min,i=t.util.array.max,s=t.util.array.invoke;if(t.Group)return;var o={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};t.Group=t.util.createClass(t.Object,t.Collection,{type:"group",initialize:function(e,t){t=t||{},this._objects=e||[];for(var n=this._objects.length;n--;)this._objects[n].group=this;this.originalState={},this.callSuper("initialize"),t.originX&&(this.originX=t.originX),t.originY&&(this.originY=t.originY),this._calcBounds(),this._updateObjectsCoords(),this.callSuper("initialize",t),this.setCoords(),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(e){var t=e.getLeft(),n=e.getTop(),r=this.getCenterPoint();e.set({originalLeft:t,originalTop:n,left:t-r.x,top:n-r.y}),e.setCoords(),e.__origHasControls=e.hasControls,e.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(e){return this._restoreObjectsState(),e&&(this._objects.push(e),e.group=this),this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(e){e.set("active",!0),e.group=this},removeWithUpdate:function(e){return this._moveFlippedObject(e),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(e),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(e){e.group=this},_onObjectRemoved:function(e){delete e.group,e.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(e,t){if(e in this.delegatedProperties){var n=this._objects.length;while(n--)this._objects[n].set(e,t)}this.callSuper("_set",e,t)},toObject:function(e){return n(this.callSuper("toObject",e),{objects:s(this._objects,"toObject",e)})},render:function(e){if(!this.visible)return;e.save(),this.clipTo&&t.util.clipContext(this,e),this.transform(e);for(var n=0,r=this._objects.length;n\n'];for(var n=0,r=this._objects.length;n\n"),e?e(t.join("")):t.join("")},get:function(e){if(e in o){if(this[e])return this[e];for(var t=0,n=this._objects.length;t\n','\n");if(this.stroke||this.strokeDashArray){var s=this.fill;this.fill=null,t.push("\n'),this.fill=s}return t.push("\n"),e?e(t.join("")):t.join("")},getSrc:function(){if(this.getElement())return this.getElement().src||this.getElement()._src},setSrc:function(e,t,n){fabric.util.loadImage(e,function(e){return this.setElement(e,t,n)},this,n&&n.crossOrigin)},toString:function(){return'#'},clone:function(e,t){this.constructor.fromObject(this.toObject(t),e)},applyFilters:function(e,t,n,r){t=t||this.filters,n=n||this._originalElement;if(!n)return;var i=n,s=fabric.util.createCanvasElement(),o=fabric.util.createImage(),u=this;return s.width=i.width,s.height=i.height,s.getContext("2d").drawImage(i,0,0,i.width,i.height),t.length===0?(this._element=n,e&&e(),s):(t.forEach(function(e){e&&e.applyTo(s,e.scaleX||u.scaleX,e.scaleY||u.scaleY),!r&&e&&e.type==="Resize"&&(u.width*=e.scaleX,u.height*=e.scaleY)}),o.width=s.width,o.height=s.height,fabric.isLikelyNode?(o.src=s.toBuffer(undefined,fabric.Image.pngCompression),u._element=o,!r&&(u._filteredEl=o),e&&e()):(o.onload=function(){u._element=o,!r&&(u._filteredEl=o),e&&e(),o.onload=s=i=null},o.src=s.toDataURL("image/png")),s)},_render:function(e,t){var n,r,i=this._findMargins(),s;n=t?this.left:-this.width/2,r=t?this.top:-this.height/2,this.meetOrSlice==="slice"&&(e.beginPath(),e.rect(n,r,this.width,this.height),e.clip()),this.isMoving===!1&&this.resizeFilters.length&&this._needsResize()?(this._lastScaleX=this.scaleX,this._lastScaleY=this.scaleY,s=this.applyFilters(null,this.resizeFilters,this._filteredEl||this._originalElement,!0)):s=this._element,s&&e.drawImage(s,n+i.marginX,r+i.marginY,i.width,i.height),this._renderStroke(e)},_needsResize:function(){return this.scaleX!==this._lastScaleX||this.scaleY!==this._lastScaleY},_findMargins:function(){var e=this.width,t=this.height,n,r,i=0,s=0;if(this.alignX!=="none"||this.alignY!=="none")n=[this.width/this._element.width,this.height/this._element.height],r=this.meetOrSlice==="meet"?Math.min.apply(null,n):Math.max.apply(null,n),e=this._element.width*r,t=this._element.height*r,this.alignX==="Mid"&&(i=(this.width-e)/2),this.alignX==="Max"&&(i=this.width-e),this.alignY==="Mid"&&(s=(this.height-t)/2),this.alignY==="Max"&&(s=this.height-t);return{width:e,height:t,marginX:i,marginY:s}},_resetWidthHeight:function(){var e=this.getElement();this.set("width",e.width),this.set("height",e.height)},_initElement:function(e){this.setElement(fabric.util.getById(e)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(e){e||(e={}),this.setOptions(e),this._setWidthHeight(e),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(e,t){e.filters&&e.filters.length?fabric.util.enlivenObjects(e.filters,function(e){t&&t(e)},"fabric.Image.filters"):t&&t()},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement()?this.getElement().width||0:0,this.height="height"in e?e.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){fabric.Image.prototype._initFilters.call(e,e,function(r){e.filters=r||[];var i=new fabric.Image(n,e);t&&t(i)})},null,e.crossOrigin)},fabric.Image.fromURL=function(e,t,n){fabric.util.loadImage(e,function(e){t&&t(new fabric.Image(e,n))},null,n&&n.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height preserveAspectRatio xlink:href" -.split(" ")),fabric.Image.fromElement=function(e,n,r){var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES),s="xMidYMid",o="meet",u,a,f;i.preserveAspectRatio&&(f=i.preserveAspectRatio.split(" ")),f&&f.length&&(o=f.pop(),o!=="meet"&&o!=="slice"?(s=o,o="meet"):f.length&&(s=f.pop())),u=s!=="none"?s.slice(1,4):"none",a=s!=="none"?s.slice(5,8):"none",i.alignX=u,i.alignY=a,i.meetOrSlice=o,fabric.Image.fromURL(i["xlink:href"],n,t(r?fabric.util.object.clone(r):{},i))},fabric.Image.async=!0,fabric.Image.pngCompression=1}(typeof exports!="undefined"?exports:this),fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var e=this.getAngle()%360;return e>0?Math.round((e-1)/90)*90:Math.round(e/90)*90},straighten:function(){return this.setAngle(this._getAngleValueForStraighten()),this},fxStraighten:function(e){e=e||{};var t=function(){},n=e.onComplete||t,r=e.onChange||t,i=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(e){i.setAngle(e),r()},onComplete:function(){i.setCoords(),n()},onStart:function(){i.set("active",!1)}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(e){return e.straighten(),this.renderAll(),this},fxStraightenObject:function(e){return e.fxStraighten({onChange:this.renderAll.bind(this)}),this}}),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",initialize:function(e){e&&this.setOptions(e)},setOptions:function(e){for(var t in e)this[t]=e[t]},toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.Brightness=t.util.createClass(t.Image.filters.BaseFilter,{type:"Brightness",initialize:function(e){e=e||{},this.brightness=e.brightness||0},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.brightness;for(var s=0,o=r.length;sa||C<0||C>u)continue;var k=(N*u+C)*4,L=t[x*i+T];b+=o[k]*L,w+=o[k+1]*L,E+=o[k+2]*L,S+=o[k+3]*L}h[y]=b,h[y+1]=w,h[y+2]=E,h[y+3]=S+p*(255-S)}n.putImageData(c,0,0)},toObject:function(){return n(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),t.Image.filters.Convolute.fromObject=function(e){return new t.Image.filters.Convolute(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.GradientTransparency=t.util.createClass(t.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(e){e=e||{},this.threshold=e.threshold||100},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.threshold,s=r.length;for(var o=0,u=r.length;o-1?e.channel:0},applyTo:function(e){if(!this.mask)return;var n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=r.data,s=this.mask.getElement(),o=t.util.createCanvasElement(),u=this.channel,a,f=r.width*r.height*4;o.width=s.width,o.height=s.height,o.getContext("2d").drawImage(s,0,0,s.width,s.height);var l=o.getContext("2d").getImageData(0,0,s.width,s.height),c=l.data;for(a=0;ao&&f>o&&l>o&&u(a-f)n&&(l=2,h=-1),u>i&&(c=2,p=-1),f=a.getImageData(0,0,n,i),e.width=o(s,n),e.height=o(u,i),a.putImageData(f,0,0);while(!d||!v)n=m,i=g,s*he)return 0;t*=Math.PI;if(s(t)<1e-16)return 1;var n=t/e;return a(t)*a(n)/t/n}}function h(e){var a,f,c,p,d,L,A,O,M,_,D;C.x=(e+.5)*b,k.x=r(C.x);for(a=0;a=t)continue;_=r(1e3*s(f-C.x)),N[_]||(N[_]={});for(var P=k.y-T;P<=k.y+T;P++){if(P<0||P>=o)continue;D=r(1e3*s(P-C.y)),N[_][D]||(N[_][D]=y(i(n(_*E,2)+n(D*S,2))/1e3)),c=N[_][D],c>0&&(p=(P*t+f)*4,d+=c,L+=c*m[p],A+=c*m[p+1],O+=c*m[p+2],M+=c*m[p+3])}}p=(a*u+e)*4,g[p]=L/d,g[p+1]=A/d,g[p+2]=O/d,g[p+3]=M/d}return++e1&&H<-1)continue;E=2*H*H*H-3*H*H+1,E>0&&(P=4*(D+A*t),k+=E*v[P+3],x+=E,v[P+3]<255&&(E=E*v[P+3]/250),T+=E*v[P],N+=E*v[P+1],C+=E*v[P+2],S+=E)}}g[w]=T/S,g[w+1]=N/S,g[w+2]=C/S,g[w+3]=k/x}return m},toObject:function(){return{type:this.type,scaleX:this.scaleX,scaley:this.scaleY,resizeType:this.resizeType,lanczosLobes:this.lanczosLobes}}}),t.Image.filters.Resize.fromObject=function(){return new t.Image.filters.Resize}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.object.clone,i=t.util.toFixed,s=t.StaticCanvas.supports("setLineDash");if(t.Text){t.warn("fabric.Text is already defined");return}var o=t.Object.prototype.stateProperties.concat();o.push("fontFamily","fontWeight","fontSize","text","textDecoration","textAlign","fontStyle","lineHeight","textBackgroundColor"),t.Text=t.util.createClass(t.Object,{_dimensionAffectingProps:{fontSize:!0,fontWeight:!0,fontFamily:!0,fontStyle:!0,lineHeight:!0,stroke:!0,strokeWidth:!0,text:!0,textAlign:!0},_reNewline:/\r?\n/,type:"text",fontSize:40,fontWeight:"normal",fontFamily:"Times New Roman",textDecoration:"",textAlign:"left",fontStyle:"",lineHeight:1.16,textBackgroundColor:"",stateProperties:o,stroke:null,shadow:null,_fontSizeFraction:.25,_fontSizeMult:1.13,initialize:function(e,t){t=t||{},this.text=e,this.__skipDimension=!0,this.setOptions(t),this.__skipDimension=!1,this._initDimensions()},_initDimensions:function(e){if(this.__skipDimension)return;e||(e=t.util.createCanvasElement().getContext("2d"),this._setTextStyles(e)),this._textLines=this.text.split(this._reNewline),this._clearCache();var n=this.textAlign;this.textAlign="left",this.width=this._getTextWidth(e),this.textAlign=n,this.height=this._getTextHeight(e)},toString:function(){return"#'},_render:function(e){this.clipTo&&t.util.clipContext(this,e),this._renderTextBackground(e),this._renderText(e),this._renderTextDecoration(e),this.clipTo&&e.restore()},_renderText:function(e){e.save(),this._translateForTextAlign(e),this._setOpacity(e),this._setShadow(e),this._setupCompositeOperation(e),this._renderTextFill(e),this._renderTextStroke(e),this._restoreCompositeOperation(e),this._removeShadow(e),e.restore()},_translateForTextAlign:function(e){this.textAlign!=="left"&&this.textAlign!=="justify"&&e.translate(this.textAlign==="center"?this.width/2:this.width,0)},_setTextStyles:function(e){e.textBaseline="alphabetic",this.skipTextAlign||(e.textAlign=this.textAlign),e.font=this._getFontDeclaration()},_getTextHeight:function(){return this._textLines.length*this._getHeightOfLine()},_getTextWidth:function(e){var t=this._getLineWidth(e,0);for(var n=1,r=this._textLines.length;nt&&(t=i)}return t},_renderChars:function(e,t,n,r,i){t[e](n,r,i)},_renderTextLine:function(e,t,n,r,i,s){i-=this.fontSize*this._fontSizeFraction;if(this.textAlign!=="justify"){this._renderChars(e,t,n,r,i,s);return}var o=this._getLineWidth(t,s),u=this.width;if(u>=o){var a=n.split(/\s+/),f=this._getWidthOfWords(t,n,s),l=u-f,c=a.length-1,h=l/c,p=0;for(var d=0,v=a.length;d-1&&r.push(.85),this.textDecoration.indexOf("line-through")>-1&&r.push(.43),this.textDecoration.indexOf("overline")>-1&&r.push(-0.12),r.length>0&&i(r)},_getFontDeclaration:function(){return[t.isLikelyNode?this.fontWeight:this.fontStyle,t.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(e,t){if(!this.visible)return;e.save(),this._setTextStyles(e),this._shouldClearCache()&&this._initDimensions(e),t||this.transform(e),this._setStrokeStyles(e),this._setFillStyles(e),this.transformMatrix&&e.transform.apply(e,this.transformMatrix),this.group&&this.group.type==="path-group"&&e.translate(this.left,this.top),this._render(e),e.restore()},toObject:function(e){var t=n(this.callSuper("toObject",e),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,textBackgroundColor:this.textBackgroundColor});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this._getSVGLeftTopOffsets(this.ctx),r=this._getSVGTextAndBg(n.textTop,n.textLeft);return this._wrapSVGTextAndBg(t,r),e?e(t.join("")):t.join("")},_getSVGLeftTopOffsets:function(e){var t=this._getHeightOfLine(e,0),n=-this.width/2,r=0;return{textLeft:n+(this.group&&this.group.type==="path-group"?this.left:0),textTop:r+(this.group&&this.group.type==="path-group"?-this.top:0),lineTop:t}},_wrapSVGTextAndBg:function(e,t){e.push(' \n',t.textBgRects.join("")," ',t.textSpans.join(""),"\n"," \n")},_getSVGTextAndBg:function(e,t){var n=[],r=[],i=0;this._setSVGBg(r);for(var s=0,o=this._textLines.length;s",t.util.string.escapeXml(this._textLines[e]),"")},_setSVGTextLineBg:function(e,t,n,r,s){e.push(" \n')},_setSVGBg:function(e){this.backgroundColor&&e.push(" \n')},_getFillAttributes:function(e){var n=e&&typeof e=="string"?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},_set:function(e,t){this.callSuper("_set",e,t),e in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),t.Text.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),t.Text.DEFAULT_SVG_FONT_SIZE=16,t.Text.fromElement=function(e,n){if(!e)return null;var r=t.parseAttributes(e,t.Text.ATTRIBUTE_NAMES);n=t.util.object.extend(n?t.util.object.clone(n):{},r),n.top=n.top||0,n.left=n.left||0,"dx"in r&&(n.left+=r.dx),"dy"in r&&(n.top+=r.dy),"fontSize"in n||(n.fontSize=t.Text.DEFAULT_SVG_FONT_SIZE),n.originX||(n.originX="left");var i=e.textContent.replace(/^\s+|\s+$|\n+/g,"").replace(/\s+/g," "),s=new t.Text(i,n),o=0;return s.originX==="left"&&(o=s.getWidth()/2),s.originX==="right"&&(o=-s.getWidth()/2),s.set({left:s.getLeft()+o,top:s.getTop()-s.getHeight()/2+s.fontSize*(.18+s._fontSizeFraction)}),s},t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},t.util.createAccessors(t.Text)}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:!0,_skipFillStrokeCheck:!1,_reSpace:/\s|\n/,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,_charWidthsCache:{},initialize:function(e,t){this.styles=t?t.styles||{}:{},this.callSuper("initialize",e,t),this.initBehavior()},_clearCache:function(){this.callSuper("_clearCache"),this.__maxFontHeights=[],this.__widthOfSpace=[]},isEmptyStyles:function(){if(!this.styles)return!0;var e=this.styles;for(var t in e)for(var n in e[t])for(var r in e[t][n])return!1;return!0},setSelectionStart:function(e){e=Math.max(e,0),this.selectionStart!==e&&(this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this}),this.selectionStart=e),this._updateTextarea()},setSelectionEnd:function(e){e=Math.min(e,this.text.length),this.selectionEnd!==e&&(this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this}),this.selectionEnd=e),this._updateTextarea()},getSelectionStyles:function(e,t){if(arguments.length===2){var n=[];for(var r=e;r=r.charIndex&&(u!==o||hs&&u0||this.skipFillStrokeCheck)&&this.callSuper("_renderChars",e,t,n,r,i)},_renderChar:function(e,t,n,r,i,s,o,u){var a,f,l,c=this._fontSizeFraction*u/this.lineHeight;if(this.styles&&this.styles[n]&&(a=this.styles[n][r])){var h=a.stroke||this.stroke,p=a.fill||this.fill;t.save(),f=this._applyCharStylesGetWidth(t,i,n,r,a),l=this._getHeightOfChar(t,i,n,r),p&&t.fillText(i,s,o),h&&t.strokeText(i,s,o),this._renderCharDecoration(t,a,s,o,c,f,l),t.restore(),t.translate(f,0)}else e==="strokeText"&& -this.stroke&&t[e](i,s,o),e==="fillText"&&this.fill&&t[e](i,s,o),f=this._applyCharStylesGetWidth(t,i,n,r),this._renderCharDecoration(t,null,s,o,c,f,this.fontSize),t.translate(t.measureText(i).width,0)},_hasStyleChanged:function(e,t){return e.fill!==t.fill||e.fontSize!==t.fontSize||e.textBackgroundColor!==t.textBackgroundColor||e.textDecoration!==t.textDecoration||e.fontFamily!==t.fontFamily||e.fontWeight!==t.fontWeight||e.fontStyle!==t.fontStyle||e.stroke!==t.stroke||e.strokeWidth!==t.strokeWidth},_renderCharDecoration:function(e,t,n,r,i,s,o){var u=t?t.textDecoration||this.textDecoration:this.textDecoration;if(!u)return;u.indexOf("underline")>-1&&e.fillRect(n,r+o/10,s,o/15),u.indexOf("line-through")>-1&&e.fillRect(n,r-o*(this._fontSizeFraction+this._fontSizeMult-1)+o/15,s,o/15),u.indexOf("overline")>-1&&e.fillRect(n,r-(this._fontSizeMult-this._fontSizeFraction)*o,s,o/15)},_renderTextLine:function(e,t,n,r,i,s){this.isEmptyStyles()||(i+=this.fontSize*(this._fontSizeFraction+.03)),this.callSuper("_renderTextLine",e,t,n,r,i,s)},_renderTextDecoration:function(e){if(this.isEmptyStyles())return this.callSuper("_renderTextDecoration",e)},_renderTextLinesBackground:function(e){if(!this.textBackgroundColor&&!this.styles)return;e.save(),this.textBackgroundColor&&(e.fillStyle=this.textBackgroundColor);var t=0;for(var n=0,r=this._textLines.length;nr&&(r=o)}return this.__maxFontHeights[t]=r,this.__lineHeights[t]=r*this.lineHeight*this._fontSizeMult,this.__lineHeights[t]},_getTextHeight:function(e){var t=0;for(var n=0,r=this._textLines.length;n-1)t++,n--;return e-t},findWordBoundaryRight:function(e){var t=0,n=e;if(this._reSpace.test(this.text.charAt(n)))while(this._reSpace.test(this.text.charAt(n)))t++,n++;while(/\S/.test(this.text.charAt(n))&&n-1)t++,n--;return e-t},findLineBoundaryRight:function(e){var t=0,n=e;while(!/\n/.test(this.text.charAt(n))&&n0&&n=e.__selectionStartOnMouseDown?(e.setSelectionStart(e.__selectionStartOnMouseDown),e.setSelectionEnd(n)):(e.setSelectionStart(n),e.setSelectionEnd(e.__selectionStartOnMouseDown))})},_setEditingProps:function(){this.hoverCursor="text",this.canvas&&(this.canvas.defaultCursor=this.canvas.moveCursor="text"),this.borderColor=this.editingBorderColor,this.hasControls=this.selectable=!1,this.lockMovementX=this.lockMovementY=!0},_updateTextarea:function(){if(!this.hiddenTextarea)return;this.hiddenTextarea.value=this.text,this.hiddenTextarea.selectionStart=this.selectionStart,this.hiddenTextarea.selectionEnd=this.selectionEnd},_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}},_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,this.canvas&&(this.canvas.defaultCursor=this._savedProps.defaultCursor,this.canvas.moveCursor=this._savedProps.moveCursor)},exitEditing:function(){return this.selected=!1,this.isEditing=!1,this.selectable=!0,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}),this},_removeExtraneousStyles:function(){for(var e in this.styles)this._textLines[e]||delete this.styles[e]},_removeCharsFromTo:function(e,t){var n=t;while(n!==e){var r=this.get2DCursorLocation(n).charIndex;n--;var i=this.get2DCursorLocation(n).charIndex,s=i>r;s?this.removeStyleObject(s,n+1):this.removeStyleObject(this.get2DCursorLocation(n).charIndex===0,n)}this.text=this.text.slice(0,e)+this.text.slice(t),this._clearCache()},insertChars:function(e,t){var n=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+e+this.text.slice(this.selectionEnd),this.selectionStart===this.selectionEnd&&this.insertStyleObjects(e,n,t),this.setSelectionStart(this.selectionStart+e.length),this.setSelectionEnd(this.selectionStart),this._clearCache(),this.canvas&&this.canvas.renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},insertNewlineStyleObject:function(t,n,r){this.shiftLineStyles(t,1),this.styles[t+1]||(this.styles[t+1]={});var i=this.styles[t][n-1],s={};if(r)s[0]=e(i),this.styles[t+1]=s;else{for(var o in this.styles[t])parseInt(o,10)>=n&&(s[parseInt(o,10)-n]=this.styles[t][o],delete this.styles[t][o]);this.styles[t+1]=s}this._clearCache()},insertCharStyleObject:function(t,n,r){var i=this.styles[t],s=e(i);n===0&&!r&&(n=1);for(var o in s){var u=parseInt(o,10);u>=n&&(i[u+1]=s[u])}this.styles[t][n]=r||e(i[n-1]),this._clearCache()},insertStyleObjects:function(e,t,n){var r=this.get2DCursorLocation(),i=r.lineIndex,s=r.charIndex;this.styles[i]||(this.styles[i]={}),e==="\n"?this.insertNewlineStyleObject(i,s,t):n?this._insertStyles(this.copiedStyles):this.insertCharStyleObject(i,s)},_insertStyles:function(e){for(var t=0,n=e.length;tt&&(this.styles[s+n]=r[s])}},removeStyleObject:function(t,n){var r=this.get2DCursorLocation(n),i=r.lineIndex,s=r.charIndex;if(t){var o=this._textLines[i-1],u=o?o.length:0;this.styles[i-1]||(this.styles[i-1]={});for(s in this.styles[i])this.styles[i-1][parseInt(s,10)+u]=this.styles[i][s];this.shiftLineStyles(i,-1)}else{var a=this.styles[i];if(a){var f=this.selectionStart===this.selectionEnd?-1:0;delete a[s+f]}var l=e(a);for(var c in l){var h=parseInt(c,10);h>=s&&h!==0&&(a[h-1]=l[h],delete a[h])}}},insertNewline:function(){this.insertChars("\n")}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+(new Date),this.__lastLastClickTime=+(new Date),this.__lastPointer={},this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(e){this.__newClickTime=+(new Date);var t=this.canvas.getPointer(e.e);this.isTripleClick(t)?(this.fire("tripleclick",e),this._stopEvent(e.e)):this.isDoubleClick(t)&&(this.fire("dblclick",e),this._stopEvent(e.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=t,this.__lastIsEditing=this.isEditing,this.__lastSelected=this.selected},isDoubleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y&&this.__lastIsEditing},isTripleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y},_stopEvent:function(e){e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler(),this.initMousedownHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("dblclick",function(e){this.selectWord(this.getSelectionStartFromPointer(e.e))}),this.on("tripleclick",function(e){this.selectLine(this.getSelectionStartFromPointer(e.e))})},initMousedownHandler:function(){this.on("mousedown",function(e){var t=this.canvas.getPointer(e.e);this.__mousedownX=t.x,this.__mousedownY=t.y,this.__isMousedown=!0,this.hiddenTextarea&&this.canvas&&this.canvas.wrapperEl.appendChild(this.hiddenTextarea),this.selected&&this.setCursorByClick(e.e),this.isEditing&&(this.__selectionStartOnMouseDown=this.selectionStart,this.initDelayedCursor(!0))})},_isObjectMoved:function(e){var t=this.canvas.getPointer(e);return this.__mousedownX!==t.x||this.__mousedownY!==t.y},initMouseupHandler:function(){this.on("mouseup",function(e){this.__isMousedown=!1;if(this._isObjectMoved(e.e))return;this.__lastSelected&&(this.enterEditing(),this.initDelayedCursor(!0)),this.selected=!0})},setCursorByClick:function(e){var t=this.getSelectionStartFromPointer(e);e.shiftKey?ts?0:1,a=r+u;return this.flipX&&(a=i-a),a>this.text.length&&(a=this.text.length),a}}),fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off"),this.hiddenTextarea.style.cssText="position: fixed; bottom: 20px; left: 0px; opacity: 0; width: 0px; height: 0px; z-index: -999;",fabric.document.body.appendChild(this.hiddenTextarea),fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this)),fabric.util.addListener(this.hiddenTextarea,"copy",this.copy.bind(this)),fabric.util.addListener(this.hiddenTextarea,"paste",this.paste.bind(this)),!this._clickHandlerInitialized&&this.canvas&&(fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this)),this._clickHandlerInitialized=!0)},_keysMap:{8:"removeChars",9:"exitEditing",27:"exitEditing",13:"insertNewline",33:"moveCursorUp",34:"moveCursorDown",35:"moveCursorRight",36:"moveCursorLeft",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},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)))return;this[this._ctrlKeysMap[e.keyCode]](e)}e.stopImmediatePropagation(),e.preventDefault(),this.canvas&&this.canvas.renderAll()},forwardDelete:function(e){this.selectionStart===this.selectionEnd&&this.moveCursorRight(e),this.removeChars(e)},copy:function(e){var t=this.getSelectedText(),n=this._getClipboardData(e);n&&n.setData("text",t),this.copiedText=t,this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(e){var t=null,n=this._getClipboardData(e);n?t=n.getData("text"):t=this.copiedText,t&&this.insertChars(t,!0)},cut:function(e){if(this.selectionStart===this.selectionEnd)return;this.copy(),this.removeChars(e)},_getClipboardData:function(e){return e&&(e.clipboardData||fabric.window.clipboardData)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey)return;e.which!==0&&this.insertChars(String.fromCharCode(e.which)),e.stopPropagation()},getDownCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r,i,s=this.text.slice(0,n),o=this.text.slice(n),u=s.slice(s.lastIndexOf("\n")+1),a=o.match(/(.*)\n?/)[1],f=(o.match(/.*\n(.*)\n?/)||{})[1]||"",l=this.get2DCursorLocation(n);if(l.lineIndex===this._textLines.length-1||e.metaKey||e.keyCode===34)return this.text.length-n;var c=this._getLineWidth(this.ctx,l.lineIndex);i=this._getLineLeftOffset(c);var h=i,p=l.lineIndex;for(var d=0,v=u.length;dn){a=!0;var p=o-h,d=o,v=Math.abs(p-n),m=Math.abs(d-n);u=mthis.text.length&&this.setSelectionEnd(this.text.length)},getUpCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.get2DCursorLocation(n);if(r.lineIndex===0||e.metaKey||e.keyCode===33)return n;var i=this.text.slice(0,n),s=i.slice(i.lastIndexOf("\n")+1),o=(i.match(/\n?(.*)\n.*$/)||{})[1]||"",u,a=this._getLineWidth(this.ctx,r.lineIndex),f=this._getLineLeftOffset(a),l=f,c=r.lineIndex;for(var h=0,p=s.length;hn){a=!0;var p=o-h,d=o,v=Math.abs(p-n),m=Math.abs(d-n);u=m=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation(),this._currentCursorOpacity=1,e.shiftKey?this.moveCursorRightWithShift(e):this.moveCursorRightWithoutShift(e),this.initDelayedCursor()},moveCursorRightWithShift:function(e){this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd?this._moveRight(e,"selectionStart"):(this._selectionDirection="right",this._moveRight(e,"selectionEnd"),this.text.charAt(this.selectionEnd-1)==="\n"&&this.setSelectionEnd(this.selectionEnd+1))},moveCursorRightWithoutShift:function(e){this._selectionDirection="right",this.selectionStart===this.selectionEnd?(this._moveRight(e,"selectionStart"),this.setSelectionEnd(this.selectionStart)):(this.setSelectionEnd(this.selectionEnd+this.getNumNewLinesInSelectedText()),this.setSelectionStart(this.selectionEnd))},removeChars:function(e){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(e):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.setSelectionEnd(this.selectionStart),this._removeExtraneousStyles(),this._clearCache(),this.canvas&&this.canvas.renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(e){if(this.selectionStart!==0)if(e.metaKey){var t=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(t,this.selectionStart),this.setSelectionStart(t)}else if(e.altKey){var n=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(n,this.selectionStart),this.setSelectionStart(n)}else{var r=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(r),this.setSelectionStart(this.selectionStart-1),this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}),fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(e,t,n,r,i,s){this.styles[e]?this._setSVGTextLineChars(e,t,n,r,s):this.callSuper("_setSVGTextLineText",e,t,n,r,i)},_setSVGTextLineChars:function(e,t,n,r,i){var s=this._textLines[e].split(""),o=0,u=this._getSVGLineLeftOffset(e)-this.width/2,a=this._getSVGLineTopOffset(e),f=this._getHeightOfLine(this.ctx,e);for(var l=0,c=s.length;l'].join("")},_createTextCharSpan:function(e,t,n,r,i){var s=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text"},t));return['',fabric.util.string.escapeXml(e),""].join("")}}),function(){function request(e,t,n){var r=URL.parse(e);r.port||(r.port=r.protocol.indexOf("https:")===0?443:80);var i=r.protocol.indexOf("https:")===0?HTTPS:HTTP,s=i.request({hostname:r.hostname,port:r.port,path:r.path,method:"GET"},function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})});s.on("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+r.hostname+":"+r.port):fabric.log(e.message)}),s.end()}function requestFs(e,t){var n=require("fs");n.readFile(e,function(e,n){if(e)throw fabric.log(e),e;t(n)})}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;fabric.util.loadImage=function(e,t,n){function r(r){i.src=new Buffer(r,"binary"),i._src=e,t&&t.call(n,i)}var i=new Image;e&&(e instanceof Buffer||e.indexOf("data")===0)?(i.src=i._src=e,t&&t.call(n,i)):e&&e.indexOf("http")!==0?requestFs(e,r):e?request(e,"binary",r):t&&t.call(n,e)},fabric.loadSVGFromURL=function(e,t,n){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),e.indexOf("http")!==0?requestFs(e,function(e){fabric.loadSVGFromString(e.toString(),t,n)}):request(e,"",function(e){fabric.loadSVGFromString(e,t,n)})},fabric.loadSVGFromString=function(e,t,n){var r=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(r.documentElement,function(e,n){t&&t(e,n)},n)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){var r=new fabric.Image(n);r._initConfig(e),r._initFilters(e,function(e){r.filters=e||[],t&&t(r)})})},fabric.createCanvasForNode=function(e,t,n,r){r=r||n;var i=fabric.document.createElement("canvas"),s=new Canvas(e||600,t||600,r);i.style={},i.width=s.width,i.height=s.height;var o=fabric.Canvas||fabric.StaticCanvas,u=new o(i,n);return u.contextContainer=s.getContext("2d"),u.nodeCanvas=s,u.Font=Canvas.Font,u},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(e){return this.nodeCanvas.createJPEGStream(e)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e,t){return origSetWidth.call(this,e,t),this.nodeCanvas.width=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth);var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(e,t){return origSetHeight.call(this,e,t),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}(); \ No newline at end of file +var fabric=fabric||{version:"1.5.0"};if(typeof exports!=="undefined"){exports.fabric=fabric}if(typeof document!=="undefined"&&typeof window!=="undefined"){fabric.document=document;fabric.window=window;window.fabric=fabric}else{fabric.document=require("jsdom").jsdom("");if(fabric.document.createWindow){fabric.window=fabric.document.createWindow()}else{fabric.window=fabric.document.parentWindow}}fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement;fabric.isLikelyNode=typeof Buffer!=="undefined"&&typeof window==="undefined";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;fabric.reNum="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)";(function(){function _removeEventListener(eventName,handler){if(!this.__eventListeners[eventName]){return}if(handler){fabric.util.removeFromArray(this.__eventListeners[eventName],handler)}else{this.__eventListeners[eventName].length=0}}function observe(eventName,handler){if(!this.__eventListeners){this.__eventListeners={}}if(arguments.length===1){for(var prop in eventName){this.on(prop,eventName[prop])}}else{if(!this.__eventListeners[eventName]){this.__eventListeners[eventName]=[]}this.__eventListeners[eventName].push(handler)}return this}function stopObserving(eventName,handler){if(!this.__eventListeners){return}if(arguments.length===0){this.__eventListeners={}}else if(arguments.length===1&&typeof arguments[0]==="object"){for(var prop in eventName){_removeEventListener.call(this,prop,eventName[prop])}}else{_removeEventListener.call(this,eventName,handler)}return this}function fire(eventName,options){if(!this.__eventListeners){return}var listenersForEvent=this.__eventListeners[eventName];if(!listenersForEvent){return}for(var i=0,len=listenersForEvent.length;i-1},complexity:function(){return this.getObjects().reduce(function(memo,current){memo+=current.complexity?current.complexity():0;return memo},0)}};(function(global){var sqrt=Math.sqrt,atan2=Math.atan2,PiBy180=Math.PI/180;fabric.util={removeFromArray:function(array,value){var idx=array.indexOf(value);if(idx!==-1){array.splice(idx,1)}return array},getRandomInt:function(min,max){return Math.floor(Math.random()*(max-min+1))+min},degreesToRadians:function(degrees){return degrees*PiBy180},radiansToDegrees:function(radians){return radians/PiBy180},rotatePoint:function(point,origin,radians){var sin=Math.sin(radians),cos=Math.cos(radians);point.subtractEquals(origin);var rx=point.x*cos-point.y*sin,ry=point.x*sin+point.y*cos;return new fabric.Point(rx,ry).addEquals(origin)},transformPoint:function(p,t,ignoreOffset){if(ignoreOffset){return new fabric.Point(t[0]*p.x+t[2]*p.y,t[1]*p.x+t[3]*p.y)}return new fabric.Point(t[0]*p.x+t[2]*p.y+t[4],t[1]*p.x+t[3]*p.y+t[5])},invertTransform:function(t){var r=t.slice(),a=1/(t[0]*t[3]-t[1]*t[2]);r=[a*t[3],-a*t[1],-a*t[2],a*t[0],0,0];var o=fabric.util.transformPoint({x:t[4],y:t[5]},r);r[4]=-o.x;r[5]=-o.y;return r},toFixed:function(number,fractionDigits){return parseFloat(Number(number).toFixed(fractionDigits))},parseUnit:function(value,fontSize){var unit=/\D{0,2}$/.exec(value),number=parseFloat(value);if(!fontSize){fontSize=fabric.Text.DEFAULT_SVG_FONT_SIZE}switch(unit[0]){case"mm":return number*fabric.DPI/25.4;case"cm":return number*fabric.DPI/2.54;case"in":return number*fabric.DPI;case"pt":return number*fabric.DPI/72;case"pc":return number*fabric.DPI/72*12;case"em":return number*fontSize;default:return number}},falseFunction:function(){return false},getKlass:function(type,namespace){type=fabric.util.string.camelize(type.charAt(0).toUpperCase()+type.slice(1));return fabric.util.resolveNamespace(namespace)[type]},resolveNamespace:function(namespace){if(!namespace){return fabric}var parts=namespace.split("."),len=parts.length,obj=global||fabric.window;for(var i=0;ix){x+=da[di++%dc];if(x>len){x=len}ctx[draw?"lineTo":"moveTo"](x,0);draw=!draw}ctx.restore()},createCanvasElement:function(canvasEl){canvasEl||(canvasEl=fabric.document.createElement("canvas"));if(!canvasEl.getContext&&typeof G_vmlCanvasManager!=="undefined"){G_vmlCanvasManager.initElement(canvasEl)}return canvasEl},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(klass){var proto=klass.prototype;for(var i=proto.stateProperties.length;i--;){var propName=proto.stateProperties[i],capitalizedPropName=propName.charAt(0).toUpperCase()+propName.slice(1),setterName="set"+capitalizedPropName,getterName="get"+capitalizedPropName;if(!proto[getterName]){proto[getterName]=function(property){return new Function('return this.get("'+property+'")')}(propName)}if(!proto[setterName]){proto[setterName]=function(property){return new Function("value",'return this.set("'+property+'", value)')}(propName)}}},clipContext:function(receiver,ctx){ctx.save();ctx.beginPath();receiver.clipTo(ctx);ctx.clip()},multiplyTransformMatrices:function(a,b){return[a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1],a[0]*b[2]+a[2]*b[3],a[1]*b[2]+a[3]*b[3],a[0]*b[4]+a[2]*b[5]+a[4],a[1]*b[4]+a[3]*b[5]+a[5]]},getFunctionBody:function(fn){return(String(fn).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(ctx,x,y,tolerance){if(tolerance>0){if(x>tolerance){x-=tolerance}else{x=0}if(y>tolerance){y-=tolerance}else{y=0}}var _isTransparent=true,imageData=ctx.getImageData(x,y,tolerance*2||1,tolerance*2||1);for(var i=3,l=imageData.data.length;i0){dtheta-=2*PI}else if(sweep===1&&dtheta<0){dtheta+=2*PI}var segments=Math.ceil(Math.abs(dtheta/PI*2)),result=[],mDelta=dtheta/segments,mT=8/3*Math.sin(mDelta/4)*Math.sin(mDelta/4)/Math.sin(mDelta/2),th3=mTheta+mDelta;for(var i=0;i=ta){return tb-ta}else{return 2*Math.PI-(ta-tb)}}fabric.util.drawArc=function(ctx,fx,fy,coords){var rx=coords[0],ry=coords[1],rot=coords[2],large=coords[3],sweep=coords[4],tx=coords[5],ty=coords[6],segs=[[],[],[],[]],segsNorm=arcToSegments(tx-fx,ty-fy,rx,ry,large,sweep,rot);for(var i=0,len=segsNorm.length;i0){b=6*y0-12*y1+6*y2;a=-3*y0+9*y1-9*y2+3*y3;c=3*y1-3*y0}if(abs(a)<1e-12){if(abs(b)<1e-12){continue}t=-c/b;if(0>>0;if(len===0){return-1}var n=0;if(arguments.length>0){n=Number(arguments[1]);if(n!==n){n=0}else if(n!==0&&n!==Number.POSITIVE_INFINITY&&n!==Number.NEGATIVE_INFINITY){n=(n>0||-1)*Math.floor(Math.abs(n))}}if(n>=len){return-1}var k=n>=0?n:Math.max(len-Math.abs(n),0);for(;k>>0;i>>0;i>>0;i>>0;i>>0;i>>0,i=0,rv;if(arguments.length>1){rv=arguments[1]}else{do{if(i in this){rv=this[i++];break}if(++i>=len){throw new TypeError}}while(true)}for(;i=value2})}function min(array,byProperty){return find(array,byProperty,function(value1,value2){return value1/g,">")}fabric.util.string={camelize:camelize,capitalize:capitalize,escapeXml:escapeXml}})();(function(){var slice=Array.prototype.slice,apply=Function.prototype.apply,Dummy=function(){};if(!Function.prototype.bind){Function.prototype.bind=function(thisArg){var _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{bound=function(){return apply.call(_this,this instanceof Dummy?this:thisArg,arguments)}}Dummy.prototype=this.prototype;bound.prototype=new Dummy;return bound}}})();(function(){var slice=Array.prototype.slice,emptyFunction=function(){},IS_DONTENUM_BUGGY=function(){for(var p in{toString:1}){if(p==="toString"){return false}}return true}(),addMethods=function(klass,source,parent){for(var property in source){if(property in klass.prototype&&typeof klass.prototype[property]==="function"&&(source[property]+"").indexOf("callSuper")>-1){klass.prototype[property]=function(property){return function(){var superclass=this.constructor.superclass;this.constructor.superclass=parent;var returnValue=source[property].apply(this,arguments);this.constructor.superclass=superclass;if(property!=="initialize"){return returnValue}}}(property)}else{klass.prototype[property]=source[property]}if(IS_DONTENUM_BUGGY){if(source.toString!==Object.prototype.toString){klass.prototype.toString=source.toString}if(source.valueOf!==Object.prototype.valueOf){klass.prototype.valueOf=source.valueOf}}}};function Subclass(){}function callSuper(methodName){var fn=this.constructor.superclass.prototype[methodName];return arguments.length>1?fn.apply(this,slice.call(arguments,1)):fn.call(this)}function createClass(){var parent=null,properties=slice.call(arguments,0);if(typeof properties[0]==="function"){parent=properties.shift()}function klass(){this.initialize.apply(this,arguments)}klass.superclass=parent;klass.subclasses=[];if(parent){Subclass.prototype=parent.prototype;klass.prototype=new Subclass;parent.subclasses.push(klass)}for(var i=0,length=properties.length;i-1?setOpacity(element,styles.match(/opacity:\s*(\d?\.?\d*)/)[1]):element}for(var property in styles){if(property==="opacity"){setOpacity(element,styles[property])}else{var normalizedProperty=property==="float"||property==="cssFloat"?typeof elementStyle.styleFloat==="undefined"?"cssFloat":"styleFloat":property;elementStyle[normalizedProperty]=styles[property]}}return element}var parseEl=fabric.document.createElement("div"),supportsOpacity=typeof parseEl.style.opacity==="string",supportsFilters=typeof parseEl.style.filter==="string",reOpacity=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,setOpacity=function(element){return element};if(supportsOpacity){setOpacity=function(element,value){element.style.opacity=value;return element}}else if(supportsFilters){setOpacity=function(element,value){var es=element.style;if(element.currentStyle&&!element.currentStyle.hasLayout){es.zoom=1}if(reOpacity.test(es.filter)){value=value>=.9999?"":"alpha(opacity="+value*100+")";es.filter=es.filter.replace(reOpacity,value)}else{es.filter+=" alpha(opacity="+value*100+")"}return element}}fabric.util.setStyle=setStyle})();(function(){var _slice=Array.prototype.slice;function getById(id){return typeof id==="string"?fabric.document.getElementById(id):id}var sliceCanConvertNodelists,toArray=function(arrayLike){return _slice.call(arrayLike,0)};try{sliceCanConvertNodelists=toArray(fabric.document.childNodes)instanceof Array}catch(err){}if(!sliceCanConvertNodelists){toArray=function(arrayLike){var arr=new Array(arrayLike.length),i=arrayLike.length;while(i--){arr[i]=arrayLike[i]}return arr}}function makeElement(tagName,attributes){var el=fabric.document.createElement(tagName);for(var prop in attributes){if(prop==="class"){el.className=attributes[prop]}else if(prop==="for"){el.htmlFor=attributes[prop]}else{el.setAttribute(prop,attributes[prop])}}return el}function addClass(element,className){if(element&&(" "+element.className+" ").indexOf(" "+className+" ")===-1){element.className+=(element.className?" ":"")+className}}function wrapElement(element,wrapper,attributes){if(typeof wrapper==="string"){wrapper=makeElement(wrapper,attributes)}if(element.parentNode){element.parentNode.replaceChild(wrapper,element)}wrapper.appendChild(element);return wrapper}function getScrollLeftTop(element,upperCanvasEl){var firstFixedAncestor,origElement,left=0,top=0,docElement=fabric.document.documentElement,body=fabric.document.body||{scrollLeft:0,scrollTop:0};origElement=element;while(element&&element.parentNode&&!firstFixedAncestor){element=element.parentNode;if(element.nodeType===1&&fabric.util.getElementStyle(element,"position")==="fixed"){firstFixedAncestor=element}if(element.nodeType===1&&origElement!==upperCanvasEl&&fabric.util.getElementStyle(element,"position")==="absolute"){left=0;top=0}else if(element===fabric.document){left=body.scrollLeft||docElement.scrollLeft||0;top=body.scrollTop||docElement.scrollTop||0}else{left+=element.scrollLeft||0;top+=element.scrollTop||0}}return{left:left,top:top}}function getElementOffset(element){var docElem,doc=element&&element.ownerDocument,box={left:0,top:0},offset={left:0,top:0},scrollLeftTop,offsetAttributes={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!doc){return{left:0,top:0}}for(var attr in offsetAttributes){offset[offsetAttributes[attr]]+=parseInt(getElementStyle(element,attr),10)||0}docElem=doc.documentElement;if(typeof element.getBoundingClientRect!=="undefined"){box=element.getBoundingClientRect()}scrollLeftTop=fabric.util.getScrollLeftTop(element,null);return{left:box.left+scrollLeftTop.left-(docElem.clientLeft||0)+offset.left,top:box.top+scrollLeftTop.top-(docElem.clientTop||0)+offset.top}}var getElementStyle;if(fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle){getElementStyle=function(element,attr){var style=fabric.document.defaultView.getComputedStyle(element,null);return style?style[attr]:undefined}}else{getElementStyle=function(element,attr){var value=element.style[attr];if(!value&&element.currentStyle){value=element.currentStyle[attr]}return value}}(function(){var style=fabric.document.documentElement.style,selectProp="userSelect"in style?"userSelect":"MozUserSelect"in style?"MozUserSelect":"WebkitUserSelect"in style?"WebkitUserSelect":"KhtmlUserSelect"in style?"KhtmlUserSelect":"";function makeElementUnselectable(element){if(typeof element.onselectstart!=="undefined"){element.onselectstart=fabric.util.falseFunction}if(selectProp){element.style[selectProp]="none"}else if(typeof element.unselectable==="string"){element.unselectable="on"}return element}function makeElementSelectable(element){if(typeof element.onselectstart!=="undefined"){element.onselectstart=null}if(selectProp){element.style[selectProp]=""}else if(typeof element.unselectable==="string"){element.unselectable=""}return element}fabric.util.makeElementUnselectable=makeElementUnselectable;fabric.util.makeElementSelectable=makeElementSelectable})();(function(){function getScript(url,callback){var headEl=fabric.document.getElementsByTagName("head")[0],scriptEl=fabric.document.createElement("script"),loading=true;scriptEl.onload=scriptEl.onreadystatechange=function(e){if(loading){if(typeof this.readyState==="string"&&this.readyState!=="loaded"&&this.readyState!=="complete"){return}loading=false;callback(e||fabric.window.event);scriptEl=scriptEl.onload=scriptEl.onreadystatechange=null}};scriptEl.src=url;headEl.appendChild(scriptEl)}fabric.util.getScript=getScript})();fabric.util.getById=getById;fabric.util.toArray=toArray;fabric.util.makeElement=makeElement;fabric.util.addClass=addClass;fabric.util.wrapElement=wrapElement;fabric.util.getScrollLeftTop=getScrollLeftTop;fabric.util.getElementOffset=getElementOffset;fabric.util.getElementStyle=getElementStyle})();(function(){function addParamToUrl(url,param){return url+(/\?/.test(url)?"&":"?")+param}var makeXHR=function(){var factories=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var i=factories.length;i--;){try{var req=factories[i]();if(req){return factories[i]}}catch(err){}}}();function emptyFn(){}function request(url,options){options||(options={});var method=options.method?options.method.toUpperCase():"GET",onComplete=options.onComplete||function(){},xhr=makeXHR(),body;xhr.onreadystatechange=function(){if(xhr.readyState===4){onComplete(xhr);xhr.onreadystatechange=emptyFn}};if(method==="GET"){body=null;if(typeof options.parameters==="string"){url=addParamToUrl(url,options.parameters)}}xhr.open(method,url,true);if(method==="POST"||method==="PUT"){xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")}xhr.send(body);return xhr}fabric.util.request=request})();fabric.log=function(){};fabric.warn=function(){};if(typeof console!=="undefined"){["log","warn"].forEach(function(methodName){if(typeof console[methodName]!=="undefined"&&typeof console[methodName].apply==="function"){fabric[methodName]=function(){return console[methodName].apply(console,arguments)}}})}(function(){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,1e3/60)};function requestAnimFrame(){return _requestAnimFrame.apply(fabric.window,arguments)}fabric.util.animate=animate;fabric.util.requestAnimFrame=requestAnimFrame})();(function(){function normalize(a,c,p,s){if(a1){matrices.shift();combinedMatrix=fabric.util.multiplyTransformMatrices(combinedMatrix,matrices[0])}return combinedMatrix}}();function parseStyleString(style,oStyle){var attr,value;style.replace(/;$/,"").split(";").forEach(function(chunk){var pair=chunk.split(":");attr=normalizeAttr(pair[0].trim().toLowerCase());value=normalizeValue(attr,pair[1].trim());oStyle[attr]=value})}function parseStyleObject(style,oStyle){var attr,value;for(var prop in style){if(typeof style[prop]==="undefined"){continue}attr=normalizeAttr(prop.toLowerCase());value=normalizeValue(attr,style[prop]);oStyle[attr]=value}}function getGlobalStylesForElement(element,svgUid){var styles={};for(var rule in fabric.cssRules[svgUid]){if(elementMatchesRule(element,rule.split(" "))){for(var property in fabric.cssRules[svgUid][rule]){styles[property]=fabric.cssRules[svgUid][rule][property]}}}return styles}function elementMatchesRule(element,selectors){var firstMatching,parentMatching=true;firstMatching=selectorMatches(element,selectors.pop());if(firstMatching&&selectors.length){parentMatching=doesSomeParentMatch(element,selectors)}return firstMatching&&parentMatching&&selectors.length===0}function doesSomeParentMatch(element,selectors){var selector,parentMatching=true;while(element.parentNode&&element.parentNode.nodeType===1&&selectors.length){if(parentMatching){selector=selectors.pop()}element=element.parentNode;parentMatching=selectorMatches(element,selector)}return selectors.length===0}function selectorMatches(element,selector){var nodeName=element.nodeName,classNames=element.getAttribute("class"),id=element.getAttribute("id"),matcher;matcher=new RegExp("^"+nodeName,"i");selector=selector.replace(matcher,"");if(id&&selector.length){matcher=new RegExp("#"+id+"(?![a-zA-Z\\-]+)","i");selector=selector.replace(matcher,"")}if(classNames&&selector.length){classNames=classNames.split(" ");for(var i=classNames.length;i--;){matcher=new RegExp("\\."+classNames[i]+"(?![a-zA-Z\\-]+)","i");selector=selector.replace(matcher,"")}}return selector.length===0}function parseUseDirectives(doc){var nodelist=doc.getElementsByTagName("use");while(nodelist.length){var el=nodelist[0],xlink=el.getAttribute("xlink:href").substr(1),x=el.getAttribute("x")||0,y=el.getAttribute("y")||0,el2=doc.getElementById(xlink).cloneNode(true),currentTrans=(el2.getAttribute("transform")||"")+" translate("+x+", "+y+")",parentNode;for(var j=0,attrs=el.attributes,l=attrs.length;jscaleY?scaleY:scaleX;if(!(scaleX!==1||scaleY!==1||minX!==0||minY!==0)){return}matrix=" matrix("+scaleX+" 0"+" 0 "+scaleY+" "+minX*scaleX+" "+minY*scaleY+") ";if(element.tagName==="svg"){el=element.ownerDocument.createElement("g");while(element.firstChild!=null){el.appendChild(element.firstChild)}element.appendChild(el)}else{el=element;matrix=el.getAttribute("transform")+matrix}el.setAttribute("transform",matrix)}fabric.parseSVGDocument=function(){var reAllowedSVGTagNames=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,reViewBoxTagNames=/^(symbol|image|marker|pattern|view)$/;function hasAncestorWithNodeName(element,nodeName){while(element&&(element=element.parentNode)){if(nodeName.test(element.nodeName)&&!element.getAttribute("instantiated_by_use")){return true}}return false}return function(doc,callback,reviver){if(!doc){return}parseUseDirectives(doc);var startTime=new Date,svgUid=fabric.Object.__uid++,widthAttr,heightAttr,toBeParsed=false;if(doc.getAttribute("width")&&doc.getAttribute("width")!=="100%"){widthAttr=parseUnit(doc.getAttribute("width"))}if(doc.getAttribute("height")&&doc.getAttribute("height")!=="100%"){heightAttr=parseUnit(doc.getAttribute("height"))}if(!widthAttr||!heightAttr){var viewBoxAttr=doc.getAttribute("viewBox");if(viewBoxAttr&&(viewBoxAttr=viewBoxAttr.match(reViewBoxAttrValue))){widthAttr=parseFloat(viewBoxAttr[3]),heightAttr=parseFloat(viewBoxAttr[4])}else{toBeParsed=true}}addVBTransform(doc,widthAttr,heightAttr);var descendants=fabric.util.toArray(doc.getElementsByTagName("*"));if(descendants.length===0&&fabric.isLikelyNode){descendants=doc.selectNodes('//*[name(.)!="svg"]');var arr=[];for(var i=0,len=descendants.length;i','')}}var reFontDeclaration=new RegExp("(normal|italic)?\\s*(normal|small-caps)?\\s*"+"(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*("+fabric.reNum+"(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|"+fabric.reNum+"))?\\s+(.*)");extend(fabric,{parseFontDeclaration:function(value,oStyle){var match=value.match(reFontDeclaration);if(!match){return}var fontStyle=match[1],fontWeight=match[3],fontSize=match[4],lineHeight=match[5],fontFamily=match[6];if(fontStyle){oStyle.fontStyle=fontStyle}if(fontWeight){oStyle.fontWeight=isNaN(parseFloat(fontWeight))?fontWeight:parseFloat(fontWeight)}if(fontSize){oStyle.fontSize=parseUnit(fontSize)}if(fontFamily){oStyle.fontFamily=fontFamily}if(lineHeight){oStyle.lineHeight=lineHeight==="normal"?1:lineHeight}},getGradientDefs:function(doc){var linearGradientEls=doc.getElementsByTagName("linearGradient"),radialGradientEls=doc.getElementsByTagName("radialGradient"),el,i,j=0,id,xlink,elList=[],gradientDefs={},idsToXlinkMap={};elList.length=linearGradientEls.length+radialGradientEls.length;i=linearGradientEls.length;while(i--){elList[j++]=linearGradientEls[i]}i=radialGradientEls.length;while(i--){elList[j++]=radialGradientEls[i]}while(j--){el=elList[j];xlink=el.getAttribute("xlink:href");id=el.getAttribute("id");if(xlink){idsToXlinkMap[id]=xlink.substr(1)}gradientDefs[id]=el}for(id in idsToXlinkMap){var el2=gradientDefs[idsToXlinkMap[id]].cloneNode(true);el=gradientDefs[id];while(el2.firstChild){el.appendChild(el2.firstChild)}}return gradientDefs},parseAttributes:function(element,attributes,svgUid){if(!element){return}var value,parentAttributes={},fontSize;if(typeof svgUid==="undefined"){svgUid=element.getAttribute("svgUid")}if(element.parentNode&&/^symbol|[g|a]$/i.test(element.parentNode.nodeName)){parentAttributes=fabric.parseAttributes(element.parentNode,attributes,svgUid)}fontSize=parentAttributes&&parentAttributes.fontSize||element.getAttribute("font-size")||fabric.Text.DEFAULT_SVG_FONT_SIZE;var ownAttributes=attributes.reduce(function(memo,attr){value=element.getAttribute(attr);if(value){attr=normalizeAttr(attr);value=normalizeValue(attr,value,parentAttributes,fontSize);memo[attr]=value}return memo},{});ownAttributes=extend(ownAttributes,extend(getGlobalStylesForElement(element,svgUid),fabric.parseStyleAttribute(element)));if(ownAttributes.font){fabric.parseFontDeclaration(ownAttributes.font,ownAttributes)}return _setStrokeFillOpacity(extend(parentAttributes,ownAttributes))},parseElements:function(elements,callback,options,reviver){new fabric.ElementsParser(elements,callback,options,reviver).parse()},parseStyleAttribute:function(element){var oStyle={},style=element.getAttribute("style");if(!style){return oStyle}if(typeof style==="string"){parseStyleString(style,oStyle)}else{parseStyleObject(style,oStyle)}return oStyle},parsePointsAttribute:function(points){if(!points){return null}points=points.replace(/,/g," ").trim();points=points.split(/\s+/);var parsedPoints=[],i,len;i=0;len=points.length;for(;i/i,""))}if(!xml||!xml.documentElement){return}fabric.parseSVGDocument(xml.documentElement,function(results,options){svgCache.set(url,{objects:fabric.util.array.invoke(results,"toObject"),options:options});callback(results,options)},reviver)}},loadSVGFromString:function(string,callback,reviver){string=string.trim();var doc;if(typeof DOMParser!=="undefined"){var parser=new DOMParser;if(parser&&parser.parseFromString){doc=parser.parseFromString(string,"text/xml")}}else if(fabric.window.ActiveXObject){doc=new ActiveXObject("Microsoft.XMLDOM");doc.async="false";doc.loadXML(string.replace(//i,""))}fabric.parseSVGDocument(doc.documentElement,function(results,options){callback(results,options)},reviver)},createSVGFontFacesMarkup:function(objects){var markup="";for(var i=0,len=objects.length;i',"",""].join("")}return markup},createSVGRefElementsMarkup:function(canvas){var markup=[];_createSVGPattern(markup,canvas,"backgroundColor");_createSVGPattern(markup,canvas,"overlayColor");return markup.join("")}})})(typeof exports!=="undefined"?exports:this);fabric.ElementsParser=function(elements,callback,options,reviver){this.elements=elements;this.callback=callback;this.options=options;this.reviver=reviver;this.svgUid=options&&options.svgUid||0};fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length);this.numElements=this.elements.length;this.createObjects()};fabric.ElementsParser.prototype.createObjects=function(){for(var i=0,len=this.elements.length;ithat.x&&this.y>that.y},gte:function(that){return this.x>=that.x&&this.y>=that.y},lerp:function(that,t){return new Point(this.x+(that.x-this.x)*t,this.y+(that.y-this.y)*t)},distanceFrom:function(that){var dx=this.x-that.x,dy=this.y-that.y;return Math.sqrt(dx*dx+dy*dy)},midPointFrom:function(that){return new Point(this.x+(that.x-this.x)/2,this.y+(that.y-this.y)/2)},min:function(that){return new Point(Math.min(this.x,that.x),Math.min(this.y,that.y))},max:function(that){return new Point(Math.max(this.x,that.x),Math.max(this.y,that.y))},toString:function(){return this.x+","+this.y},setXY:function(x,y){this.x=x;this.y=y},setFromPoint:function(that){this.x=that.x;this.y=that.y},swap:function(that){var x=this.x,y=this.y;this.x=that.x;this.y=that.y;that.x=x;that.y=y}}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Intersection){fabric.warn("fabric.Intersection is already defined");return}function Intersection(status){this.status=status;this.points=[]}fabric.Intersection=Intersection;fabric.Intersection.prototype={appendPoint:function(point){this.points.push(point)},appendPoints:function(points){this.points=this.points.concat(points)}};fabric.Intersection.intersectLineLine=function(a1,a2,b1,b2){var result,uaT=(b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x),ubT=(a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x),uB=(b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);if(uB!==0){var ua=uaT/uB,ub=ubT/uB;if(0<=ua&&ua<=1&&0<=ub&&ub<=1){result=new Intersection("Intersection");result.points.push(new fabric.Point(a1.x+ua*(a2.x-a1.x),a1.y+ua*(a2.y-a1.y)))}else{result=new Intersection}}else{if(uaT===0||ubT===0){result=new Intersection("Coincident")}else{result=new Intersection("Parallel")}}return result};fabric.Intersection.intersectLinePolygon=function(a1,a2,points){var result=new Intersection,length=points.length;for(var i=0;i0){result.status="Intersection"}return result};fabric.Intersection.intersectPolygonPolygon=function(points1,points2){var result=new Intersection,length=points1.length;for(var i=0;i0){result.status="Intersection"}return result};fabric.Intersection.intersectPolygonRectangle=function(points,r1,r2){var min=r1.min(r2),max=r1.max(r2),topRight=new fabric.Point(max.x,min.y),bottomLeft=new fabric.Point(min.x,max.y),inter1=Intersection.intersectLinePolygon(min,topRight,points),inter2=Intersection.intersectLinePolygon(topRight,max,points),inter3=Intersection.intersectLinePolygon(max,bottomLeft,points),inter4=Intersection.intersectLinePolygon(bottomLeft,min,points),result=new Intersection;result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0){result.status="Intersection"}return result}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Color){fabric.warn("fabric.Color is already defined.");return}function Color(color){if(!color){this.setSource([0,0,0,1])}else{this._tryParsingColor(color)}}fabric.Color=Color;fabric.Color.prototype={_tryParsingColor:function(color){var source;if(color in Color.colorNameMap){color=Color.colorNameMap[color]}if(color==="transparent"){this.setSource([255,255,255,0]);return}source=Color.sourceFromHex(color);if(!source){source=Color.sourceFromRgb(color)}if(!source){source=Color.sourceFromHsl(color)}if(source){this.setSource(source)}},_rgbToHsl:function(r,g,b){r/=255,g/=255,b/=255;var h,s,l,max=fabric.util.array.max([r,g,b]),min=fabric.util.array.min([r,g,b]);l=(max+min)/2;if(max===min){h=s=0}else{var d=max-min;s=l>.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g1){t-=1}if(t<1/6){return p+(q-p)*6*t}if(t<1/2){return q}if(t<2/3){return p+(q-p)*(2/3-t)*6}return p}fabric.Color.fromRgb=function(color){return Color.fromSource(Color.sourceFromRgb(color))};fabric.Color.sourceFromRgb=function(color){var match=color.match(Color.reRGBa);if(match){var r=parseInt(match[1],10)/(/%$/.test(match[1])?100:1)*(/%$/.test(match[1])?255:1),g=parseInt(match[2],10)/(/%$/.test(match[2])?100:1)*(/%$/.test(match[2])?255:1),b=parseInt(match[3],10)/(/%$/.test(match[3])?100:1)*(/%$/.test(match[3])?255:1);return[parseInt(r,10),parseInt(g,10),parseInt(b,10),match[4]?parseFloat(match[4]):1]}};fabric.Color.fromRgba=Color.fromRgb; + +fabric.Color.fromHsl=function(color){return Color.fromSource(Color.sourceFromHsl(color))};fabric.Color.sourceFromHsl=function(color){var match=color.match(Color.reHSLa);if(!match){return}var h=(parseFloat(match[1])%360+360)%360/360,s=parseFloat(match[2])/(/%$/.test(match[2])?100:1),l=parseFloat(match[3])/(/%$/.test(match[3])?100:1),r,g,b;if(s===0){r=g=b=l}else{var q=l<=.5?l*(s+1):l+s-l*s,p=l*2-q;r=hue2rgb(p,q,h+1/3);g=hue2rgb(p,q,h);b=hue2rgb(p,q,h-1/3)}return[Math.round(r*255),Math.round(g*255),Math.round(b*255),match[4]?parseFloat(match[4]):1]};fabric.Color.fromHsla=Color.fromHsl;fabric.Color.fromHex=function(color){return Color.fromSource(Color.sourceFromHex(color))};fabric.Color.sourceFromHex=function(color){if(color.match(Color.reHex)){var value=color.slice(color.indexOf("#")+1),isShortNotation=value.length===3,r=isShortNotation?value.charAt(0)+value.charAt(0):value.substring(0,2),g=isShortNotation?value.charAt(1)+value.charAt(1):value.substring(2,4),b=isShortNotation?value.charAt(2)+value.charAt(2):value.substring(4,6);return[parseInt(r,16),parseInt(g,16),parseInt(b,16),1]}};fabric.Color.fromSource=function(source){var oColor=new Color;oColor.setSource(source);return oColor}})(typeof exports!=="undefined"?exports:this);(function(){function getColorStop(el){var style=el.getAttribute("style"),offset=el.getAttribute("offset"),color,colorAlpha,opacity;offset=parseFloat(offset)/(/%$/.test(offset)?100:1);offset=offset<0?0:offset>1?1:offset;if(style){var keyValuePairs=style.split(/\s*;\s*/);if(keyValuePairs[keyValuePairs.length-1]===""){keyValuePairs.pop()}for(var i=keyValuePairs.length;i--;){var split=keyValuePairs[i].split(/\s*:\s*/),key=split[0].trim(),value=split[1].trim();if(key==="stop-color"){color=value}else if(key==="stop-opacity"){opacity=value}}}if(!color){color=el.getAttribute("stop-color")||"rgb(0,0,0)"}if(!opacity){opacity=el.getAttribute("stop-opacity")}color=new fabric.Color(color);colorAlpha=color.getAlpha();opacity=isNaN(parseFloat(opacity))?1:parseFloat(opacity);opacity*=colorAlpha;return{offset:offset,color:color.toRgb(),opacity:opacity}}function getLinearCoords(el){return{x1:el.getAttribute("x1")||0,y1:el.getAttribute("y1")||0,x2:el.getAttribute("x2")||"100%",y2:el.getAttribute("y2")||0}}function getRadialCoords(el){return{x1:el.getAttribute("fx")||el.getAttribute("cx")||"50%",y1:el.getAttribute("fy")||el.getAttribute("cy")||"50%",r1:0,x2:el.getAttribute("cx")||"50%",y2:el.getAttribute("cy")||"50%",r2:el.getAttribute("r")||"50%"}}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(options){options||(options={});var coords={};this.id=fabric.Object.__uid++;this.type=options.type||"linear";coords={x1:options.coords.x1||0,y1:options.coords.y1||0,x2:options.coords.x2||0,y2:options.coords.y2||0};if(this.type==="radial"){coords.r1=options.coords.r1||0;coords.r2=options.coords.r2||0}this.coords=coords;this.colorStops=options.colorStops.slice();if(options.gradientTransform){this.gradientTransform=options.gradientTransform}this.offsetX=options.offsetX||this.offsetX;this.offsetY=options.offsetY||this.offsetY},addColorStop:function(colorStop){for(var position in colorStop){var color=new fabric.Color(colorStop[position]);this.colorStops.push({offset:position,color:color.toRgb(),opacity:color.getAlpha()})}return this},toObject:function(){return{type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(object){var coords=fabric.util.object.clone(this.coords),markup,commonAttributes;this.colorStops.sort(function(a,b){return a.offset-b.offset});if(!(object.group&&object.group.type==="path-group")){for(var prop in coords){if(prop==="x1"||prop==="x2"||prop==="r2"){coords[prop]+=this.offsetX-object.width/2}else if(prop==="y1"||prop==="y2"){coords[prop]+=this.offsetY-object.height/2}}}commonAttributes='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"';if(this.gradientTransform){commonAttributes+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '}if(this.type==="linear"){markup=["\n']}else if(this.type==="radial"){markup=["\n']}for(var i=0;i\n')}markup.push(this.type==="linear"?"\n":"\n");return markup.join("")},toLive:function(ctx,object){var gradient,prop,coords=fabric.util.object.clone(this.coords);if(!this.type){return}if(object.group&&object.group.type==="path-group"){for(prop in coords){if(prop==="x1"||prop==="x2"){coords[prop]+=-this.offsetX+object.width/2}else if(prop==="y1"||prop==="y2"){coords[prop]+=-this.offsetY+object.height/2}}}if(object.type==="text"||object.type==="i-text"){for(prop in coords){if(prop==="x1"||prop==="x2"){coords[prop]-=object.width/2}else if(prop==="y1"||prop==="y2"){coords[prop]-=object.height/2}}}if(this.type==="linear"){gradient=ctx.createLinearGradient(coords.x1,coords.y1,coords.x2,coords.y2)}else if(this.type==="radial"){gradient=ctx.createRadialGradient(coords.x1,coords.y1,coords.r1,coords.x2,coords.y2,coords.r2)}for(var i=0,len=this.colorStops.length;i\n'+'\n'+"\n"},toLive:function(ctx){var source=typeof this.source==="function"?this.source():this.source;if(!source){return""}if(typeof source.src!=="undefined"){if(!source.complete){return""}if(source.naturalWidth===0||source.naturalHeight===0){return""}}return ctx.createPattern(source,this.repeat)}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),toFixed=fabric.util.toFixed;if(fabric.Shadow){fabric.warn("fabric.Shadow is already defined.");return}fabric.Shadow=fabric.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:false,includeDefaultValues:true,initialize:function(options){if(typeof options==="string"){options=this._parseShadow(options)}for(var prop in options){this[prop]=options[prop]}this.id=fabric.Object.__uid++},_parseShadow:function(shadow){var shadowStr=shadow.trim(),offsetsAndBlur=fabric.Shadow.reOffsetsAndBlur.exec(shadowStr)||[],color=shadowStr.replace(fabric.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:color.trim(),offsetX:parseInt(offsetsAndBlur[1],10)||0,offsetY:parseInt(offsetsAndBlur[2],10)||0,blur:parseInt(offsetsAndBlur[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(object){var mode="SourceAlpha",fBoxX=40,fBoxY=40;if(object&&(object.fill===this.color||object.stroke===this.color)){mode="SourceGraphic"}if(object.width&&object.height){fBoxX=toFixed(Math.abs(this.offsetX/object.getWidth()),2)*100+20;fBoxY=toFixed(Math.abs(this.offsetY/object.getHeight()),2)*100+20}return'\n"+' \n'+' \n'+' \n'+" \n"+" \n"+' \n'+" \n"+"\n"},toObject:function(){if(this.includeDefaultValues){return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY}}var obj={},proto=fabric.Shadow.prototype;if(this.color!==proto.color){obj.color=this.color}if(this.blur!==proto.blur){obj.blur=this.blur}if(this.offsetX!==proto.offsetX){obj.offsetX=this.offsetX}if(this.offsetY!==proto.offsetY){obj.offsetY=this.offsetY}return obj}});fabric.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/})(typeof exports!=="undefined"?exports:this);(function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var extend=fabric.util.object.extend,getElementOffset=fabric.util.getElementOffset,removeFromArray=fabric.util.removeFromArray,CANVAS_INIT_ERROR=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(el,options){options||(options={});this._initStatic(el,options);fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:true,stateful:true,renderOnAddRemove:true,clipTo:null,controlsAboveOverlay:false,allowTouchScrolling:false,imageSmoothingEnabled:true,preserveObjectStacking:false,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(el,options){this._objects=[];this._createLowerCanvas(el);this._initOptions(options);this._setImageSmoothing();if(options.overlayImage){this.setOverlayImage(options.overlayImage,this.renderAll.bind(this))}if(options.backgroundImage){this.setBackgroundImage(options.backgroundImage,this.renderAll.bind(this))}if(options.backgroundColor){this.setBackgroundColor(options.backgroundColor,this.renderAll.bind(this))}if(options.overlayColor){this.setOverlayColor(options.overlayColor,this.renderAll.bind(this))}this.calcOffset()},calcOffset:function(){this._offset=getElementOffset(this.lowerCanvasEl);return this},setOverlayImage:function(image,callback,options){return this.__setBgOverlayImage("overlayImage",image,callback,options)},setBackgroundImage:function(image,callback,options){return this.__setBgOverlayImage("backgroundImage",image,callback,options)},setOverlayColor:function(overlayColor,callback){return this.__setBgOverlayColor("overlayColor",overlayColor,callback)},setBackgroundColor:function(backgroundColor,callback){return this.__setBgOverlayColor("backgroundColor",backgroundColor,callback)},_setImageSmoothing:function(){var ctx=this.getContext();ctx.imageSmoothingEnabled=this.imageSmoothingEnabled;ctx.webkitImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.mozImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.msImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(property,image,callback,options){if(typeof image==="string"){fabric.util.loadImage(image,function(img){this[property]=new fabric.Image(img,options);callback&&callback()},this,options&&options.crossOrigin)}else{options&&image.setOptions(options);this[property]=image;callback&&callback()}return this},__setBgOverlayColor:function(property,color,callback){if(color&&color.source){var _this=this;fabric.util.loadImage(color.source,function(img){_this[property]=new fabric.Pattern({source:img,repeat:color.repeat,offsetX:color.offsetX,offsetY:color.offsetY});callback&&callback()})}else{this[property]=color;callback&&callback()}return this},_createCanvasElement:function(){var element=fabric.document.createElement("canvas");if(!element.style){element.style={}}if(!element){throw CANVAS_INIT_ERROR}this._initCanvasElement(element);return element},_initCanvasElement:function(element){fabric.util.createCanvasElement(element);if(typeof element.getContext==="undefined"){throw CANVAS_INIT_ERROR}},_initOptions:function(options){for(var prop in options){this[prop]=options[prop]}this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0;this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style){return}this.lowerCanvasEl.width=this.width;this.lowerCanvasEl.height=this.height;this.lowerCanvasEl.style.width=this.width+"px";this.lowerCanvasEl.style.height=this.height+"px";this.viewportTransform=this.viewportTransform.slice()},_createLowerCanvas:function(canvasEl){this.lowerCanvasEl=fabric.util.getById(canvasEl)||this._createCanvasElement();this._initCanvasElement(this.lowerCanvasEl);fabric.util.addClass(this.lowerCanvasEl,"lower-canvas");if(this.interactive){this._applyCanvasStyle(this.lowerCanvasEl)}this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(value,options){return this.setDimensions({width:value},options)},setHeight:function(value,options){return this.setDimensions({height:value},options)},setDimensions:function(dimensions,options){var cssValue;options=options||{};for(var prop in dimensions){cssValue=dimensions[prop];if(!options.cssOnly){this._setBackstoreDimension(prop,dimensions[prop]);cssValue+="px"}if(!options.backstoreOnly){this._setCssDimension(prop,cssValue)}}if(!options.cssOnly){this.renderAll()}this.calcOffset();return this},_setBackstoreDimension:function(prop,value){this.lowerCanvasEl[prop]=value;if(this.upperCanvasEl){this.upperCanvasEl[prop]=value}if(this.cacheCanvasEl){this.cacheCanvasEl[prop]=value}this[prop]=value;return this},_setCssDimension:function(prop,value){this.lowerCanvasEl.style[prop]=value;if(this.upperCanvasEl){this.upperCanvasEl.style[prop]=value}if(this.wrapperEl){this.wrapperEl.style[prop]=value}return this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(vpt){var activeGroup=this.getActiveGroup();this.viewportTransform=vpt;this.renderAll();for(var i=0,len=this._objects.length;i");return markup.join("")},_setSVGPreamble:function(markup,options){if(!options.suppressPreamble){markup.push('','\n')}},_setSVGHeader:function(markup,options){var width,height,vpt;if(options.viewBox){width=options.viewBox.width;height=options.viewBox.height}else{width=this.width;height=this.height;if(!this.svgViewportTransformation){vpt=this.viewportTransform;width/=vpt[0];height/=vpt[3]}}markup.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(markup,reviver){for(var i=0,objects=this.getObjects(),len=objects.length;i")}else if(this[property]&&property==="overlayColor"){markup.push('")}},sendToBack:function(object){removeFromArray(this._objects,object);this._objects.unshift(object);return this.renderAll&&this.renderAll()},bringToFront:function(object){removeFromArray(this._objects,object);this._objects.push(object);return this.renderAll&&this.renderAll()},sendBackwards:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==0){var newIdx=this._findNewLowerIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx-1;i>=0;--i){var isIntersecting=object.intersectsWithObject(this._objects[i])||object.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(object);if(isIntersecting){newIdx=i;break}}}else{newIdx=idx-1}return newIdx},bringForward:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==this._objects.length-1){var newIdx=this._findNewUpperIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx+1;i"}});extend(fabric.StaticCanvas.prototype,fabric.Observable);extend(fabric.StaticCanvas.prototype,fabric.Collection);extend(fabric.StaticCanvas.prototype,fabric.DataURLExporter);extend(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(methodName){var el=fabric.util.createCanvasElement();if(!el||!el.getContext){return null}var ctx=el.getContext("2d");if(!ctx){return null}switch(methodName){case"getImageData":return typeof ctx.getImageData!=="undefined";case"setLineDash":return typeof ctx.setLineDash!=="undefined";case"toDataURL":return typeof el.toDataURL!=="undefined";case"toDataURLWithQuality":try{el.toDataURL("image/jpeg",0);return true}catch(e){}return false;default:return null}}});fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject})();fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",strokeDashArray:null,setShadow:function(options){this.shadow=new fabric.Shadow(options);return this},_setBrushStyles:function(){var ctx=this.canvas.contextTop;ctx.strokeStyle=this.color;ctx.lineWidth=this.width;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin;if(this.strokeDashArray&&fabric.StaticCanvas.supports("setLineDash")){ctx.setLineDash(this.strokeDashArray)}},_setShadow:function(){if(!this.shadow){return}var ctx=this.canvas.contextTop;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur;ctx.shadowOffsetX=this.shadow.offsetX;ctx.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var ctx=this.canvas.contextTop;ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0}});(function(){fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(canvas){this.canvas=canvas;this._points=[]},onMouseDown:function(pointer){this._prepareForDrawing(pointer);this._captureDrawingPath(pointer);this._render()},onMouseMove:function(pointer){this._captureDrawingPath(pointer);this.canvas.clearContext(this.canvas.contextTop);this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(pointer){var p=new fabric.Point(pointer.x,pointer.y);this._reset();this._addPoint(p);this.canvas.contextTop.moveTo(p.x,p.y)},_addPoint:function(point){this._points.push(point)},_reset:function(){this._points.length=0;this._setBrushStyles();this._setShadow()},_captureDrawingPath:function(pointer){var pointerPoint=new fabric.Point(pointer.x,pointer.y);this._addPoint(pointerPoint)},_render:function(){ +var ctx=this.canvas.contextTop,v=this.canvas.viewportTransform,p1=this._points[0],p2=this._points[1];ctx.save();ctx.transform(v[0],v[1],v[2],v[3],v[4],v[5]);ctx.beginPath();if(this._points.length===2&&p1.x===p2.x&&p1.y===p2.y){p1.x-=.5;p2.x+=.5}ctx.moveTo(p1.x,p1.y);for(var i=1,len=this._points.length;itarget.padding){if(localMouse.x<0){localMouse.x+=target.padding}else{localMouse.x-=target.padding}}else{localMouse.x=0}if(abs(localMouse.y)>target.padding){if(localMouse.y<0){localMouse.y+=target.padding}else{localMouse.y-=target.padding}}else{localMouse.y=0}},_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);if(angle<0){angle=360+angle}t.target.angle=angle%360},setCursor:function(value){this.upperCanvasEl.style.cursor=value},_resetObjectTransform:function(target){target.scaleX=1;target.scaleY=1;target.setAngle(0)},_drawSelection:function(){var ctx=this.contextTop,groupSelector=this._groupSelector,left=groupSelector.left,top=groupSelector.top,aleft=abs(left),atop=abs(top);ctx.fillStyle=this.selectionColor;ctx.fillRect(groupSelector.ex-(left>0?0:-left),groupSelector.ey-(top>0?0:-top),aleft,atop);ctx.lineWidth=this.selectionLineWidth;ctx.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var px=groupSelector.ex+STROKE_OFFSET-(left>0?0:aleft),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)}},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e,true))},findTarget:function(e,skipGroup){if(this.skipTargetFind){return}if(this._isLastRenderedObject(e)){return this.lastRenderedObjectWithControlsAboveOverlay}var activeGroup=this.getActiveGroup();if(activeGroup&&!skipGroup&&this.containsPoint(e,activeGroup)){return activeGroup}var target=this._searchPossibleTargets(e);this._fireOverOutEvents(target);return target},_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}},_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}}},_searchPossibleTargets:function(e){var target,pointer=this.getPointer(e,true),i=this._objects.length;while(i--){if(!this._objects[i].group&&this._checkTarget(e,this._objects[i],pointer)){this.relatedTarget=this._objects[i];target=this._objects[i];break}}return target},getPointer:function(e,ignoreZoom,upperCanvasEl){if(!upperCanvasEl){upperCanvasEl=this.upperCanvasEl}var pointer=getPointer(e,upperCanvasEl),bounds=upperCanvasEl.getBoundingClientRect(),boundsWidth=bounds.width||0,boundsHeight=bounds.height||0,cssScale;if(!boundsWidth||!boundsHeight){if("top"in bounds&&"bottom"in bounds){boundsHeight=Math.abs(bounds.top-bounds.bottom)}if("right"in bounds&&"left"in bounds){boundsWidth=Math.abs(bounds.right-bounds.left)}}this.calcOffset();pointer.x=pointer.x-this._offset.left;pointer.y=pointer.y-this._offset.top;if(!ignoreZoom){pointer=fabric.util.transformPoint(pointer,fabric.util.invertTransform(this.viewportTransform))}if(boundsWidth===0||boundsHeight===0){cssScale={width:1,height:1}}else{cssScale={width:upperCanvasEl.width/boundsWidth,height:upperCanvasEl.height/boundsHeight}}return{x:pointer.x*cssScale.width,y:pointer.y*cssScale.height}},_createUpperCanvas:function(){var lowerCanvasClass=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement();fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+lowerCanvasClass);this.wrapperEl.appendChild(this.upperCanvasEl);this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl);this._applyCanvasStyle(this.upperCanvasEl);this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement();this.cacheCanvasEl.setAttribute("width",this.width);this.cacheCanvasEl.setAttribute("height",this.height);this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass});fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"});fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(element){var width=this.getWidth()||element.width,height=this.getHeight()||element.height;fabric.util.setStyle(element,{position:"absolute",width:width+"px",height:height+"px",left:0,top:0});element.width=width;element.height=height;fabric.util.makeElementUnselectable(element)},_copyCanvasStyle:function(fromEl,toEl){toEl.style.cssText=fromEl.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(object){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=object;object.set("active",true)},setActiveObject:function(object,e){this._setActiveObject(object);this.renderAll();this.fire("object:selected",{target:object,e:e});object.fire("selected",{e:e});return this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=null},discardActiveObject:function(e){this._discardActiveObject();this.renderAll();this.fire("selection:cleared",{e:e});return this},_setActiveGroup:function(group){this._activeGroup=group;if(group){group.set("active",true)}},setActiveGroup:function(group,e){this._setActiveGroup(group);if(group){this.fire("object:selected",{target:group,e:e});group.fire("selected",{e:e})}return this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var g=this.getActiveGroup();if(g){g.destroy()}this.setActiveGroup(null)},discardActiveGroup:function(e){this._discardActiveGroup();this.fire("selection:cleared",{e:e});return this},deactivateAll:function(){var allObjects=this.getObjects(),i=0,len=allObjects.length;for(;i1){ +return}var groupSelector=this._groupSelector;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})},_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()},_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)}},_fire:function(eventName,target,e){this.fire("object:"+eventName,{target:target,e:e});target.fire(eventName,{e:e})},_beforeScaleTransform:function(e,transform){if(transform.action==="scale"||transform.action==="scaleX"||transform.action==="scaleY"){var centerTransform=this._shouldCenterTransform(e,transform.target);if(centerTransform&&(transform.originX!=="center"||transform.originY!=="center")||!centerTransform&&transform.originX==="center"&&transform.originY==="center"){this._resetCurrentTransform(e);transform.reset=true}}},_onScale:function(e,transform,x,y){if((e.shiftKey||this.uniScaleTransform)&&!transform.target.get("lockUniScaling")){transform.currentAction="scale";this._scaleObject(x,y)}else{if(!transform.reset&&transform.currentAction==="scale"){this._resetCurrentTransform(e,transform.target)}transform.currentAction="scaleEqually";this._scaleObject(x,y,"equally")}},_setCursorFromEvent:function(e,target){if(!target||!target.selectable){this.setCursor(this.defaultCursor);return false}else{var activeGroup=this.getActiveGroup(),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},_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}},_getRotatedCornerCursor:function(corner,target){var n=Math.round(target.getAngle()%360/45);if(n<0){n+=8}n+=cursorOffset[corner];n%=8;return this.cursorMap[n]}})})();(function(){var min=Math.min,max=Math.max;fabric.util.object.extend(fabric.Canvas.prototype,{_shouldGroup:function(e,target){var activeObject=this.getActiveObject();return e.shiftKey&&(this.getActiveGroup()||activeObject&&activeObject!==target)&&this.selection},_handleGrouping:function(e,target){if(target===this.getActiveGroup()){target=this.findTarget(e,true);if(!target||target.isType("group")){return}}if(this.getActiveGroup()){this._updateActiveGroup(target,e)}else{this._createActiveGroup(target,e)}if(this._activeGroup){this._activeGroup.saveCoords()}},_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){this.discardActiveGroup(e);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)},_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)},_createGroup:function(target){var objects=this.getObjects(),isActiveLower=objects.indexOf(this._activeObject)1){group=new fabric.Group(group.reverse(),{canvas:this});group.addWithUpdate();this.setActiveGroup(group,e);group.saveCoords();this.fire("selection:created",{target:group});this.renderAll()}},_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);if(isClick){break}}}return group},_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)}this._groupSelector=null;this._currentTransform=null}})})();fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(options){options||(options={});var format=options.format||"png",quality=options.quality||1,multiplier=options.multiplier||1,cropping={left:options.left,top:options.top,width:options.width,height:options.height};if(multiplier!==1){return this.__toDataURLWithMultiplier(format,quality,cropping,multiplier)}else{return this.__toDataURL(format,quality,cropping)}},__toDataURL:function(format,quality,cropping){this.renderAll(true);var canvasEl=this.upperCanvasEl||this.lowerCanvasEl,croppedCanvasEl=this.__getCroppedCanvas(canvasEl,cropping);if(format==="jpg"){format="jpeg"}var data=fabric.StaticCanvas.supports("toDataURLWithQuality")?(croppedCanvasEl||canvasEl).toDataURL("image/"+format,quality):(croppedCanvasEl||canvasEl).toDataURL("image/"+format);this.contextTop&&this.clearContext(this.contextTop);this.renderAll();if(croppedCanvasEl){croppedCanvasEl=null}return data},__getCroppedCanvas:function(canvasEl,cropping){var croppedCanvasEl,croppedCtx,shouldCrop="left"in cropping||"top"in cropping||"width"in cropping||"height"in cropping;if(shouldCrop){croppedCanvasEl=fabric.util.createCanvasElement();croppedCtx=croppedCanvasEl.getContext("2d");croppedCanvasEl.width=cropping.width||this.width;croppedCanvasEl.height=cropping.height||this.height;croppedCtx.drawImage(canvasEl,-cropping.left||0,-cropping.top||0)}return croppedCanvasEl},__toDataURLWithMultiplier:function(format,quality,cropping,multiplier){var origWidth=this.getWidth(),origHeight=this.getHeight(),scaledWidth=origWidth*multiplier,scaledHeight=origHeight*multiplier,activeObject=this.getActiveObject(),activeGroup=this.getActiveGroup(),ctx=this.contextTop||this.contextContainer;if(multiplier>1){this.setWidth(scaledWidth).setHeight(scaledHeight)}ctx.scale(multiplier,multiplier);if(cropping.left){cropping.left*=multiplier}if(cropping.top){cropping.top*=multiplier}if(cropping.width){cropping.width*=multiplier}else if(multiplier<1){cropping.width=scaledWidth}if(cropping.height){cropping.height*=multiplier}else if(multiplier<1){cropping.height=scaledHeight}if(activeGroup){this._tempRemoveBordersControlsFromGroup(activeGroup)}else if(activeObject&&this.deactivateAll){this.deactivateAll()}this.renderAll(true);var data=this.__toDataURL(format,quality,cropping);this.width=origWidth;this.height=origHeight;ctx.scale(1/multiplier,1/multiplier);this.setWidth(origWidth).setHeight(origHeight);if(activeGroup){this._restoreBordersControlsOnGroup(activeGroup)}else if(activeObject&&this.setActiveObject){this.setActiveObject(activeObject)}this.contextTop&&this.clearContext(this.contextTop);this.renderAll();return data},toDataURLWithMultiplier:function(format,multiplier,quality){return this.toDataURL({format:format,multiplier:multiplier,quality:quality})},_tempRemoveBordersControlsFromGroup:function(group){group.origHasControls=group.hasControls;group.origBorderColor=group.borderColor;group.hasControls=true;group.borderColor="rgba(0,0,0,0)";group.forEachObject(function(o){o.origBorderColor=o.borderColor;o.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(group){group.hideControls=group.origHideControls;group.borderColor=group.origBorderColor;group.forEachObject(function(o){o.borderColor=o.origBorderColor;delete o.origBorderColor})}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(json,callback,reviver){return this.loadFromJSON(json,callback,reviver)},loadFromJSON:function(json,callback,reviver){if(!json){return}var serialized=typeof json==="string"?JSON.parse(json):json;this.clear();var _this=this;this._enlivenObjects(serialized.objects,function(){_this._setBgOverlay(serialized,callback)},reviver);return this},_setBgOverlay:function(serialized,callback){var _this=this,loaded={backgroundColor:false,overlayColor:false,backgroundImage:false,overlayImage:false};if(!serialized.backgroundImage&&!serialized.overlayImage&&!serialized.background&&!serialized.overlay){callback&&callback();return}var cbIfLoaded=function(){if(loaded.backgroundImage&&loaded.overlayImage&&loaded.backgroundColor&&loaded.overlayColor){_this.renderAll();callback&&callback()}};this.__setBgOverlay("backgroundImage",serialized.backgroundImage,loaded,cbIfLoaded);this.__setBgOverlay("overlayImage",serialized.overlayImage,loaded,cbIfLoaded);this.__setBgOverlay("backgroundColor",serialized.background,loaded,cbIfLoaded);this.__setBgOverlay("overlayColor",serialized.overlay,loaded,cbIfLoaded);cbIfLoaded()},__setBgOverlay:function(property,value,loaded,callback){var _this=this;if(!value){loaded[property]=true;return}if(property==="backgroundImage"||property==="overlayImage"){fabric.Image.fromObject(value,function(img){_this[property]=img;loaded[property]=true;callback&&callback()})}else{this["set"+fabric.util.string.capitalize(property,true)](value,function(){loaded[property]=true;callback&&callback()})}},_enlivenObjects:function(objects,callback,reviver){var _this=this;if(!objects||objects.length===0){callback&&callback();return}var renderOnAddRemove=this.renderOnAddRemove;this.renderOnAddRemove=false;fabric.util.enlivenObjects(objects,function(enlivenedObjects){enlivenedObjects.forEach(function(obj,index){_this.insertAt(obj,index,true)});_this.renderOnAddRemove=renderOnAddRemove;callback&&callback()},null,reviver)},_toDataURL:function(format,callback){this.clone(function(clone){callback(clone.toDataURL(format))})},_toDataURLWithMultiplier:function(format,multiplier,callback){this.clone(function(clone){callback(clone.toDataURLWithMultiplier(format,multiplier))})},clone:function(callback,properties){var data=JSON.stringify(this.toJSON(properties));this.cloneWithoutData(function(clone){clone.loadFromJSON(data,function(){callback&&callback(clone)})})},cloneWithoutData:function(callback){var el=fabric.document.createElement("canvas");el.width=this.getWidth();el.height=this.getHeight();var clone=new fabric.Canvas(el);clone.clipTo=this.clipTo;if(this.backgroundImage){clone.setBackgroundImage(this.backgroundImage.src,function(){clone.renderAll();callback&&callback(clone)});clone.backgroundImageOpacity=this.backgroundImageOpacity;clone.backgroundImageStretch=this.backgroundImageStretch}else{callback&&callback(clone)}}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,toFixed=fabric.util.toFixed,capitalize=fabric.util.string.capitalize,degreesToRadians=fabric.util.degreesToRadians,supportsLineDash=fabric.StaticCanvas.supports("setLineDash");if(fabric.Object){return}fabric.Object=fabric.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:false,flipY:false,opacity:1,angle:0,cornerSize:12,transparentCorners:true,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:false,centeredRotation:true,fill:"rgb(0,0,0)",fillRule:"nonzero",globalCompositeOperation:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:true,evented:true,visible:true,hasControls:true,hasBorders:true,hasRotatingPoint:true,rotatingPointOffset:40,perPixelTargetFind:false,includeDefaultValues:true,clipTo:null,lockMovementX:false,lockMovementY:false,lockRotation:false,lockScalingX:false,lockScalingY:false,lockUniScaling:false,lockScalingFlip:false,stateProperties:("top left width height scaleX scaleY flipX flipY originX originY transformMatrix "+"stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit "+"angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor").split(" "),initialize:function(options){if(options){this.setOptions(options)}},_initGradient:function(options){if(options.fill&&options.fill.colorStops&&!(options.fill instanceof fabric.Gradient)){this.set("fill",new fabric.Gradient(options.fill))}},_initPattern:function(options){if(options.fill&&options.fill.source&&!(options.fill instanceof fabric.Pattern)){this.set("fill",new fabric.Pattern(options.fill))}if(options.stroke&&options.stroke.source&&!(options.stroke instanceof fabric.Pattern)){this.set("stroke",new fabric.Pattern(options.stroke))}},_initClipping:function(options){if(!options.clipTo||typeof options.clipTo!=="string"){return}var functionBody=fabric.util.getFunctionBody(options.clipTo);if(typeof functionBody!=="undefined"){this.clipTo=new Function("ctx",functionBody)}},setOptions:function(options){for(var prop in options){this.set(prop,options[prop])}this._initGradient(options);this._initPattern(options);this._initClipping(options)},transform:function(ctx,fromLeft){var center=fromLeft?this._getLeftTopCoords():this.getCenterPoint();ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));ctx.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(propertiesToInclude){var NUM_FRACTION_DIGITS=fabric.Object.NUM_FRACTION_DIGITS,object={type:this.type,originX:this.originX,originY:this.originY,left:toFixed(this.left,NUM_FRACTION_DIGITS),top:toFixed(this.top,NUM_FRACTION_DIGITS),width:toFixed(this.width,NUM_FRACTION_DIGITS),height:toFixed(this.height,NUM_FRACTION_DIGITS),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:toFixed(this.strokeWidth,NUM_FRACTION_DIGITS),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:toFixed(this.strokeMiterLimit,NUM_FRACTION_DIGITS),scaleX:toFixed(this.scaleX,NUM_FRACTION_DIGITS),scaleY:toFixed(this.scaleY,NUM_FRACTION_DIGITS),angle:toFixed(this.getAngle(),NUM_FRACTION_DIGITS),flipX:this.flipX,flipY:this.flipY,opacity:toFixed(this.opacity,NUM_FRACTION_DIGITS),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor,fillRule:this.fillRule,globalCompositeOperation:this.globalCompositeOperation};if(!this.includeDefaultValues){object=this._removeDefaultValues(object)}fabric.util.populateWithProperties(this,object,propertiesToInclude);return object},toDatalessObject:function(propertiesToInclude){return this.toObject(propertiesToInclude)},_removeDefaultValues:function(object){var prototype=fabric.util.getKlass(object.type).prototype,stateProperties=prototype.stateProperties;stateProperties.forEach(function(prop){if(object[prop]===prototype[prop]){delete object[prop]}});return object},toString:function(){return"#"},get:function(property){return this[property]},_setObject:function(obj){for(var prop in obj){this._set(prop,obj[prop])}},set:function(key,value){if(typeof key==="object"){this._setObject(key)}else{if(typeof value==="function"&&key!=="clipTo"){this._set(key,value(this.get(key)))}else{this._set(key,value)}}return this},_set:function(key,value){var shouldConstrainValue=key==="scaleX"||key==="scaleY";if(shouldConstrainValue){value=this._constrainScale(value)}if(key==="scaleX"&&value<0){this.flipX=!this.flipX;value*=-1}else if(key==="scaleY"&&value<0){this.flipY=!this.flipY;value*=-1}else if(key==="width"||key==="height"){this.minScaleLimit=toFixed(Math.min(.1,1/Math.max(this.width,this.height)),2)}else if(key==="shadow"&&value&&!(value instanceof fabric.Shadow)){value=new fabric.Shadow(value)}this[key]=value;return this},toggle:function(property){var value=this.get(property);if(typeof value==="boolean"){this.set(property,!value)}return this},setSourcePath:function(value){this.sourcePath=value;return this},getViewportTransform:function(){if(this.canvas&&this.canvas.viewportTransform){return this.canvas.viewportTransform}return[1,0,0,1,0,0]},render:function(ctx,noTransform){if(this.width===0&&this.height===0||!this.visible){return}ctx.save();this._setupCompositeOperation(ctx);if(!noTransform){this.transform(ctx)}this._setStrokeStyles(ctx);this._setFillStyles(ctx);if(this.transformMatrix){ctx.transform.apply(ctx,this.transformMatrix)}this._setOpacity(ctx);this._setShadow(ctx);this.clipTo&&fabric.util.clipContext(this,ctx);this._render(ctx,noTransform);this.clipTo&&ctx.restore();this._removeShadow(ctx);this._restoreCompositeOperation(ctx);ctx.restore()},_setOpacity:function(ctx){if(this.group){this.group._setOpacity(ctx)}ctx.globalAlpha*=this.opacity},_setStrokeStyles:function(ctx){if(this.stroke){ctx.lineWidth=this.strokeWidth;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin;ctx.miterLimit=this.strokeMiterLimit;ctx.strokeStyle=this.stroke.toLive?this.stroke.toLive(ctx,this):this.stroke}},_setFillStyles:function(ctx){if(this.fill){ctx.fillStyle=this.fill.toLive?this.fill.toLive(ctx,this):this.fill}},_renderControls:function(ctx,noTransform){if(!this.active||noTransform){return}var vpt=this.getViewportTransform();ctx.save();var center;if(this.group){center=fabric.util.transformPoint(this.group.getCenterPoint(),vpt);ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.group.angle))}center=fabric.util.transformPoint(this.getCenterPoint(),vpt,null!=this.group);if(this.group){center.x*=this.group.scaleX;center.y*=this.group.scaleY}ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));this.drawBorders(ctx);this.drawControls(ctx);ctx.restore()},_setShadow:function(ctx){if(!this.shadow){return}var multX=this.canvas&&this.canvas.viewportTransform[0]||1,multY=this.canvas&&this.canvas.viewportTransform[3]||1;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur*(multX+multY)*(this.scaleX+this.scaleY)/4;ctx.shadowOffsetX=this.shadow.offsetX*multX*this.scaleX;ctx.shadowOffsetY=this.shadow.offsetY*multY*this.scaleY},_removeShadow:function(ctx){if(!this.shadow){return}ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0},_renderFill:function(ctx){if(!this.fill){return}ctx.save();if(this.fill.gradientTransform){var g=this.fill.gradientTransform;ctx.transform.apply(ctx,g)}if(this.fill.toLive){ctx.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0)}if(this.fillRule==="evenodd"){ctx.fill("evenodd")}else{ctx.fill()}ctx.restore();if(this.shadow&&!this.shadow.affectStroke){this._removeShadow(ctx)}},_renderStroke:function(ctx){if(!this.stroke||this.strokeWidth===0){return}ctx.save();if(this.strokeDashArray){if(1&this.strokeDashArray.length){this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray)}if(supportsLineDash){ctx.setLineDash(this.strokeDashArray);this._stroke&&this._stroke(ctx)}else{this._renderDashedStroke&&this._renderDashedStroke(ctx)}ctx.stroke()}else{if(this.stroke.gradientTransform){var g=this.stroke.gradientTransform;ctx.transform.apply(ctx,g)}this._stroke?this._stroke(ctx):ctx.stroke()}this._removeShadow(ctx);ctx.restore()},clone:function(callback,propertiesToInclude){if(this.constructor.fromObject){return this.constructor.fromObject(this.toObject(propertiesToInclude),callback)}return new fabric.Object(this.toObject(propertiesToInclude))},cloneAsImage:function(callback){var dataUrl=this.toDataURL();fabric.util.loadImage(dataUrl,function(img){if(callback){callback(new fabric.Image(img))}});return this},toDataURL:function(options){options||(options={});var el=fabric.util.createCanvasElement(),boundingRect=this.getBoundingRect();el.width=boundingRect.width;el.height=boundingRect.height;fabric.util.wrapElement(el,"div");var canvas=new fabric.StaticCanvas(el);if(options.format==="jpg"){options.format="jpeg"}if(options.format==="jpeg"){canvas.backgroundColor="#fff"}var origParams={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",false);this.setPositionByOrigin(new fabric.Point(el.width/2,el.height/2),"center","center");var originalCanvas=this.canvas;canvas.add(this);var data=canvas.toDataURL(options);this.set(origParams).setCoords();this.canvas=originalCanvas;canvas.dispose();canvas=null;return data},isType:function(type){return this.type===type},complexity:function(){return 0},toJSON:function(propertiesToInclude){return this.toObject(propertiesToInclude)},setGradient:function(property,options){options||(options={});var gradient={colorStops:[]};gradient.type=options.type||(options.r1||options.r2?"radial":"linear");gradient.coords={x1:options.x1,y1:options.y1,x2:options.x2,y2:options.y2};if(options.r1||options.r2){gradient.coords.r1=options.r1;gradient.coords.r2=options.r2}for(var position in options.colorStops){var color=new fabric.Color(options.colorStops[position]);gradient.colorStops.push({offset:position,color:color.toRgb(),opacity:color.getAlpha()})}return this.set(property,fabric.Gradient.forObject(this,gradient))},setPatternFill:function(options){return this.set("fill",new fabric.Pattern(options))},setShadow:function(options){return this.set("shadow",options?new fabric.Shadow(options):null)},setColor:function(color){this.set("fill",color);return this},setAngle:function(angle){var shouldCenterOrigin=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;if(shouldCenterOrigin){this._setOriginToCenter()}this.set("angle",angle);if(shouldCenterOrigin){this._resetOrigin()}return this},centerH:function(){this.canvas.centerObjectH(this);return this},centerV:function(){this.canvas.centerObjectV(this);return this},center:function(){this.canvas.centerObject(this);return this},remove:function(){this.canvas.remove(this);return this},getLocalPointer:function(e,pointer){pointer=pointer||this.canvas.getPointer(e);var objectLeftTop=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:pointer.x-objectLeftTop.x,y:pointer.y-objectLeftTop.y}},_setupCompositeOperation:function(ctx){if(this.globalCompositeOperation){this._prevGlobalCompositeOperation=ctx.globalCompositeOperation;ctx.globalCompositeOperation=this.globalCompositeOperation}},_restoreCompositeOperation:function(ctx){if(this.globalCompositeOperation&&this._prevGlobalCompositeOperation){ctx.globalCompositeOperation=this._prevGlobalCompositeOperation}}});fabric.util.createAccessors(fabric.Object);fabric.Object.prototype.rotate=fabric.Object.prototype.setAngle;extend(fabric.Object.prototype,fabric.Observable);fabric.Object.NUM_FRACTION_DIGITS=2;fabric.Object.__uid=0})(typeof exports!=="undefined"?exports:this);(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(point,originX,originY){var cx=point.x,cy=point.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){cx=point.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){cx=point.x-(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){cy=point.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){cy=point.y-(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(cx,cy),point,degreesToRadians(this.angle))},translateToOriginPoint:function(center,originX,originY){var x=center.x,y=center.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(x,y),center,degreesToRadians(this.angle))},getCenterPoint:function(){var leftTop=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(leftTop,this.originX,this.originY)},getPointByOrigin:function(originX,originY){var center=this.getCenterPoint();return this.translateToOriginPoint(center,originX,originY)},toLocalPoint:function(point,originX,originY){var center=this.getCenterPoint(),strokeWidth=this.stroke?this.strokeWidth:0,x,y;if(originX&&originY){if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else{x=center.x}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else{y=center.y}}else{x=this.left;y=this.top}return fabric.util.rotatePoint(new fabric.Point(point.x,point.y),center,-degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x,y))},setPositionByOrigin:function(pos,originX,originY){var center=this.translateToCenterPoint(pos,originX,originY),position=this.translateToOriginPoint(center,this.originX,this.originY);this.set("left",position.x);this.set("top",position.y)},adjustPosition:function(to){var angle=degreesToRadians(this.angle),hypotHalf=this.getWidth()/2,xHalf=Math.cos(angle)*hypotHalf,yHalf=Math.sin(angle)*hypotHalf,hypotFull=this.getWidth(),xFull=Math.cos(angle)*hypotFull,yFull=Math.sin(angle)*hypotFull;if(this.originX==="center"&&to==="left"||this.originX==="right"&&to==="center"){this.left-=xHalf;this.top-=yHalf}else if(this.originX==="left"&&to==="center"||this.originX==="center"&&to==="right"){this.left+=xHalf;this.top+=yHalf}else if(this.originX==="left"&&to==="right"){this.left+=xFull;this.top+=yFull}else if(this.originX==="right"&&to==="left"){this.left-=xFull;this.top-=yFull}this.setCoords();this.originX=to},_setOriginToCenter:function(){this._originalOriginX=this.originX;this._originalOriginY=this.originY;var center=this.getCenterPoint();this.originX="center";this.originY="center";this.left=center.x;this.top=center.y},_resetOrigin:function(){var originPoint=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX;this.originY=this._originalOriginY;this.left=originPoint.x;this.top=originPoint.y;this._originalOriginX=null;this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})})();(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(pointTL,pointBR){var oCoords=this.oCoords,tl=new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr=new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl=new fabric.Point(oCoords.bl.x,oCoords.bl.y),br=new fabric.Point(oCoords.br.x,oCoords.br.y),intersection=fabric.Intersection.intersectPolygonRectangle([tl,tr,br,bl],pointTL,pointBR);return intersection.status==="Intersection"},intersectsWithObject:function(other){function getCoords(oCoords){return{tl:new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr:new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl:new fabric.Point(oCoords.bl.x,oCoords.bl.y),br:new fabric.Point(oCoords.br.x,oCoords.br.y)}}var thisCoords=getCoords(this.oCoords),otherCoords=getCoords(other.oCoords),intersection=fabric.Intersection.intersectPolygonPolygon([thisCoords.tl,thisCoords.tr,thisCoords.br,thisCoords.bl],[otherCoords.tl,otherCoords.tr,otherCoords.br,otherCoords.bl]);return intersection.status==="Intersection"},isContainedWithinObject:function(other){var boundingRect=other.getBoundingRect(),point1=new fabric.Point(boundingRect.left,boundingRect.top),point2=new fabric.Point(boundingRect.left+boundingRect.width,boundingRect.top+boundingRect.height);return this.isContainedWithinRect(point1,point2)},isContainedWithinRect:function(pointTL,pointBR){var boundingRect=this.getBoundingRect();return boundingRect.left>=pointTL.x&&boundingRect.left+boundingRect.width<=pointBR.x&&boundingRect.top>=pointTL.y&&boundingRect.top+boundingRect.height<=pointBR.y},containsPoint:function(point){var lines=this._getImageLines(this.oCoords),xPoints=this._findCrossPoints(point,lines);return xPoints!==0&&xPoints%2===1},_getImageLines:function(oCoords){return{topline:{o:oCoords.tl,d:oCoords.tr},rightline:{o:oCoords.tr,d:oCoords.br},bottomline:{o:oCoords.br,d:oCoords.bl},leftline:{o:oCoords.bl,d:oCoords.tl}}},_findCrossPoints:function(point,oCoords){var b1,b2,a1,a2,xi,yi,xcount=0,iLine;for(var lineKey in oCoords){iLine=oCoords[lineKey];if(iLine.o.y=point.y&&iLine.d.y>=point.y){continue}if(iLine.o.x===iLine.d.x&&iLine.o.x>=point.x){xi=iLine.o.x;yi=point.y}else{b1=0;b2=(iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);a1=point.y-b1*point.x;a2=iLine.o.y-b2*iLine.o.x;xi=-(a1-a2)/(b1-b2);yi=a1+b1*xi}if(xi>=point.x){xcount+=1}if(xcount===2){break}}return xcount},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var xCoords=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],minX=fabric.util.array.min(xCoords),maxX=fabric.util.array.max(xCoords),width=Math.abs(minX-maxX),yCoords=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],minY=fabric.util.array.min(yCoords),maxY=fabric.util.array.max(yCoords),height=Math.abs(minY-maxY);return{left:minX,top:minY,width:width,height:height}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(value){if(Math.abs(value)\n');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Line.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" "));fabric.Line.fromElement=function(element,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Line.ATTRIBUTE_NAMES),points=[parsedAttributes.x1||0,parsedAttributes.y1||0,parsedAttributes.x2||0,parsedAttributes.y2||0];return new fabric.Line(points,extend(parsedAttributes,options))};fabric.Line.fromObject=function(object){var points=[object.x1,object.y1,object.x2,object.y2];return new fabric.Line(points,object)};function makeEdgeToOriginGetter(propertyNames,originValues){var origin=propertyNames.origin,axis1=propertyNames.axis1,axis2=propertyNames.axis2,dimension=propertyNames.dimension,nearest=originValues.nearest,center=originValues.center,farthest=originValues.farthest;return function(){switch(this.get(origin)){case nearest:return Math.min(this.get(axis1),this.get(axis2));case center:return Math.min(this.get(axis1),this.get(axis2))+.5*this.get(dimension);case farthest:return Math.max(this.get(axis1),this.get(axis2))}}}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),pi=Math.PI,extend=fabric.util.object.extend;if(fabric.Circle){fabric.warn("fabric.Circle is already defined.");return}fabric.Circle=fabric.util.createClass(fabric.Object,{type:"circle",radius:0,startAngle:0,endAngle:pi*2,initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("radius",options.radius||0);this.startAngle=options.startAngle||this.startAngle;this.endAngle=options.endAngle||this.endAngle},_set:function(key,value){this.callSuper("_set",key,value);if(key==="radius"){this.setRadius(value)}return this},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{radius:this.get("radius"),startAngle:this.startAngle,endAngle:this.endAngle})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),x=0,y=0,angle=(this.endAngle-this.startAngle)%(2*pi);if(angle===0){if(this.group&&this.group.type==="path-group"){x=this.left+this.radius;y=this.top+this.radius}markup.push("\n')}else{var startX=Math.cos(this.startAngle)*this.radius,startY=Math.sin(this.startAngle)*this.radius,endX=Math.cos(this.endAngle)*this.radius,endY=Math.sin(this.endAngle)*this.radius,largeFlag=angle>pi?"1":"0";markup.push('\n')}return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx,noTransform){ctx.beginPath();ctx.arc(noTransform?this.left+this.radius:0,noTransform?this.top+this.radius:0,this.radius,this.startAngle,this.endAngle,false);this._renderFill(ctx);this._renderStroke(ctx)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(value){this.radius=value;this.set("width",value*2).set("height",value*2)},complexity:function(){return 1}});fabric.Circle.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy r".split(" "));fabric.Circle.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Circle.ATTRIBUTE_NAMES);if(!isValidRadius(parsedAttributes)){throw new Error("value of `r` attribute is required and can not be negative")}parsedAttributes.left=parsedAttributes.left||0;parsedAttributes.top=parsedAttributes.top||0;var obj=new fabric.Circle(extend(parsedAttributes,options));obj.left-=obj.radius;obj.top-=obj.radius;return obj};function isValidRadius(attributes){return"radius"in attributes&&attributes.radius>=0}fabric.Circle.fromObject=function(object){return new fabric.Circle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Triangle){fabric.warn("fabric.Triangle is already defined");return}fabric.Triangle=fabric.util.createClass(fabric.Object,{type:"triangle",initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("width",options.width||100).set("height",options.height||100)},_render:function(ctx){var widthBy2=this.width/2,heightBy2=this.height/2;ctx.beginPath();ctx.moveTo(-widthBy2,heightBy2);ctx.lineTo(0,-heightBy2);ctx.lineTo(widthBy2,heightBy2);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_renderDashedStroke:function(ctx){var widthBy2=this.width/2,heightBy2=this.height/2;ctx.beginPath();fabric.util.drawDashedLine(ctx,-widthBy2,heightBy2,0,-heightBy2,this.strokeDashArray);fabric.util.drawDashedLine(ctx,0,-heightBy2,widthBy2,heightBy2,this.strokeDashArray);fabric.util.drawDashedLine(ctx,widthBy2,heightBy2,-widthBy2,heightBy2,this.strokeDashArray);ctx.closePath()},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),widthBy2=this.width/2,heightBy2=this.height/2,points=[-widthBy2+" "+heightBy2,"0 "+-heightBy2,widthBy2+" "+heightBy2].join(",");markup.push("');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Triangle.fromObject=function(object){return new fabric.Triangle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),piBy2=Math.PI*2,extend=fabric.util.object.extend;if(fabric.Ellipse){fabric.warn("fabric.Ellipse is already defined.");return}fabric.Ellipse=fabric.util.createClass(fabric.Object,{type:"ellipse",rx:0,ry:0,initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("rx",options.rx||0);this.set("ry",options.ry||0)},_set:function(key,value){this.callSuper("_set",key,value);switch(key){case"rx":this.rx=value;this.set("width",value*2);break;case"ry":this.ry=value;this.set("height",value*2);break}return this},getRx:function(){return this.get("rx")*this.get("scaleX")},getRy:function(){return this.get("ry")*this.get("scaleY")},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),x=0,y=0;if(this.group&&this.group.type==="path-group"){x=this.left+this.rx;y=this.top+this.ry}markup.push("\n');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx,noTransform){ctx.beginPath();ctx.save();ctx.transform(1,0,0,this.ry/this.rx,0,0);ctx.arc(noTransform?this.left+this.rx:0,noTransform?(this.top+this.ry)*this.rx/this.ry:0,this.rx,0,piBy2,false);ctx.restore();this._renderFill(ctx);this._renderStroke(ctx)},complexity:function(){return 1}});fabric.Ellipse.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" "));fabric.Ellipse.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Ellipse.ATTRIBUTE_NAMES);parsedAttributes.left=parsedAttributes.left||0;parsedAttributes.top=parsedAttributes.top||0;var ellipse=new fabric.Ellipse(extend(parsedAttributes,options));ellipse.top-=ellipse.ry;ellipse.left-=ellipse.rx;return ellipse};fabric.Ellipse.fromObject=function(object){return new fabric.Ellipse(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;if(fabric.Rect){console.warn("fabric.Rect is already defined");return}var stateProperties=fabric.Object.prototype.stateProperties.concat();stateProperties.push("rx","ry","x","y");fabric.Rect=fabric.util.createClass(fabric.Object,{stateProperties:stateProperties,type:"rect",rx:0,ry:0,strokeDashArray:null,initialize:function(options){options=options||{};this.callSuper("initialize",options);this._initRxRy()},_initRxRy:function(){if(this.rx&&!this.ry){this.ry=this.rx}else if(this.ry&&!this.rx){this.rx=this.ry}},_render:function(ctx,noTransform){if(this.width===1&&this.height===1){ctx.fillRect(0,0,1,1);return}var rx=this.rx?Math.min(this.rx,this.width/2):0,ry=this.ry?Math.min(this.ry,this.height/2):0,w=this.width,h=this.height,x=noTransform?this.left:-this.width/2,y=noTransform?this.top:-this.height/2,isRounded=rx!==0||ry!==0,k=1-.5522847498;ctx.beginPath();ctx.moveTo(x+rx,y);ctx.lineTo(x+w-rx,y);isRounded&&ctx.bezierCurveTo(x+w-k*rx,y,x+w,y+k*ry,x+w,y+ry);ctx.lineTo(x+w,y+h-ry);isRounded&&ctx.bezierCurveTo(x+w,y+h-k*ry,x+w-k*rx,y+h,x+w-rx,y+h);ctx.lineTo(x+rx,y+h);isRounded&&ctx.bezierCurveTo(x+k*rx,y+h,x,y+h-k*ry,x,y+h-ry);ctx.lineTo(x,y+ry);isRounded&&ctx.bezierCurveTo(x,y+k*ry,x+k*rx,y,x+rx,y);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_renderDashedStroke:function(ctx){var x=-this.width/2,y=-this.height/2,w=this.width,h=this.height;ctx.beginPath();fabric.util.drawDashedLine(ctx,x,y,x+w,y,this.strokeDashArray);fabric.util.drawDashedLine(ctx,x+w,y,x+w,y+h,this.strokeDashArray);fabric.util.drawDashedLine(ctx,x+w,y+h,x,y+h,this.strokeDashArray);fabric.util.drawDashedLine(ctx,x,y+h,x,y,this.strokeDashArray);ctx.closePath()},toObject:function(propertiesToInclude){var object=extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx")||0,ry:this.get("ry")||0});if(!this.includeDefaultValues){this._removeDefaultValues(object)}return object},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),x=this.left,y=this.top;if(!(this.group&&this.group.type==="path-group")){x=-this.width/2;y=-this.height/2}markup.push("\n');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Rect.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" "));fabric.Rect.fromElement=function(element,options){if(!element){return null}options=options||{};var parsedAttributes=fabric.parseAttributes(element,fabric.Rect.ATTRIBUTE_NAMES);parsedAttributes.left=parsedAttributes.left||0;parsedAttributes.top=parsedAttributes.top||0;var rect=new fabric.Rect(extend(options?fabric.util.object.clone(options):{},parsedAttributes));rect.visible=rect.width>0&&rect.height>0;return rect};fabric.Rect.fromObject=function(object){return new fabric.Rect(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Polyline){fabric.warn("fabric.Polyline is already defined");return}fabric.Polyline=fabric.util.createClass(fabric.Object,{type:"polyline",points:null,minX:0,minY:0,initialize:function(points,options){return fabric.Polygon.prototype.initialize.call(this,points,options)},_calcDimensions:function(){return fabric.Polygon.prototype._calcDimensions.call(this)},_applyPointOffset:function(){return fabric.Polygon.prototype._applyPointOffset.call(this)},toObject:function(propertiesToInclude){return fabric.Polygon.prototype.toObject.call(this,propertiesToInclude)},toSVG:function(reviver){return fabric.Polygon.prototype.toSVG.call(this,reviver)},_render:function(ctx){if(!fabric.Polygon.prototype.commonRender.call(this,ctx)){return}this._renderFill(ctx);this._renderStroke(ctx)},_renderDashedStroke:function(ctx){var p1,p2;ctx.beginPath();for(var i=0,len=this.points.length;i\n');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx){if(!this.commonRender(ctx)){return}this._renderFill(ctx);if(this.stroke||this.strokeDashArray){ctx.closePath();this._renderStroke(ctx)}},commonRender:function(ctx){var point,len=this.points.length;if(!len||isNaN(this.points[len-1].y)){return false}ctx.beginPath();if(this._applyPointOffset){if(!(this.group&&this.group.type==="path-group")){this._applyPointOffset()}this._applyPointOffset=null}ctx.moveTo(this.points[0].x,this.points[0].y);for(var i=0;i"},toObject:function(propertiesToInclude){var o=extend(this.callSuper("toObject",propertiesToInclude),{path:this.path.map(function(item){return item.slice()}),pathOffset:this.pathOffset});if(this.sourcePath){o.sourcePath=this.sourcePath}if(this.transformMatrix){o.transformMatrix=this.transformMatrix}return o},toDatalessObject:function(propertiesToInclude){var o=this.toObject(propertiesToInclude);if(this.sourcePath){o.path=this.sourcePath}delete o.sourcePath;return o},toSVG:function(reviver){var chunks=[],markup=this._createBaseSVGMarkup(),addTransform="";for(var i=0,len=this.path.length;i\n");return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return this.path.length},_parsePath:function(){var result=[],coords=[],currentPath,parsed,re=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,match,coordsStr;for(var i=0,coordsParsed,len=this.path.length;icommandLength){for(var k=1,klen=coordsParsed.length;k\n"];for(var i=0,len=objects.length;i\n");return reviver?reviver(markup.join("")):markup.join("")},toString:function(){return"#"},isSameColor:function(){var firstPathFill=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(path){return(path.get("fill")||"").toLowerCase()===firstPathFill})},complexity:function(){return this.paths.reduce(function(total,path){return total+(path&&path.complexity?path.complexity():0)},0)},getObjects:function(){return this.paths}});fabric.PathGroup.fromObject=function(object,callback){if(typeof object.paths==="string"){fabric.loadSVGFromURL(object.paths,function(elements){var pathUrl=object.paths;delete object.paths;var pathGroup=fabric.util.groupSVGElements(elements,object,pathUrl);callback(pathGroup)})}else{fabric.util.enlivenObjects(object.paths,function(enlivenedObjects){delete object.paths;callback(new fabric.PathGroup(enlivenedObjects,object))})}};fabric.PathGroup.async=true})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,min=fabric.util.array.min,max=fabric.util.array.max,invoke=fabric.util.array.invoke;if(fabric.Group){return}var _lockProperties={lockMovementX:true,lockMovementY:true,lockRotation:true,lockScalingX:true,lockScalingY:true,lockUniScaling:true};fabric.Group=fabric.util.createClass(fabric.Object,fabric.Collection,{type:"group",initialize:function(objects,options){options=options||{};this._objects=objects||[];for(var i=this._objects.length;i--;){this._objects[i].group=this}this.originalState={};this.callSuper("initialize");if(options.originX){this.originX=options.originX}if(options.originY){this.originY=options.originY}this._calcBounds();this._updateObjectsCoords();this.callSuper("initialize",options);this.setCoords();this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(object){var objectLeft=object.getLeft(),objectTop=object.getTop(),center=this.getCenterPoint();object.set({originalLeft:objectLeft,originalTop:objectTop,left:objectLeft-center.x,top:objectTop-center.y});object.setCoords();object.__origHasControls=object.hasControls;object.hasControls=false},toString:function(){return"#"},addWithUpdate:function(object){this._restoreObjectsState();if(object){this._objects.push(object);object.group=this}this.forEachObject(this._setObjectActive,this);this._calcBounds();this._updateObjectsCoords();return this},_setObjectActive:function(object){object.set("active",true);object.group=this},removeWithUpdate:function(object){this._moveFlippedObject(object);this._restoreObjectsState();this.forEachObject(this._setObjectActive,this);this.remove(object);this._calcBounds();this._updateObjectsCoords();return this},_onObjectAdded:function(object){object.group=this},_onObjectRemoved:function(object){delete object.group;object.set("active",false)},delegatedProperties:{fill:true,opacity:true,fontFamily:true,fontWeight:true,fontSize:true,fontStyle:true,lineHeight:true,textDecoration:true,textAlign:true,backgroundColor:true},_set:function(key,value){if(key in this.delegatedProperties){var i=this._objects.length;while(i--){this._objects[i].set(key,value)}}this.callSuper("_set",key,value)},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{objects:invoke(this._objects,"toObject",propertiesToInclude)})},render:function(ctx){if(!this.visible){return}ctx.save();this.clipTo&&fabric.util.clipContext(this,ctx);this.transform(ctx);for(var i=0,len=this._objects.length;i\n'];for(var i=0,len=this._objects.length;i\n");return reviver?reviver(markup.join("")):markup.join("")},get:function(prop){if(prop in _lockProperties){if(this[prop]){return this[prop]}else{for(var i=0,len=this._objects.length;i\n','\n");if(this.stroke||this.strokeDashArray){var origFill=this.fill;this.fill=null;markup.push("\n');this.fill=origFill}markup.push("\n");return reviver?reviver(markup.join("")):markup.join("")},getSrc:function(){if(this.getElement()){return this.getElement().src||this.getElement()._src}},setSrc:function(src,callback,options){fabric.util.loadImage(src,function(img){return this.setElement(img,callback,options)},this,options&&options.crossOrigin)},toString:function(){return'#'},clone:function(callback,propertiesToInclude){this.constructor.fromObject(this.toObject(propertiesToInclude),callback)},applyFilters:function(callback,filters,imgElement,forResizing){filters=filters||this.filters;imgElement=imgElement||this._originalElement;if(!imgElement){return}var imgEl=imgElement,canvasEl=fabric.util.createCanvasElement(),replacement=fabric.util.createImage(),_this=this;canvasEl.width=imgEl.width;canvasEl.height=imgEl.height;canvasEl.getContext("2d").drawImage(imgEl,0,0,imgEl.width,imgEl.height);if(filters.length===0){this._element=imgElement;callback&&callback();return canvasEl}filters.forEach(function(filter){filter&&filter.applyTo(canvasEl,filter.scaleX||_this.scaleX,filter.scaleY||_this.scaleY);if(!forResizing&&filter&&filter.type==="Resize"){_this.width*=filter.scaleX;_this.height*=filter.scaleY}});replacement.width=canvasEl.width;replacement.height=canvasEl.height;if(fabric.isLikelyNode){replacement.src=canvasEl.toBuffer(undefined,fabric.Image.pngCompression);_this._element=replacement;!forResizing&&(_this._filteredEl=replacement);callback&&callback()}else{replacement.onload=function(){_this._element=replacement;!forResizing&&(_this._filteredEl=replacement);callback&&callback();replacement.onload=canvasEl=imgEl=null};replacement.src=canvasEl.toDataURL("image/png")}return canvasEl},_render:function(ctx,noTransform){var x,y,imageMargins=this._findMargins(),elementToDraw;x=noTransform?this.left:-this.width/2;y=noTransform?this.top:-this.height/2;if(this.meetOrSlice==="slice"){ctx.beginPath();ctx.rect(x,y,this.width,this.height);ctx.clip()}if(this.isMoving===false&&this.resizeFilters.length&&this._needsResize()){this._lastScaleX=this.scaleX;this._lastScaleY=this.scaleY;elementToDraw=this.applyFilters(null,this.resizeFilters,this._filteredEl||this._originalElement,true)}else{elementToDraw=this._element}elementToDraw&&ctx.drawImage(elementToDraw,x+imageMargins.marginX,y+imageMargins.marginY,imageMargins.width,imageMargins.height);this._renderStroke(ctx)},_needsResize:function(){return this.scaleX!==this._lastScaleX||this.scaleY!==this._lastScaleY},_findMargins:function(){var width=this.width,height=this.height,scales,scale,marginX=0,marginY=0;if(this.alignX!=="none"||this.alignY!=="none"){scales=[this.width/this._element.width,this.height/this._element.height];scale=this.meetOrSlice==="meet"?Math.min.apply(null,scales):Math.max.apply(null,scales);width=this._element.width*scale;height=this._element.height*scale;if(this.alignX==="Mid"){marginX=(this.width-width)/2}if(this.alignX==="Max"){marginX=this.width-width}if(this.alignY==="Mid"){marginY=(this.height-height)/2}if(this.alignY==="Max"){marginY=this.height-height}}return{width:width,height:height,marginX:marginX,marginY:marginY}},_resetWidthHeight:function(){var element=this.getElement();this.set("width",element.width);this.set("height",element.height)},_initElement:function(element){this.setElement(fabric.util.getById(element));fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(options){options||(options={});this.setOptions(options);this._setWidthHeight(options);if(this._element&&this.crossOrigin){this._element.crossOrigin=this.crossOrigin}},_initFilters:function(object,callback){if(object.filters&&object.filters.length){fabric.util.enlivenObjects(object.filters,function(enlivenedObjects){callback&&callback(enlivenedObjects)},"fabric.Image.filters")}else{callback&&callback()}},_setWidthHeight:function(options){this.width="width"in options?options.width:this.getElement()?this.getElement().width||0:0;this.height="height"in options?options.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}});fabric.Image.CSS_CANVAS="canvas-img";fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc;fabric.Image.fromObject=function(object,callback){fabric.util.loadImage(object.src,function(img){fabric.Image.prototype._initFilters.call(object,object,function(filters){object.filters=filters||[];var instance=new fabric.Image(img,object);callback&&callback(instance)})},null,object.crossOrigin)};fabric.Image.fromURL=function(url,callback,imgOptions){fabric.util.loadImage(url,function(img){callback&&callback(new fabric.Image(img,imgOptions))},null,imgOptions&&imgOptions.crossOrigin)};fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height preserveAspectRatio xlink:href".split(" "));fabric.Image.fromElement=function(element,callback,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Image.ATTRIBUTE_NAMES),align="xMidYMid",meetOrSlice="meet",alignX,alignY,aspectRatioAttrs;if(parsedAttributes.preserveAspectRatio){aspectRatioAttrs=parsedAttributes.preserveAspectRatio.split(" ")}if(aspectRatioAttrs&&aspectRatioAttrs.length){meetOrSlice=aspectRatioAttrs.pop();if(meetOrSlice!=="meet"&&meetOrSlice!=="slice"){align=meetOrSlice;meetOrSlice="meet"}else if(aspectRatioAttrs.length){align=aspectRatioAttrs.pop()}}alignX=align!=="none"?align.slice(1,4):"none";alignY=align!=="none"?align.slice(5,8):"none";parsedAttributes.alignX=alignX;parsedAttributes.alignY=alignY;parsedAttributes.meetOrSlice=meetOrSlice;fabric.Image.fromURL(parsedAttributes["xlink:href"],callback,extend(options?fabric.util.object.clone(options):{},parsedAttributes))};fabric.Image.async=true;fabric.Image.pngCompression=1})(typeof exports!=="undefined"?exports:this);fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var angle=this.getAngle()%360;if(angle>0){return Math.round((angle-1)/90)*90}return Math.round(angle/90)*90},straighten:function(){this.setAngle(this._getAngleValueForStraighten());return this},fxStraighten:function(callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(value){_this.setAngle(value);onChange()},onComplete:function(){_this.setCoords();onComplete()},onStart:function(){_this.set("active",false)}});return this}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(object){object.straighten();this.renderAll();return this},fxStraightenObject:function(object){object.fxStraighten({onChange:this.renderAll.bind(this)});return this}});fabric.Image.filters=fabric.Image.filters||{};fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",initialize:function(options){if(options){this.setOptions(options)}},setOptions:function(options){for(var prop in options){this[prop]=options[prop]}},toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.Brightness=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"Brightness",initialize:function(options){options=options||{};this.brightness=options.brightness||0},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,brightness=this.brightness;for(var i=0,len=data.length;ish||scx<0||scx>sw){continue}var srcOff=(scy*sw+scx)*4,wt=weights[cy*side+cx];r+=src[srcOff]*wt;g+=src[srcOff+1]*wt;b+=src[srcOff+2]*wt;a+=src[srcOff+3]*wt}}dst[dstOff]=r;dst[dstOff+1]=g;dst[dstOff+2]=b;dst[dstOff+3]=a+alphaFac*(255-a)}}context.putImageData(output,0,0)},toObject:function(){return extend(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}});fabric.Image.filters.Convolute.fromObject=function(object){return new fabric.Image.filters.Convolute(object); + +}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.GradientTransparency=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(options){options=options||{};this.threshold=options.threshold||100},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,threshold=this.threshold,total=data.length;for(var i=0,len=data.length;i-1?options.channel:0},applyTo:function(canvasEl){if(!this.mask){return}var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,maskEl=this.mask.getElement(),maskCanvasEl=fabric.util.createCanvasElement(),channel=this.channel,i,iLen=imageData.width*imageData.height*4;maskCanvasEl.width=maskEl.width;maskCanvasEl.height=maskEl.height;maskCanvasEl.getContext("2d").drawImage(maskEl,0,0,maskEl.width,maskEl.height);var maskImageData=maskCanvasEl.getContext("2d").getImageData(0,0,maskEl.width,maskEl.height),maskData=maskImageData.data;for(i=0;ilimit&&g>limit&&b>limit&&abs(r-g)width){multW=2;signW=-1}if(newHeight>height){multH=2;signH=-1}imageData=context.getImageData(0,0,width,height);canvasEl.width=max(newWidth,width);canvasEl.height=max(newHeight,height);context.putImageData(imageData,0,0);while(!doneW||!doneH){width=stepW;height=stepH;if(newWidth*signWlobes){return 0}x*=Math.PI;if(abs(x)<1e-16){return 1}var xx=x/lobes;return sin(x)*sin(xx)/x/xx}}function process(u){var v,i,weight,idx,a,red,green,blue,alpha,fX,fY;center.x=(u+.5)*ratioX;icenter.x=floor(center.x);for(v=0;v=oW){continue}fX=floor(1e3*abs(i-center.x));if(!cacheLanc[fX]){cacheLanc[fX]={}}for(var j=icenter.y-range2Y;j<=icenter.y+range2Y;j++){if(j<0||j>=oH){continue}fY=floor(1e3*abs(j-center.y));if(!cacheLanc[fX][fY]){cacheLanc[fX][fY]=lanczos(sqrt(pow(fX*rcpRatioX,2)+pow(fY*rcpRatioY,2))/1e3)}weight=cacheLanc[fX][fY];if(weight>0){idx=(j*oW+i)*4;a+=weight;red+=weight*srcData[idx];green+=weight*srcData[idx+1];blue+=weight*srcData[idx+2];alpha+=weight*srcData[idx+3]}}}idx=(v*dW+u)*4;destData[idx]=red/a;destData[idx+1]=green/a;destData[idx+2]=blue/a;destData[idx+3]=alpha/a}if(++u1&&w<-1){continue}weight=2*w*w*w-3*w*w+1;if(weight>0){dx=4*(xx+yy*oW);gxA+=weight*data[dx+3];weightsAlpha+=weight;if(data[dx+3]<255){weight=weight*data[dx+3]/250}gxR+=weight*data[dx];gxG+=weight*data[dx+1];gxB+=weight*data[dx+2];weights+=weight}}}data2[x2]=gxR/weights;data2[x2+1]=gxG/weights;data2[x2+2]=gxB/weights;data2[x2+3]=gxA/weightsAlpha}}return img2},toObject:function(){return{type:this.type,scaleX:this.scaleX,scaley:this.scaleY,resizeType:this.resizeType,lanczosLobes:this.lanczosLobes}}});fabric.Image.filters.Resize.fromObject=function(){return new fabric.Image.filters.Resize}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,clone=fabric.util.object.clone,toFixed=fabric.util.toFixed,supportsLineDash=fabric.StaticCanvas.supports("setLineDash");if(fabric.Text){fabric.warn("fabric.Text is already defined");return}var stateProperties=fabric.Object.prototype.stateProperties.concat();stateProperties.push("fontFamily","fontWeight","fontSize","text","textDecoration","textAlign","fontStyle","lineHeight","textBackgroundColor");fabric.Text=fabric.util.createClass(fabric.Object,{_dimensionAffectingProps:{fontSize:true,fontWeight:true,fontFamily:true,fontStyle:true,lineHeight:true,stroke:true,strokeWidth:true,text:true,textAlign:true},_reNewline:/\r?\n/,type:"text",fontSize:40,fontWeight:"normal",fontFamily:"Times New Roman",textDecoration:"",textAlign:"left",fontStyle:"",lineHeight:1.16,textBackgroundColor:"",stateProperties:stateProperties,stroke:null,shadow:null,_fontSizeFraction:.25,_fontSizeMult:1.13,initialize:function(text,options){options=options||{};this.text=text;this.__skipDimension=true;this.setOptions(options);this.__skipDimension=false;this._initDimensions()},_initDimensions:function(ctx){if(this.__skipDimension){return}if(!ctx){ctx=fabric.util.createCanvasElement().getContext("2d");this._setTextStyles(ctx)}this._textLines=this.text.split(this._reNewline);this._clearCache();var currentTextAlign=this.textAlign;this.textAlign="left";this.width=this._getTextWidth(ctx);this.textAlign=currentTextAlign;this.height=this._getTextHeight(ctx)},toString:function(){return"#'},_render:function(ctx){this.clipTo&&fabric.util.clipContext(this,ctx);this._renderTextBackground(ctx);this._renderText(ctx);this._renderTextDecoration(ctx);this.clipTo&&ctx.restore()},_renderText:function(ctx){ctx.save();this._translateForTextAlign(ctx);this._setOpacity(ctx);this._setShadow(ctx);this._setupCompositeOperation(ctx);this._renderTextFill(ctx);this._renderTextStroke(ctx);this._restoreCompositeOperation(ctx);this._removeShadow(ctx);ctx.restore()},_translateForTextAlign:function(ctx){if(this.textAlign!=="left"&&this.textAlign!=="justify"){ctx.translate(this.textAlign==="center"?this.width/2:this.width,0)}},_setTextStyles:function(ctx){ctx.textBaseline="alphabetic";if(!this.skipTextAlign){ctx.textAlign=this.textAlign}ctx.font=this._getFontDeclaration()},_getTextHeight:function(){return this._textLines.length*this._getHeightOfLine()},_getTextWidth:function(ctx){var maxWidth=this._getLineWidth(ctx,0);for(var i=1,len=this._textLines.length;imaxWidth){maxWidth=currentLineWidth}}return maxWidth},_renderChars:function(method,ctx,chars,left,top){ctx[method](chars,left,top)},_renderTextLine:function(method,ctx,line,left,top,lineIndex){top-=this.fontSize*this._fontSizeFraction;if(this.textAlign!=="justify"){this._renderChars(method,ctx,line,left,top,lineIndex);return}var lineWidth=this._getLineWidth(ctx,lineIndex),totalWidth=this.width;if(totalWidth>=lineWidth){var words=line.split(/\s+/),wordsWidth=this._getWidthOfWords(ctx,line,lineIndex),widthDiff=totalWidth-wordsWidth,numSpaces=words.length-1,spaceWidth=widthDiff/numSpaces,leftOffset=0;for(var i=0,len=words.length;i-1){offsets.push(.85)}if(this.textDecoration.indexOf("line-through")>-1){offsets.push(.43)}if(this.textDecoration.indexOf("overline")>-1){offsets.push(-.12)}if(offsets.length>0){renderLinesAtOffset(offsets)}},_getFontDeclaration:function(){return[fabric.isLikelyNode?this.fontWeight:this.fontStyle,fabric.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",fabric.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(ctx,noTransform){if(!this.visible){return}ctx.save();this._setTextStyles(ctx);if(this._shouldClearCache()){this._initDimensions(ctx)}if(!noTransform){this.transform(ctx)}this._setStrokeStyles(ctx);this._setFillStyles(ctx);if(this.transformMatrix){ctx.transform.apply(ctx,this.transformMatrix)}if(this.group&&this.group.type==="path-group"){ctx.translate(this.left,this.top)}this._render(ctx);ctx.restore()},toObject:function(propertiesToInclude){var object=extend(this.callSuper("toObject",propertiesToInclude),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,textBackgroundColor:this.textBackgroundColor});if(!this.includeDefaultValues){this._removeDefaultValues(object)}return object},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),offsets=this._getSVGLeftTopOffsets(this.ctx),textAndBg=this._getSVGTextAndBg(offsets.textTop,offsets.textLeft);this._wrapSVGTextAndBg(markup,textAndBg);return reviver?reviver(markup.join("")):markup.join("")},_getSVGLeftTopOffsets:function(ctx){var lineTop=this._getHeightOfLine(ctx,0),textLeft=-this.width/2,textTop=0;return{textLeft:textLeft+(this.group&&this.group.type==="path-group"?this.left:0),textTop:textTop+(this.group&&this.group.type==="path-group"?-this.top:0),lineTop:lineTop}},_wrapSVGTextAndBg:function(markup,textAndBg){markup.push(' \n',textAndBg.textBgRects.join("")," ',textAndBg.textSpans.join(""),"\n"," \n")},_getSVGTextAndBg:function(textTopOffset,textLeftOffset){var textSpans=[],textBgRects=[],height=0;this._setSVGBg(textBgRects);for(var i=0,len=this._textLines.length;i",fabric.util.string.escapeXml(this._textLines[i]),"")},_setSVGTextLineBg:function(textBgRects,i,textLeftOffset,textTopOffset,height){textBgRects.push(" \n')},_setSVGBg:function(textBgRects){if(this.backgroundColor){textBgRects.push(" \n')}},_getFillAttributes:function(value){var fillColor=value&&typeof value==="string"?new fabric.Color(value):"";if(!fillColor||!fillColor.getSource()||fillColor.getAlpha()===1){return'fill="'+value+'"'}return'opacity="'+fillColor.getAlpha()+'" fill="'+fillColor.setAlpha(1).toRgb()+'"'},_set:function(key,value){this.callSuper("_set",key,value);if(key in this._dimensionAffectingProps){this._initDimensions();this.setCoords()}},complexity:function(){return 1}});fabric.Text.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" "));fabric.Text.DEFAULT_SVG_FONT_SIZE=16;fabric.Text.fromElement=function(element,options){if(!element){return null}var parsedAttributes=fabric.parseAttributes(element,fabric.Text.ATTRIBUTE_NAMES);options=fabric.util.object.extend(options?fabric.util.object.clone(options):{},parsedAttributes);options.top=options.top||0;options.left=options.left||0;if("dx"in parsedAttributes){options.left+=parsedAttributes.dx}if("dy"in parsedAttributes){options.top+=parsedAttributes.dy}if(!("fontSize"in options)){options.fontSize=fabric.Text.DEFAULT_SVG_FONT_SIZE}if(!options.originX){options.originX="left"}var textContent=element.textContent.replace(/^\s+|\s+$|\n+/g,"").replace(/\s+/g," "),text=new fabric.Text(textContent,options),offX=0;if(text.originX==="left"){offX=text.getWidth()/2}if(text.originX==="right"){offX=-text.getWidth()/2}text.set({left:text.getLeft()+offX,top:text.getTop()-text.getHeight()/2+text.fontSize*(.18+text._fontSizeFraction)});return text};fabric.Text.fromObject=function(object){return new fabric.Text(object.text,clone(object))};fabric.util.createAccessors(fabric.Text)})(typeof exports!=="undefined"?exports:this);(function(){var clone=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:false,editable:true,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:true,_skipFillStrokeCheck:false,_reSpace:/\s|\n/,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:false,_charWidthsCache:{},initialize:function(text,options){this.styles=options?options.styles||{}:{};this.callSuper("initialize",text,options);this.initBehavior()},_clearCache:function(){this.callSuper("_clearCache");this.__maxFontHeights=[];this.__widthOfSpace=[]},isEmptyStyles:function(){if(!this.styles){return true}var obj=this.styles;for(var p1 in obj){for(var p2 in obj[p1]){for(var p3 in obj[p1][p2]){return false}}}return true},setSelectionStart:function(index){index=Math.max(index,0);if(this.selectionStart!==index){this.fire("selection:changed");this.canvas&&this.canvas.fire("text:selection:changed",{target:this});this.selectionStart=index}this._updateTextarea()},setSelectionEnd:function(index){index=Math.min(index,this.text.length);if(this.selectionEnd!==index){this.fire("selection:changed");this.canvas&&this.canvas.fire("text:selection:changed",{target:this});this.selectionEnd=index}this._updateTextarea(); + +},getSelectionStyles:function(startIndex,endIndex){if(arguments.length===2){var styles=[];for(var i=startIndex;i=start.charIndex&&(i!==endLine||jstartLine&&i0||this.skipFillStrokeCheck)){this.callSuper("_renderChars",method,ctx,line,left,top)}},_renderChar:function(method,ctx,lineIndex,i,_char,left,top,lineHeight){var decl,charWidth,charHeight,offset=this._fontSizeFraction*lineHeight/this.lineHeight;if(this.styles&&this.styles[lineIndex]&&(decl=this.styles[lineIndex][i])){var shouldStroke=decl.stroke||this.stroke,shouldFill=decl.fill||this.fill;ctx.save();charWidth=this._applyCharStylesGetWidth(ctx,_char,lineIndex,i,decl);charHeight=this._getHeightOfChar(ctx,_char,lineIndex,i);if(shouldFill){ctx.fillText(_char,left,top)}if(shouldStroke){ctx.strokeText(_char,left,top)}this._renderCharDecoration(ctx,decl,left,top,offset,charWidth,charHeight);ctx.restore();ctx.translate(charWidth,0)}else{if(method==="strokeText"&&this.stroke){ctx[method](_char,left,top)}if(method==="fillText"&&this.fill){ctx[method](_char,left,top)}charWidth=this._applyCharStylesGetWidth(ctx,_char,lineIndex,i);this._renderCharDecoration(ctx,null,left,top,offset,charWidth,this.fontSize);ctx.translate(ctx.measureText(_char).width,0)}},_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},_renderCharDecoration:function(ctx,styleDeclaration,left,top,offset,charWidth,charHeight){var textDecoration=styleDeclaration?styleDeclaration.textDecoration||this.textDecoration:this.textDecoration;if(!textDecoration){return}if(textDecoration.indexOf("underline")>-1){ctx.fillRect(left,top+charHeight/10,charWidth,charHeight/15)}if(textDecoration.indexOf("line-through")>-1){ctx.fillRect(left,top-charHeight*(this._fontSizeFraction+this._fontSizeMult-1)+charHeight/15,charWidth,charHeight/15)}if(textDecoration.indexOf("overline")>-1){ctx.fillRect(left,top-(this._fontSizeMult-this._fontSizeFraction)*charHeight,charWidth,charHeight/15)}},_renderTextLine:function(method,ctx,line,left,top,lineIndex){if(!this.isEmptyStyles()){top+=this.fontSize*(this._fontSizeFraction+.03)}this.callSuper("_renderTextLine",method,ctx,line,left,top,lineIndex)},_renderTextDecoration:function(ctx){if(this.isEmptyStyles()){return this.callSuper("_renderTextDecoration",ctx)}},_renderTextLinesBackground:function(ctx){if(!this.textBackgroundColor&&!this.styles){return}ctx.save();if(this.textBackgroundColor){ctx.fillStyle=this.textBackgroundColor}var lineHeights=0;for(var i=0,len=this._textLines.length;imaxHeight){maxHeight=currentCharHeight}}this.__maxFontHeights[lineIndex]=maxHeight;this.__lineHeights[lineIndex]=maxHeight*this.lineHeight*this._fontSizeMult;return this.__lineHeights[lineIndex]},_getTextHeight:function(ctx){var height=0;for(var i=0,len=this._textLines.length;i-1){offset++;index--}return startFrom-offset},findWordBoundaryRight:function(startFrom){var offset=0,index=startFrom;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},findLineBoundaryRight:function(startFrom){var offset=0,index=startFrom;while(!/\n/.test(this.text.charAt(index))&&index0&&index=_this.__selectionStartOnMouseDown){_this.setSelectionStart(_this.__selectionStartOnMouseDown);_this.setSelectionEnd(newSelectionStart)}else{_this.setSelectionStart(newSelectionStart);_this.setSelectionEnd(_this.__selectionStartOnMouseDown)}})},_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},_updateTextarea:function(){if(!this.hiddenTextarea){return}this.hiddenTextarea.value=this.text;this.hiddenTextarea.selectionStart=this.selectionStart;this.hiddenTextarea.selectionEnd=this.selectionEnd},_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}},_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}},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},_removeExtraneousStyles:function(){for(var prop in this.styles){if(!this._textLines[prop]){delete this.styles[prop]}}},_removeCharsFromTo:function(start,end){var i=end;while(i!==start){var prevIndex=this.get2DCursorLocation(i).charIndex;i--;var index=this.get2DCursorLocation(i).charIndex,isNewline=index>prevIndex;if(isNewline){this.removeStyleObject(isNewline,i+1)}else{this.removeStyleObject(this.get2DCursorLocation(i).charIndex===0,i)}}this.text=this.text.slice(0,start)+this.text.slice(end);this._clearCache()},insertChars:function(_chars,useCopiedStyle){var isEndOfLine=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+_chars+this.text.slice(this.selectionEnd);if(this.selectionStart===this.selectionEnd){this.insertStyleObjects(_chars,isEndOfLine,useCopiedStyle)}this.setSelectionStart(this.selectionStart+_chars.length);this.setSelectionEnd(this.selectionStart);this._clearCache();this.canvas&&this.canvas.renderAll();this.setCoords();this.fire("changed");this.canvas&&this.canvas.fire("text:changed",{target:this})},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(isEndOfLine){newLineStyles[0]=clone(currentCharStyle);this.styles[lineIndex+1]=newLineStyles}else{for(var index in this.styles[lineIndex]){if(parseInt(index,10)>=charIndex){newLineStyles[parseInt(index,10)-charIndex]=this.styles[lineIndex][index];delete this.styles[lineIndex][index]}}this.styles[lineIndex+1]=newLineStyles}this._clearCache()},insertCharStyleObject:function(lineIndex,charIndex,style){var currentLineStyles=this.styles[lineIndex],currentLineStylesCloned=clone(currentLineStyles);if(charIndex===0&&!style){charIndex=1}for(var index in currentLineStylesCloned){var numericIndex=parseInt(index,10);if(numericIndex>=charIndex){currentLineStyles[numericIndex+1]=currentLineStylesCloned[numericIndex]}}this.styles[lineIndex][charIndex]=style||clone(currentLineStyles[charIndex-1]);this._clearCache()},insertStyleObjects:function(_chars,isEndOfLine,useCopiedStyle){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(useCopiedStyle){this._insertStyles(this.copiedStyles)}else{this.insertCharStyleObject(lineIndex,charIndex)}}},_insertStyles:function(styles){for(var i=0,len=styles.length;ilineIndex){this.styles[numericLine+offset]=clonedStyles[numericLine]}}},removeStyleObject:function(isBeginningOfLine,index){var cursorLocation=this.get2DCursorLocation(index),lineIndex=cursorLocation.lineIndex,charIndex=cursorLocation.charIndex;if(isBeginningOfLine){var textOnPreviousLine=this._textLines[lineIndex-1],newCharIndexOnPrevLine=textOnPreviousLine?textOnPreviousLine.length:0;if(!this.styles[lineIndex-1]){this.styles[lineIndex-1]={}}for(charIndex in this.styles[lineIndex]){this.styles[lineIndex-1][parseInt(charIndex,10)+newCharIndexOnPrevLine]=this.styles[lineIndex][charIndex]}this.shiftLineStyles(lineIndex,-1)}else{var currentLineStyles=this.styles[lineIndex];if(currentLineStyles){var offset=this.selectionStart===this.selectionEnd?-1:0;delete currentLineStyles[charIndex+offset]}var currentLineStylesCloned=clone(currentLineStyles);for(var i in currentLineStylesCloned){var numericIndex=parseInt(i,10);if(numericIndex>=charIndex&&numericIndex!==0){currentLineStyles[numericIndex-1]=currentLineStylesCloned[numericIndex];delete currentLineStyles[numericIndex]}}}},insertNewline:function(){this.insertChars("\n")}})})();fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+new Date;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},_stopEvent:function(e){e.preventDefault&&e.preventDefault();e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler();this.initMousedownHandler();this.initMouseupHandler();this.initClicks()},initClicks:function(){this.on("dblclick",function(options){this.selectWord(this.getSelectionStartFromPointer(options.e))});this.on("tripleclick",function(options){this.selectLine(this.getSelectionStartFromPointer(options.e))})},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)}})},_isObjectMoved:function(e){var pointer=this.canvas.getPointer(e);return this.__mousedownX!==pointer.x||this.__mousedownY!==pointer.y},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})},setCursorByClick:function(e){var newSelectionStart=this.getSelectionStartFromPointer(e);if(e.shiftKey){if(newSelectionStartdistanceBtwLastCharAndCursor?0:1,newSelectionStart=index+offset;if(this.flipX){newSelectionStart=jlen-newSelectionStart}if(newSelectionStart>this.text.length){newSelectionStart=this.text.length}return newSelectionStart}});fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea");this.hiddenTextarea.setAttribute("autocapitalize","off");this.hiddenTextarea.style.cssText="position: fixed; bottom: 20px; left: 0px; opacity: 0;"+" width: 0px; height: 0px; z-index: -999;";fabric.document.body.appendChild(this.hiddenTextarea); + +fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this));fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this));fabric.util.addListener(this.hiddenTextarea,"copy",this.copy.bind(this));fabric.util.addListener(this.hiddenTextarea,"paste",this.paste.bind(this));if(!this._clickHandlerInitialized&&this.canvas){fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this));this._clickHandlerInitialized=true}},_keysMap:{8:"removeChars",9:"exitEditing",27:"exitEditing",13:"insertNewline",33:"moveCursorUp",34:"moveCursorDown",35:"moveCursorRight",36:"moveCursorLeft",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},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()},forwardDelete:function(e){if(this.selectionStart===this.selectionEnd){this.moveCursorRight(e)}this.removeChars(e)},copy:function(e){var selectedText=this.getSelectedText(),clipboardData=this._getClipboardData(e);if(clipboardData){clipboardData.setData("text",selectedText)}this.copiedText=selectedText;this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(e){var copiedText=null,clipboardData=this._getClipboardData(e);if(clipboardData){copiedText=clipboardData.getData("text")}else{copiedText=this.copiedText}if(copiedText){this.insertChars(copiedText,true)}},cut:function(e){if(this.selectionStart===this.selectionEnd){return}this.copy();this.removeChars(e)},_getClipboardData:function(e){return e&&(e.clipboardData||fabric.window.clipboardData)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey){return}if(e.which!==0){this.insertChars(String.fromCharCode(e.which))}e.stopPropagation()},getDownCursorOffset:function(e,isRight){var selectionProp=isRight?this.selectionEnd:this.selectionStart,_char,lineLeftOffset,textBeforeCursor=this.text.slice(0,selectionProp),textAfterCursor=this.text.slice(selectionProp),textOnSameLineBeforeCursor=textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n")+1),textOnSameLineAfterCursor=textAfterCursor.match(/(.*)\n?/)[1],textOnNextLine=(textAfterCursor.match(/.*\n(.*)\n?/)||{})[1]||"",cursorLocation=this.get2DCursorLocation(selectionProp);if(cursorLocation.lineIndex===this._textLines.length-1||e.metaKey||e.keyCode===34){return this.text.length-selectionProp}var widthOfSameLineBeforeCursor=this._getLineWidth(this.ctx,cursorLocation.lineIndex);lineLeftOffset=this._getLineLeftOffset(widthOfSameLineBeforeCursor);var widthOfCharsOnSameLineBeforeCursor=lineLeftOffset,lineIndex=cursorLocation.lineIndex;for(var i=0,len=textOnSameLineBeforeCursor.length;iwidthOfCharsOnSameLineBeforeCursor){foundMatch=true;var leftEdge=widthOfCharsOnNextLine-widthOfChar,rightEdge=widthOfCharsOnNextLine,offsetFromLeftEdge=Math.abs(leftEdge-widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge=Math.abs(rightEdge-widthOfCharsOnSameLineBeforeCursor);indexOnNextLine=offsetFromRightEdgethis.text.length){this.setSelectionEnd(this.text.length)}},getUpCursorOffset:function(e,isRight){var selectionProp=isRight?this.selectionEnd:this.selectionStart,cursorLocation=this.get2DCursorLocation(selectionProp);if(cursorLocation.lineIndex===0||e.metaKey||e.keyCode===33){return selectionProp}var textBeforeCursor=this.text.slice(0,selectionProp),textOnSameLineBeforeCursor=textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n")+1),textOnPreviousLine=(textBeforeCursor.match(/\n?(.*)\n.*$/)||{})[1]||"",_char,widthOfSameLineBeforeCursor=this._getLineWidth(this.ctx,cursorLocation.lineIndex),lineLeftOffset=this._getLineLeftOffset(widthOfSameLineBeforeCursor),widthOfCharsOnSameLineBeforeCursor=lineLeftOffset,lineIndex=cursorLocation.lineIndex;for(var i=0,len=textOnSameLineBeforeCursor.length;iwidthOfCharsOnSameLineBeforeCursor){foundMatch=true;var leftEdge=widthOfCharsOnPreviousLine-widthOfChar,rightEdge=widthOfCharsOnPreviousLine,offsetFromLeftEdge=Math.abs(leftEdge-widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge=Math.abs(rightEdge-widthOfCharsOnSameLineBeforeCursor);indexOnPrevLine=offsetFromRightEdge=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()},moveCursorRightWithShift:function(e){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this._moveRight(e,"selectionStart")}else{this._selectionDirection="right";this._moveRight(e,"selectionEnd");if(this.text.charAt(this.selectionEnd-1)==="\n"){this.setSelectionEnd(this.selectionEnd+1)}}},moveCursorRightWithoutShift:function(e){this._selectionDirection="right";if(this.selectionStart===this.selectionEnd){this._moveRight(e,"selectionStart");this.setSelectionEnd(this.selectionStart)}else{this.setSelectionEnd(this.selectionEnd+this.getNumNewLinesInSelectedText());this.setSelectionStart(this.selectionEnd)}},removeChars:function(e){if(this.selectionStart===this.selectionEnd){this._removeCharsNearCursor(e)}else{this._removeCharsFromTo(this.selectionStart,this.selectionEnd)}this.setSelectionEnd(this.selectionStart);this._removeExtraneousStyles();this._clearCache();this.canvas&&this.canvas.renderAll();this.setCoords();this.fire("changed");this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(e){if(this.selectionStart!==0){if(e.metaKey){var leftLineBoundary=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftLineBoundary,this.selectionStart);this.setSelectionStart(leftLineBoundary)}else if(e.altKey){var leftWordBoundary=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftWordBoundary,this.selectionStart);this.setSelectionStart(leftWordBoundary)}else{var isBeginningOfLine=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(isBeginningOfLine);this.setSelectionStart(this.selectionStart-1);this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}});fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(lineIndex,textSpans,height,textLeftOffset,textTopOffset,textBgRects){if(!this.styles[lineIndex]){this.callSuper("_setSVGTextLineText",lineIndex,textSpans,height,textLeftOffset,textTopOffset)}else{this._setSVGTextLineChars(lineIndex,textSpans,height,textLeftOffset,textBgRects)}},_setSVGTextLineChars:function(lineIndex,textSpans,height,textLeftOffset,textBgRects){var chars=this._textLines[lineIndex].split(""),charOffset=0,lineLeftOffset=this._getSVGLineLeftOffset(lineIndex)-this.width/2,lineOffset=this._getSVGLineTopOffset(lineIndex),heightOfLine=this._getHeightOfLine(this.ctx,lineIndex);for(var i=0,len=chars.length;i'].join("")},_createTextCharSpan:function(_char,styleDecl,lineLeftOffset,lineTopOffset,charOffset){var fillStyles=this.getSvgStyles.call(fabric.util.object.extend({visible:true,fill:this.fill,stroke:this.stroke,type:"text"},styleDecl));return['',fabric.util.string.escapeXml(_char),""].join("")}});(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;function request(url,encoding,callback){var oURL=URL.parse(url);if(!oURL.port){oURL.port=oURL.protocol.indexOf("https:")===0?443:80}var reqHandler=oURL.protocol.indexOf("https:")===0?HTTPS:HTTP,req=reqHandler.request({hostname:oURL.hostname,port:oURL.port,path:oURL.path,method:"GET"},function(response){var body="";if(encoding){response.setEncoding(encoding)}response.on("end",function(){callback(body)});response.on("data",function(chunk){if(response.statusCode===200){body+=chunk}})});req.on("error",function(err){if(err.errno===process.ECONNREFUSED){fabric.log("ECONNREFUSED: connection refused to "+oURL.hostname+":"+oURL.port)}else{fabric.log(err.message)}});req.end()}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");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)})})};fabric.createCanvasForNode=function(width,height,options,nodeCanvasOptions){nodeCanvasOptions=nodeCanvasOptions||options;var canvasEl=fabric.document.createElement("canvas"),nodeCanvas=new Canvas(width||600,height||600,nodeCanvasOptions);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};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}})(); \ No newline at end of file diff --git a/dist/fabric.min.js.gz b/dist/fabric.min.js.gz index 60a94515..2a9a23b4 100644 Binary files a/dist/fabric.min.js.gz and b/dist/fabric.min.js.gz differ diff --git a/dist/fabric.require.js b/dist/fabric.require.js index aafdcc12..134a4756 100644 --- a/dist/fabric.require.js +++ b/dist/fabric.require.js @@ -1,15083 +1,7101 @@ -/* build: `node build.js modules=ALL exclude=gestures,json minifier=uglifyjs` */ -/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ - -var fabric = fabric || { version: "1.5.0" }; -if (typeof exports !== 'undefined') { - exports.fabric = fabric; -} - -if (typeof document !== 'undefined' && typeof window !== 'undefined') { - fabric.document = document; - fabric.window = window; - // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) - window.fabric = fabric; -} -else { - // assume we're running under node.js when document/window are not present - fabric.document = require("jsdom") - .jsdom(""); - - if (fabric.document.createWindow) { - fabric.window = fabric.document.createWindow(); - } else { - fabric.window = fabric.document.parentWindow; - } -} - -/** - * True when in environment that supports touch events - * @type boolean - */ -fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; - -/** - * True when in environment that's probably Node.js - * @type boolean - */ -fabric.isLikelyNode = typeof Buffer !== 'undefined' && - typeof window === 'undefined'; - -/* _FROM_SVG_START_ */ -/** - * Attributes parsed from all SVG elements - * @type array - */ -fabric.SHARED_ATTRIBUTES = [ - "display", - "transform", - "fill", "fill-opacity", "fill-rule", - "opacity", - "stroke", "stroke-dasharray", "stroke-linecap", - "stroke-linejoin", "stroke-miterlimit", - "stroke-opacity", "stroke-width" -]; -/* _FROM_SVG_END_ */ - -/** - * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. - */ -fabric.DPI = 96; -fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)'; - - -(function() { - - /** - * @private - * @param {String} eventName - * @param {Function} handler - */ - function _removeEventListener(eventName, handler) { - if (!this.__eventListeners[eventName]) { - return; - } - - if (handler) { - fabric.util.removeFromArray(this.__eventListeners[eventName], handler); - } - else { - this.__eventListeners[eventName].length = 0; - } - } - - /** - * Observes specified event - * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) - * @memberOf fabric.Observable - * @alias on - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function that receives a notification when an event of the specified type occurs - * @return {Self} thisArg - * @chainable - */ - function observe(eventName, handler) { - if (!this.__eventListeners) { - this.__eventListeners = { }; - } - // one object with key/value pairs was passed - if (arguments.length === 1) { - for (var prop in eventName) { - this.on(prop, eventName[prop]); - } - } - else { - if (!this.__eventListeners[eventName]) { - this.__eventListeners[eventName] = [ ]; - } - this.__eventListeners[eventName].push(handler); - } - return this; - } - - /** - * Stops event observing for a particular event handler. Calling this method - * without arguments removes all handlers for all events - * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) - * @memberOf fabric.Observable - * @alias off - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function to be deleted from EventListeners - * @return {Self} thisArg - * @chainable - */ - function stopObserving(eventName, handler) { - if (!this.__eventListeners) { - return; - } - - // remove all key/value pairs (event name -> event handler) - if (arguments.length === 0) { - this.__eventListeners = { }; - } - // one object with key/value pairs was passed - else if (arguments.length === 1 && typeof arguments[0] === 'object') { - for (var prop in eventName) { - _removeEventListener.call(this, prop, eventName[prop]); - } - } - else { - _removeEventListener.call(this, eventName, handler); - } - return this; - } - - /** - * Fires event with an optional options object - * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) - * @memberOf fabric.Observable - * @alias trigger - * @param {String} eventName Event name to fire - * @param {Object} [options] Options object - * @return {Self} thisArg - * @chainable - */ - function fire(eventName, options) { - if (!this.__eventListeners) { - return; - } - - var listenersForEvent = this.__eventListeners[eventName]; - if (!listenersForEvent) { - return; - } - - for (var i = 0, len = listenersForEvent.length; i < len; i++) { - // avoiding try/catch for perf. reasons - listenersForEvent[i].call(this, options || { }); - } - return this; - } - - /** - * @namespace fabric.Observable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events} - * @see {@link http://fabricjs.com/events/|Events demo} - */ - fabric.Observable = { - observe: observe, - stopObserving: stopObserving, - fire: fire, - - on: observe, - off: stopObserving, - trigger: fire - }; -})(); - - -/** - * @namespace fabric.Collection - */ -fabric.Collection = { - - /** - * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * Objects should be instances of (or inherit from) fabric.Object - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - */ - add: function () { - this._objects.push.apply(this._objects, arguments); - for (var i = 0, length = arguments.length; i < length; i++) { - this._onObjectAdded(arguments[i]); - } - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) - * An object should be an instance of (or inherit from) fabric.Object - * @param {Object} object Object to insert - * @param {Number} index Index to insert object at - * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs - * @return {Self} thisArg - * @chainable - */ - insertAt: function (object, index, nonSplicing) { - var objects = this.getObjects(); - if (nonSplicing) { - objects[index] = object; - } - else { - objects.splice(index, 0, object); - } - this._onObjectAdded(object); - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - * @chainable - */ - remove: function() { - var objects = this.getObjects(), - index; - - for (var i = 0, length = arguments.length; i < length; i++) { - index = objects.indexOf(arguments[i]); - - // only call onObjectRemoved if an object was actually removed - if (index !== -1) { - objects.splice(index, 1); - this._onObjectRemoved(arguments[i]); - } - } - - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Executes given function for each object in this group - * @param {Function} callback - * Callback invoked with current object as first argument, - * index - as second and an array of all objects - as third. - * Iteration happens in reverse order (for performance reasons). - * Callback is invoked in a context of Global Object (e.g. `window`) - * when no `context` argument is given - * - * @param {Object} context Context (aka thisObject) - * @return {Self} thisArg - */ - forEachObject: function(callback, context) { - var objects = this.getObjects(), - i = objects.length; - while (i--) { - callback.call(context, objects[i], i, objects); - } - return this; - }, - - /** - * Returns an array of children objects of this instance - * Type parameter introduced in 1.3.10 - * @param {String} [type] When specified, only objects of this type are returned - * @return {Array} - */ - getObjects: function(type) { - if (typeof type === 'undefined') { - return this._objects; - } - return this._objects.filter(function(o) { - return o.type === type; - }); - }, - - /** - * Returns object at specified index - * @param {Number} index - * @return {Self} thisArg - */ - item: function (index) { - return this.getObjects()[index]; - }, - - /** - * Returns true if collection contains no objects - * @return {Boolean} true if collection is empty - */ - isEmpty: function () { - return this.getObjects().length === 0; - }, - - /** - * Returns a size of a collection (i.e: length of an array containing its objects) - * @return {Number} Collection size - */ - size: function() { - return this.getObjects().length; - }, - - /** - * Returns true if collection contains an object - * @param {Object} object Object to check against - * @return {Boolean} `true` if collection contains an object - */ - contains: function(object) { - return this.getObjects().indexOf(object) > -1; - }, - - /** - * Returns number representation of a collection complexity - * @return {Number} complexity - */ - complexity: function () { - return this.getObjects().reduce(function (memo, current) { - memo += current.complexity ? current.complexity() : 0; - return memo; - }, 0); - } +var fabric = fabric || { + version: "1.5.0" }; - -(function(global) { - - var sqrt = Math.sqrt, - atan2 = Math.atan2, - PiBy180 = Math.PI / 180; - - /** - * @namespace fabric.util - */ - fabric.util = { - - /** - * Removes value from an array. - * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` - * @static - * @memberOf fabric.util - * @param {Array} array - * @param {Any} value - * @return {Array} original array - */ - removeFromArray: function(array, value) { - var idx = array.indexOf(value); - if (idx !== -1) { - array.splice(idx, 1); - } - return array; - }, - - /** - * Returns random number between 2 specified ones. - * @static - * @memberOf fabric.util - * @param {Number} min lower limit - * @param {Number} max upper limit - * @return {Number} random value (between min and max) - */ - getRandomInt: function(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - }, - - /** - * Transforms degrees to radians. - * @static - * @memberOf fabric.util - * @param {Number} degrees value in degrees - * @return {Number} value in radians - */ - degreesToRadians: function(degrees) { - return degrees * PiBy180; - }, - - /** - * Transforms radians to degrees. - * @static - * @memberOf fabric.util - * @param {Number} radians value in radians - * @return {Number} value in degrees - */ - radiansToDegrees: function(radians) { - return radians / PiBy180; - }, - - /** - * Rotates `point` around `origin` with `radians` - * @static - * @memberOf fabric.util - * @param {fabric.Point} point The point to rotate - * @param {fabric.Point} origin The origin of the rotation - * @param {Number} radians The radians of the angle for the rotation - * @return {fabric.Point} The new rotated point - */ - rotatePoint: function(point, origin, radians) { - var sin = Math.sin(radians), - cos = Math.cos(radians); - - point.subtractEquals(origin); - - var rx = point.x * cos - point.y * sin, - ry = point.x * sin + point.y * cos; - - return new fabric.Point(rx, ry).addEquals(origin); - }, - - /** - * Apply transform t to point p - * @static - * @memberOf fabric.util - * @param {fabric.Point} p The point to transform - * @param {Array} t The transform - * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied - * @return {fabric.Point} The transformed point - */ - transformPoint: function(p, t, ignoreOffset) { - if (ignoreOffset) { - return new fabric.Point( - t[0] * p.x + t[2] * p.y, - t[1] * p.x + t[3] * p.y - ); - } - return new fabric.Point( - t[0] * p.x + t[2] * p.y + t[4], - t[1] * p.x + t[3] * p.y + t[5] - ); - }, - - /** - * Invert transformation t - * @static - * @memberOf fabric.util - * @param {Array} t The transform - * @return {Array} The inverted transform - */ - invertTransform: function(t) { - var r = t.slice(), - a = 1 / (t[0] * t[3] - t[1] * t[2]); - r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; - var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r); - r[4] = -o.x; - r[5] = -o.y; - return r; - }, - - /** - * A wrapper around Number#toFixed, which contrary to native method returns number, not string. - * @static - * @memberOf fabric.util - * @param {Number|String} number number to operate on - * @param {Number} fractionDigits number of fraction digits to "leave" - * @return {Number} - */ - toFixed: function(number, fractionDigits) { - return parseFloat(Number(number).toFixed(fractionDigits)); - }, - - /** - * Converts from attribute value to pixel value if applicable. - * Returns converted pixels or original value not converted. - * @param {Number|String} value number to operate on - * @return {Number|String} - */ - parseUnit: function(value, fontSize) { - var unit = /\D{0,2}$/.exec(value), - number = parseFloat(value); - if (!fontSize) { - fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; - } - switch (unit[0]) { - case 'mm': - return number * fabric.DPI / 25.4; - - case 'cm': - return number * fabric.DPI / 2.54; - - case 'in': - return number * fabric.DPI; - - case 'pt': - return number * fabric.DPI / 72; // or * 4 / 3 - - case 'pc': - return number * fabric.DPI / 72 * 12; // or * 16 - - case 'em': - return number * fontSize; - - default: - return number; - } - }, - - /** - * Function which always returns `false`. - * @static - * @memberOf fabric.util - * @return {Boolean} - */ - falseFunction: function() { - return false; - }, - - /** - * Returns klass "Class" object of given namespace - * @memberOf fabric.util - * @param {String} type Type of object (eg. 'circle') - * @param {String} namespace Namespace to get klass "Class" object from - * @return {Object} klass "Class" - */ - getKlass: function(type, namespace) { - // capitalize first letter only - type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); - return fabric.util.resolveNamespace(namespace)[type]; - }, - - /** - * Returns object of given namespace - * @memberOf fabric.util - * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' - * @return {Object} Object for given namespace (default fabric) - */ - resolveNamespace: function(namespace) { - if (!namespace) { - return fabric; - } - - var parts = namespace.split('.'), - len = parts.length, - obj = global || fabric.window; - - for (var i = 0; i < len; ++i) { - obj = obj[parts[i]]; - } - - return obj; - }, - - /** - * Loads image element from given url and passes it to a callback - * @memberOf fabric.util - * @param {String} url URL representing an image - * @param {Function} callback Callback; invoked with loaded image - * @param {Any} [context] Context to invoke callback in - * @param {Object} [crossOrigin] crossOrigin value to set image element to - */ - loadImage: function(url, callback, context, crossOrigin) { - if (!url) { - callback && callback.call(context, url); - return; - } - - var img = fabric.util.createImage(); - - /** @ignore */ - img.onload = function () { - callback && callback.call(context, img); - img = img.onload = img.onerror = null; - }; - - /** @ignore */ - img.onerror = function() { - fabric.log('Error loading ' + img.src); - callback && callback.call(context, null, true); - img = img.onload = img.onerror = null; - }; - - // data-urls appear to be buggy with crossOrigin - // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 - // see https://code.google.com/p/chromium/issues/detail?id=315152 - // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 - if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') { - img.crossOrigin = crossOrigin; - } - - img.src = url; - }, - - /** - * Creates corresponding fabric instances from their object representations - * @static - * @memberOf fabric.util - * @param {Array} objects Objects to enliven - * @param {Function} callback Callback to invoke when all objects are created - * @param {String} namespace Namespace to get klass "Class" object from - * @param {Function} reviver Method for further parsing of object elements, - * called after each fabric object created. - */ - enlivenObjects: function(objects, callback, namespace, reviver) { - objects = objects || [ ]; - - function onLoaded() { - if (++numLoadedObjects === numTotalObjects) { - callback && callback(enlivenedObjects); - } - } - - var enlivenedObjects = [ ], - numLoadedObjects = 0, - numTotalObjects = objects.length; - - if (!numTotalObjects) { - callback && callback(enlivenedObjects); - return; - } - - objects.forEach(function (o, index) { - // if sparse array - if (!o || !o.type) { - onLoaded(); - return; - } - var klass = fabric.util.getKlass(o.type, namespace); - if (klass.async) { - klass.fromObject(o, function (obj, error) { - if (!error) { - enlivenedObjects[index] = obj; - reviver && reviver(o, enlivenedObjects[index]); - } - onLoaded(); - }); - } - else { - enlivenedObjects[index] = klass.fromObject(o); - reviver && reviver(o, enlivenedObjects[index]); - onLoaded(); - } - }); - }, - - /** - * Groups SVG elements (usually those retrieved from SVG document) - * @static - * @memberOf fabric.util - * @param {Array} elements SVG elements to group - * @param {Object} [options] Options object - * @return {fabric.Object|fabric.PathGroup} - */ - groupSVGElements: function(elements, options, path) { - var object; - - object = new fabric.PathGroup(elements, options); - - if (typeof path !== 'undefined') { - object.setSourcePath(path); - } - return object; - }, - - /** - * Populates an object with properties of another object - * @static - * @memberOf fabric.util - * @param {Object} source Source object - * @param {Object} destination Destination object - * @return {Array} properties Propertie names to include - */ - populateWithProperties: function(source, destination, properties) { - if (properties && Object.prototype.toString.call(properties) === '[object Array]') { - for (var i = 0, len = properties.length; i < len; i++) { - if (properties[i] in source) { - destination[properties[i]] = source[properties[i]]; - } - } - } - }, - - /** - * Draws a dashed line between two points - * - * This method is used to draw dashed line around selection area. - * See dotted stroke in canvas - * - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x start x coordinate - * @param {Number} y start y coordinate - * @param {Number} x2 end x coordinate - * @param {Number} y2 end y coordinate - * @param {Array} da dash array pattern - */ - drawDashedLine: function(ctx, x, y, x2, y2, da) { - var dx = x2 - x, - dy = y2 - y, - len = sqrt(dx * dx + dy * dy), - rot = atan2(dy, dx), - dc = da.length, - di = 0, - draw = true; - - ctx.save(); - ctx.translate(x, y); - ctx.moveTo(0, 0); - ctx.rotate(rot); - - x = 0; - while (len > x) { - x += da[di++ % dc]; - if (x > len) { - x = len; - } - ctx[draw ? 'lineTo' : 'moveTo'](x, 0); - draw = !draw; - } - - ctx.restore(); - }, - - /** - * Creates canvas element and initializes it via excanvas if necessary - * @static - * @memberOf fabric.util - * @param {CanvasElement} [canvasEl] optional canvas element to initialize; - * when not given, element is created implicitly - * @return {CanvasElement} initialized canvas element - */ - createCanvasElement: function(canvasEl) { - canvasEl || (canvasEl = fabric.document.createElement('canvas')); - //jscs:disable requireCamelCaseOrUpperCaseIdentifiers - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } - //jscs:enable requireCamelCaseOrUpperCaseIdentifiers - return canvasEl; - }, - - /** - * Creates image element (works on client and node) - * @static - * @memberOf fabric.util - * @return {HTMLImageElement} HTML image element - */ - createImage: function() { - return fabric.isLikelyNode - ? new (require('canvas').Image)() - : fabric.document.createElement('img'); - }, - - /** - * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array - * @static - * @memberOf fabric.util - * @param {Object} klass "Class" to create accessors for - */ - createAccessors: function(klass) { - var proto = klass.prototype; - - for (var i = proto.stateProperties.length; i--; ) { - - var propName = proto.stateProperties[i], - capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), - setterName = 'set' + capitalizedPropName, - getterName = 'get' + capitalizedPropName; - - // using `new Function` for better introspection - if (!proto[getterName]) { - proto[getterName] = (function(property) { - return new Function('return this.get("' + property + '")'); - })(propName); - } - if (!proto[setterName]) { - proto[setterName] = (function(property) { - return new Function('value', 'return this.set("' + property + '", value)'); - })(propName); - } - } - }, - - /** - * @static - * @memberOf fabric.util - * @param {fabric.Object} receiver Object implementing `clipTo` method - * @param {CanvasRenderingContext2D} ctx Context to clip - */ - clipContext: function(receiver, ctx) { - ctx.save(); - ctx.beginPath(); - receiver.clipTo(ctx); - ctx.clip(); - }, - - /** - * Multiply matrix A by matrix B to nest transformations - * @static - * @memberOf fabric.util - * @param {Array} a First transformMatrix - * @param {Array} b Second transformMatrix - * @return {Array} The product of the two transform matrices - */ - multiplyTransformMatrices: function(a, b) { - // Matrix multiply a * b - return [ - a[0] * b[0] + a[2] * b[1], - a[1] * b[0] + a[3] * b[1], - a[0] * b[2] + a[2] * b[3], - a[1] * b[2] + a[3] * b[3], - a[0] * b[4] + a[2] * b[5] + a[4], - a[1] * b[4] + a[3] * b[5] + a[5] - ]; - }, - - /** - * Returns string representation of function body - * @param {Function} fn Function to get body of - * @return {String} Function body - */ - getFunctionBody: function(fn) { - return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; - }, - - /** - * Returns true if context has transparent pixel - * at specified location (taking tolerance into account) - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x x coordinate - * @param {Number} y y coordinate - * @param {Number} tolerance Tolerance - */ - isTransparent: function(ctx, x, y, tolerance) { - - // If tolerance is > 0 adjust start coords to take into account. - // If moves off Canvas fix to 0 - if (tolerance > 0) { - if (x > tolerance) { - x -= tolerance; - } - else { - x = 0; - } - if (y > tolerance) { - y -= tolerance; - } - else { - y = 0; - } - } - - var _isTransparent = true, - imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); - - // Split image data - for tolerance > 1, pixelDataSize = 4; - for (var i = 3, l = imageData.data.length; i < l; i += 4) { - var temp = imageData.data[i]; - _isTransparent = temp <= 0; - if (_isTransparent === false) { - break; // Stop if colour found - } - } - - imageData = null; - - return _isTransparent; - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var arcToSegmentsCache = { }, - segmentToBezierCache = { }, - boundsOfCurveCache = { }, - _join = Array.prototype.join; - - /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp - * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here - * http://mozilla.org/MPL/2.0/ - */ - function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { - var argsString = _join.call(arguments); - if (arcToSegmentsCache[argsString]) { - return arcToSegmentsCache[argsString]; - } - - var PI = Math.PI, th = rotateX * PI / 180, - sinTh = Math.sin(th), - cosTh = Math.cos(th), - fromX = 0, fromY = 0; - - rx = Math.abs(rx); - ry = Math.abs(ry); - - var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, - py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, - rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, - pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, - root = 0; - - if (pl < 0) { - var s = Math.sqrt(1 - pl/(rx2 * ry2)); - rx *= s; - ry *= s; - } - else { - root = (large === sweep ? -1.0 : 1.0) * - Math.sqrt( pl /(rx2 * py2 + ry2 * px2)); - } - - var cx = root * rx * py / ry, - cy = -root * ry * px / rx, - cx1 = cosTh * cx - sinTh * cy + toX * 0.5, - cy1 = sinTh * cx + cosTh * cy + toY * 0.5, - mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), - dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); - - if (sweep === 0 && dtheta > 0) { - dtheta -= 2 * PI; - } - else if (sweep === 1 && dtheta < 0) { - dtheta += 2 * PI; - } - - // Convert into cubic bezier segments <= 90deg - var segments = Math.ceil(Math.abs(dtheta / PI * 2)), - result = [], mDelta = dtheta / segments, - mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), - th3 = mTheta + mDelta; - - for (var i = 0; i < segments; i++) { - result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); - fromX = result[i][4]; - fromY = result[i][5]; - mTheta = th3; - th3 += mDelta; - } - arcToSegmentsCache[argsString] = result; - return result; - } - - function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { - var argsString2 = _join.call(arguments); - if (segmentToBezierCache[argsString2]) { - return segmentToBezierCache[argsString2]; - } - - var costh2 = Math.cos(th2), - sinth2 = Math.sin(th2), - costh3 = Math.cos(th3), - sinth3 = Math.sin(th3), - toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, - toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, - cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2), - cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2), - cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), - cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); - - segmentToBezierCache[argsString2] = [ - cp1X, cp1Y, - cp2X, cp2Y, - toX, toY - ]; - return segmentToBezierCache[argsString2]; - } - - /* - * Private - */ - function calcVectorAngle(ux, uy, vx, vy) { - var ta = Math.atan2(uy, ux), - tb = Math.atan2(vy, vx); - if (tb >= ta) { - return tb - ta; - } - else { - return 2 * Math.PI - (ta - tb); - } - } - - /** - * Draws arc - * @param {CanvasRenderingContext2D} ctx - * @param {Number} fx - * @param {Number} fy - * @param {Array} coords - */ - fabric.util.drawArc = function(ctx, fx, fy, coords) { - var rx = coords[0], - ry = coords[1], - rot = coords[2], - large = coords[3], - sweep = coords[4], - tx = coords[5], - ty = coords[6], - segs = [[ ], [ ], [ ], [ ]], - segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); - - for (var i = 0, len = segsNorm.length; i < len; i++) { - segs[i][0] = segsNorm[i][0] + fx; - segs[i][1] = segsNorm[i][1] + fy; - segs[i][2] = segsNorm[i][2] + fx; - segs[i][3] = segsNorm[i][3] + fy; - segs[i][4] = segsNorm[i][4] + fx; - segs[i][5] = segsNorm[i][5] + fy; - ctx.bezierCurveTo.apply(ctx, segs[i]); - } - }; - - /** - * Calculate bounding box of a elliptic-arc - * @param {Number} fx start point of arc - * @param {Number} fy - * @param {Number} rx horizontal radius - * @param {Number} ry vertical radius - * @param {Number} rot angle of horizontal axe - * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points - * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction - * @param {Number} tx end point of arc - * @param {Number} ty - */ - fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { - - var fromX = 0, fromY = 0, bound = [ ], bounds = [ ], - segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot), - boundCopy = [[ ], [ ]]; - - for (var i = 0, len = segs.length; i < len; i++) { - bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); - boundCopy[0].x = bound[0].x + fx; - boundCopy[0].y = bound[0].y + fy; - boundCopy[1].x = bound[1].x + fx; - boundCopy[1].y = bound[1].y + fy; - bounds.push(boundCopy[0]); - bounds.push(boundCopy[1]); - fromX = segs[i][4]; - fromY = segs[i][5]; - } - return bounds; - }; - - /** - * Calculate bounding box of a beziercurve - * @param {Number} x0 starting point - * @param {Number} y0 - * @param {Number} x1 first control point - * @param {Number} y1 - * @param {Number} x2 secondo control point - * @param {Number} y2 - * @param {Number} x3 end of beizer - * @param {Number} y3 - */ - // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. - function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { - var argsString = _join.call(arguments); - if (boundsOfCurveCache[argsString]) { - return boundsOfCurveCache[argsString]; - } - - var sqrt = Math.sqrt, - min = Math.min, max = Math.max, - abs = Math.abs, tvalues = [ ], - bounds = [[ ], [ ]], - a, b, c, t, t1, t2, b2ac, sqrtb2ac; - - b = 6 * x0 - 12 * x1 + 6 * x2; - a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; - c = 3 * x1 - 3 * x0; - - for (var i = 0; i < 2; ++i) { - if (i > 0) { - b = 6 * y0 - 12 * y1 + 6 * y2; - a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; - c = 3 * y1 - 3 * y0; - } - - if (abs(a) < 1e-12) { - if (abs(b) < 1e-12) { - continue; - } - t = -c / b; - if (0 < t && t < 1) { - tvalues.push(t); - } - continue; - } - b2ac = b * b - 4 * c * a; - if (b2ac < 0) { - continue; - } - sqrtb2ac = sqrt(b2ac); - t1 = (-b + sqrtb2ac) / (2 * a); - if (0 < t1 && t1 < 1) { - tvalues.push(t1); - } - t2 = (-b - sqrtb2ac) / (2 * a); - if (0 < t2 && t2 < 1) { - tvalues.push(t2); - } - } - - var x, y, j = tvalues.length, jlen = j, mt; - while (j--) { - t = tvalues[j]; - mt = 1 - t; - x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); - bounds[0][j] = x; - - y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); - bounds[1][j] = y; - } - - bounds[0][jlen] = x0; - bounds[1][jlen] = y0; - bounds[0][jlen + 1] = x3; - bounds[1][jlen + 1] = y3; - var result = [ - { - x: min.apply(null, bounds[0]), - y: min.apply(null, bounds[1]) - }, - { - x: max.apply(null, bounds[0]), - y: max.apply(null, bounds[1]) - } - ]; - boundsOfCurveCache[argsString] = result; - return result; - } - - fabric.util.getBoundsOfCurve = getBoundsOfCurve; - -})(); - - -(function() { - - var slice = Array.prototype.slice; - - /* _ES5_COMPAT_START_ */ - - if (!Array.prototype.indexOf) { - /** - * Finds index of an element in an array - * @param {Any} searchElement - * @param {Number} [fromIndex] - * @return {Number} - */ - Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - if (this === void 0 || this === null) { - throw new TypeError(); - } - var t = Object(this), len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = 0; - if (arguments.length > 0) { - n = Number(arguments[1]); - if (n !== n) { // shortcut for verifying if it's 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 - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} method Name of a method to invoke - * @return {Array} - */ - function invoke(array, method) { - var args = slice.call(arguments, 2), result = [ ]; - for (var i = 0, len = array.length; i < len; i++) { - result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); - } - return result; - } - - /** - * Finds maximum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {Any} - */ - function max(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 >= value2; - }); - } - - /** - * Finds minimum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {Any} - */ - function min(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 < value2; - }); - } - - /** - * @private - */ - function find(array, byProperty, condition) { - if (!array || array.length === 0) { - return; - } - - var i = array.length - 1, - result = byProperty ? array[i][byProperty] : array[i]; - if (byProperty) { - while (i--) { - if (condition(array[i][byProperty], result)) { - result = array[i][byProperty]; - } - } - } - else { - while (i--) { - if (condition(array[i], result)) { - result = array[i]; - } - } - } - return result; - } - - /** - * @namespace fabric.util.array - */ - fabric.util.array = { - invoke: invoke, - min: min, - max: max - }; - -})(); - - -(function() { - - /** - * Copies all enumerable properties of one object to another - * @memberOf fabric.util.object - * @param {Object} destination Where to copy to - * @param {Object} source Where to copy from - * @return {Object} - */ - function extend(destination, source) { - // JScript DontEnum bug is not taken care of - for (var property in source) { - destination[property] = source[property]; - } - return destination; - } - - /** - * Creates an empty object and copies all enumerable properties of another object to it - * @memberOf fabric.util.object - * @param {Object} object Object to clone - * @return {Object} - */ - function clone(object) { - return extend({ }, object); - } - - /** @namespace fabric.util.object */ - fabric.util.object = { - extend: extend, - clone: clone - }; - -})(); - - -(function() { - - /* _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 - * @memberOf fabric.util.string - * @param {String} string String to camelize - * @return {String} Camelized version of a string - */ - function camelize(string) { - return string.replace(/-+(.)?/g, function(match, character) { - return character ? character.toUpperCase() : ''; - }); - } - - /** - * Capitalizes a string - * @memberOf fabric.util.string - * @param {String} string String to capitalize - * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized - * and other letters stay untouched, if false first letter is capitalized - * and other letters are converted to lowercase. - * @return {String} Capitalized version of a string - */ - function capitalize(string, firstLetterOnly) { - return string.charAt(0).toUpperCase() + - (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); - } - - /** - * Escapes XML in a string - * @memberOf fabric.util.string - * @param {String} string String to escape - * @return {String} Escaped version of a string - */ - function escapeXml(string) { - return string.replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>'); - } - - /** - * String utilities - * @namespace fabric.util.string - */ - fabric.util.string = { - camelize: camelize, - capitalize: capitalize, - escapeXml: escapeXml - }; -}()); - - -/* _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() { - - var slice = Array.prototype.slice, emptyFunction = function() { }, - - IS_DONTENUM_BUGGY = (function() { - for (var p in { toString: 1 }) { - if (p === 'toString') { - return false; - } - } - return true; - })(), - - /** @ignore */ - addMethods = function(klass, source, parent) { - for (var property in source) { - - if (property in klass.prototype && - typeof klass.prototype[property] === 'function' && - (source[property] + '').indexOf('callSuper') > -1) { - - klass.prototype[property] = (function(property) { - return function() { - - var superclass = this.constructor.superclass; - this.constructor.superclass = parent; - var returnValue = source[property].apply(this, arguments); - this.constructor.superclass = superclass; - - if (property !== 'initialize') { - return returnValue; - } - }; - })(property); - } - else { - klass.prototype[property] = source[property]; - } - - if (IS_DONTENUM_BUGGY) { - if (source.toString !== Object.prototype.toString) { - klass.prototype.toString = source.toString; - } - if (source.valueOf !== Object.prototype.valueOf) { - klass.prototype.valueOf = source.valueOf; - } - } - } - }; - - function Subclass() { } - - function callSuper(methodName) { - var fn = this.constructor.superclass.prototype[methodName]; - return (arguments.length > 1) - ? fn.apply(this, slice.call(arguments, 1)) - : fn.call(this); - } - - /** - * Helper for creation of "classes". - * @memberOf fabric.util - * @param {Function} [parent] optional "Class" to inherit from - * @param {Object} [properties] Properties shared by all instances of this class - * (be careful modifying objects defined here as this would affect all instances) - */ - function createClass() { - var parent = null, - properties = slice.call(arguments, 0); - - if (typeof properties[0] === 'function') { - parent = properties.shift(); - } - function klass() { - this.initialize.apply(this, arguments); - } - - klass.superclass = parent; - klass.subclasses = [ ]; - - if (parent) { - Subclass.prototype = parent.prototype; - klass.prototype = new Subclass(); - parent.subclasses.push(klass); - } - for (var i = 0, length = properties.length; i < length; i++) { - addMethods(klass, properties[i], parent); - } - if (!klass.prototype.initialize) { - klass.prototype.initialize = emptyFunction; - } - klass.prototype.constructor = klass; - klass.prototype.callSuper = callSuper; - return klass; - } - - fabric.util.createClass = createClass; -})(); - - -(function () { - - var unknown = 'unknown'; - - /* EVENT HANDLING */ - - function areHostMethods(object) { - var methodNames = Array.prototype.slice.call(arguments, 1), - t, i, len = methodNames.length; - for (i = 0; i < len; i++) { - t = typeof object[methodNames[i]]; - if (!(/^(?:function|object|unknown)$/).test(t)) { - return false; - } - } - return true; - } - - /** @ignore */ - var getElement, - setElement, - getUniqueId = (function () { - var uid = 0; - return function (element) { - return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); - }; - })(); - - (function () { - var elements = { }; - /** @ignore */ - getElement = function (uid) { - return elements[uid]; - }; - /** @ignore */ - setElement = function (uid, element) { - elements[uid] = element; - }; - })(); - - function createListener(uid, handler) { - return { - handler: handler, - wrappedHandler: createWrappedHandler(uid, handler) - }; - } - - function createWrappedHandler(uid, handler) { - return function (e) { - handler.call(getElement(uid), e || fabric.window.event); - }; - } - - function createDispatcher(uid, eventName) { - return function (e) { - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - handlersForEvent[i].call(this, e || fabric.window.event); - } - } - }; - } - - var shouldUseAddListenerRemoveListener = ( - areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && - areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), - - shouldUseAttachEventDetachEvent = ( - areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && - areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), - - // IE branch - listeners = { }, - - // DOM L0 branch - handlers = { }, - - addListener, removeListener; - - if (shouldUseAddListenerRemoveListener) { - /** @ignore */ - addListener = function (element, eventName, handler) { - element.addEventListener(eventName, handler, false); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - element.removeEventListener(eventName, handler, false); - }; - } - - else if (shouldUseAttachEventDetachEvent) { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - setElement(uid, element); - if (!listeners[uid]) { - listeners[uid] = { }; - } - if (!listeners[uid][eventName]) { - listeners[uid][eventName] = [ ]; - - } - var listener = createListener(uid, handler); - listeners[uid][eventName].push(listener); - element.attachEvent('on' + eventName, listener.wrappedHandler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element), listener; - if (listeners[uid] && listeners[uid][eventName]) { - for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { - listener = listeners[uid][eventName][i]; - if (listener && listener.handler === handler) { - element.detachEvent('on' + eventName, listener.wrappedHandler); - listeners[uid][eventName][i] = null; - } - } - } - }; - } - else { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (!handlers[uid]) { - handlers[uid] = { }; - } - if (!handlers[uid][eventName]) { - handlers[uid][eventName] = [ ]; - var existingHandler = element['on' + eventName]; - if (existingHandler) { - handlers[uid][eventName].push(existingHandler); - } - element['on' + eventName] = createDispatcher(uid, eventName); - } - handlers[uid][eventName].push(handler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - if (handlersForEvent[i] === handler) { - handlersForEvent.splice(i, 1); - } - } - } - }; - } - - /** - * Adds an event listener to an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.addListener = addListener; - - /** - * Removes an event listener from an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.removeListener = removeListener; - - /** - * Cross-browser wrapper for getting event's coordinates - * @memberOf fabric.util - * @param {Event} event Event object - * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn - */ - function getPointer(event, upperCanvasEl) { - event || (event = fabric.window.event); - - var element = event.target || - (typeof event.srcElement !== unknown ? event.srcElement : null), - - scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); - - return { - x: pointerX(event) + scroll.left, - y: pointerY(event) + scroll.top - }; - } - - var pointerX = function(event) { - // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) - // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] - // need to investigate later - return (typeof event.clientX !== unknown ? event.clientX : 0); - }, - - pointerY = function(event) { - return (typeof event.clientY !== unknown ? event.clientY : 0); - }; - - function _getPointer(event, pageProp, clientProp) { - var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; - - return (event[touchProp] && event[touchProp][0] - ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) - || event[clientProp] - : event[clientProp]); - } - - if (fabric.isTouchSupported) { - pointerX = function(event) { - return _getPointer(event, 'pageX', 'clientX'); - }; - pointerY = function(event) { - return _getPointer(event, 'pageY', 'clientY'); - }; - } - - fabric.util.getPointer = getPointer; - - fabric.util.object.extend(fabric.util, fabric.Observable); - -})(); - - -(function () { - - /** - * Cross-browser wrapper for setting element's style - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {Object} styles - * @return {HTMLElement} Element that was passed as a first argument - */ - function setStyle(element, styles) { - var elementStyle = element.style; - if (!elementStyle) { - return element; - } - if (typeof styles === 'string') { - element.style.cssText += ';' + styles; - return styles.indexOf('opacity') > -1 - ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) - : element; - } - for (var property in styles) { - if (property === 'opacity') { - setOpacity(element, styles[property]); - } - else { - var normalizedProperty = (property === 'float' || property === 'cssFloat') - ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') - : property; - elementStyle[normalizedProperty] = styles[property]; - } - } - return element; - } - - var parseEl = fabric.document.createElement('div'), - supportsOpacity = typeof parseEl.style.opacity === 'string', - supportsFilters = typeof parseEl.style.filter === 'string', - reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, - - /** @ignore */ - setOpacity = function (element) { return element; }; - - if (supportsOpacity) { - /** @ignore */ - setOpacity = function(element, value) { - element.style.opacity = value; - return element; - }; - } - else if (supportsFilters) { - /** @ignore */ - setOpacity = function(element, value) { - var es = element.style; - if (element.currentStyle && !element.currentStyle.hasLayout) { - es.zoom = 1; - } - if (reOpacity.test(es.filter)) { - value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); - es.filter = es.filter.replace(reOpacity, value); - } - else { - es.filter += ' alpha(opacity=' + (value * 100) + ')'; - } - return element; - }; - } - - fabric.util.setStyle = setStyle; - -})(); - - -(function() { - - var _slice = Array.prototype.slice; - - /** - * Takes id and returns an element with that id (if one exists in a document) - * @memberOf fabric.util - * @param {String|HTMLElement} id - * @return {HTMLElement|null} - */ - function getById(id) { - return typeof id === 'string' ? fabric.document.getElementById(id) : id; - } - - var sliceCanConvertNodelists, - /** - * Converts an array-like object (e.g. arguments or NodeList) to an array - * @memberOf fabric.util - * @param {Object} arrayLike - * @return {Array} - */ - toArray = function(arrayLike) { - return _slice.call(arrayLike, 0); - }; - - try { - sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; - } - catch (err) { } - - if (!sliceCanConvertNodelists) { - toArray = function(arrayLike) { - var arr = new Array(arrayLike.length), i = arrayLike.length; - while (i--) { - arr[i] = arrayLike[i]; - } - return arr; - }; - } - - /** - * Creates specified element with specified attributes - * @memberOf fabric.util - * @param {String} tagName Type of an element to create - * @param {Object} [attributes] Attributes to set on an element - * @return {HTMLElement} Newly created element - */ - function makeElement(tagName, attributes) { - var el = fabric.document.createElement(tagName); - for (var prop in attributes) { - if (prop === 'class') { - el.className = attributes[prop]; - } - else if (prop === 'for') { - el.htmlFor = attributes[prop]; - } - else { - el.setAttribute(prop, attributes[prop]); - } - } - return el; - } - - /** - * Adds class to an element - * @memberOf fabric.util - * @param {HTMLElement} element Element to add class to - * @param {String} className Class to add to an element - */ - function addClass(element, className) { - if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { - element.className += (element.className ? ' ' : '') + className; - } - } - - /** - * Wraps element with another element - * @memberOf fabric.util - * @param {HTMLElement} element Element to wrap - * @param {HTMLElement|String} wrapper Element to wrap with - * @param {Object} [attributes] Attributes to set on a wrapper - * @return {HTMLElement} wrapper - */ - function wrapElement(element, wrapper, attributes) { - if (typeof wrapper === 'string') { - wrapper = makeElement(wrapper, attributes); - } - if (element.parentNode) { - element.parentNode.replaceChild(wrapper, element); - } - wrapper.appendChild(element); - return wrapper; - } - - /** - * Returns element scroll offsets - * @memberOf fabric.util - * @param {HTMLElement} element Element to operate on - * @param {HTMLElement} upperCanvasEl Upper canvas element - * @return {Object} Object with left/top values - */ - function getScrollLeftTop(element, upperCanvasEl) { - - var firstFixedAncestor, - origElement, - left = 0, - top = 0, - docElement = fabric.document.documentElement, - body = fabric.document.body || { - scrollLeft: 0, scrollTop: 0 - }; - - origElement = element; - - while (element && element.parentNode && !firstFixedAncestor) { - - element = element.parentNode; - - if (element.nodeType === 1 && - fabric.util.getElementStyle(element, 'position') === 'fixed') { - firstFixedAncestor = element; - } - - if (element.nodeType === 1 && - origElement !== upperCanvasEl && - fabric.util.getElementStyle(element, 'position') === 'absolute') { - left = 0; - top = 0; - } - else if (element === fabric.document) { - left = body.scrollLeft || docElement.scrollLeft || 0; - top = body.scrollTop || docElement.scrollTop || 0; - } - else { - left += element.scrollLeft || 0; - top += element.scrollTop || 0; - } - } - - return { left: left, top: top }; - } - - /** - * Returns offset for a given element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element Element to get offset for - * @return {Object} Object with "left" and "top" properties - */ - function getElementOffset(element) { - var docElem, - doc = element && element.ownerDocument, - box = { left: 0, top: 0 }, - offset = { left: 0, top: 0 }, - scrollLeftTop, - offsetAttributes = { - borderLeftWidth: 'left', - borderTopWidth: 'top', - paddingLeft: 'left', - paddingTop: 'top' - }; - - if (!doc) { - return { left: 0, top: 0 }; - } - - for (var attr in offsetAttributes) { - offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; - } - - docElem = doc.documentElement; - if ( typeof element.getBoundingClientRect !== 'undefined' ) { - box = element.getBoundingClientRect(); - } - - scrollLeftTop = fabric.util.getScrollLeftTop(element, null); - - return { - left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, - top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top - }; - } - - /** - * Returns style attribute value of a given element - * @memberOf fabric.util - * @param {HTMLElement} element Element to get style attribute for - * @param {String} attr Style attribute to get for element - * @return {String} Style attribute value of the given element. - */ - var getElementStyle; - if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { - getElementStyle = function(element, attr) { - var style = fabric.document.defaultView.getComputedStyle(element, null); - return style ? style[attr] : undefined; - }; - } - else { - getElementStyle = function(element, attr) { - var value = element.style[attr]; - if (!value && element.currentStyle) { - value = element.currentStyle[attr]; - } - return value; - }; - } - - (function () { - var style = fabric.document.documentElement.style, - selectProp = 'userSelect' in style - ? 'userSelect' - : 'MozUserSelect' in style - ? 'MozUserSelect' - : 'WebkitUserSelect' in style - ? 'WebkitUserSelect' - : 'KhtmlUserSelect' in style - ? 'KhtmlUserSelect' - : ''; - - /** - * Makes element unselectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make unselectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementUnselectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = fabric.util.falseFunction; - } - if (selectProp) { - element.style[selectProp] = 'none'; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = 'on'; - } - return element; - } - - /** - * Makes element selectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make selectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementSelectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = null; - } - if (selectProp) { - element.style[selectProp] = ''; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = ''; - } - return element; - } - - fabric.util.makeElementUnselectable = makeElementUnselectable; - fabric.util.makeElementSelectable = makeElementSelectable; - })(); - - (function() { - - /** - * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading - * @memberOf fabric.util - * @param {String} url URL of a script to load - * @param {Function} callback Callback to execute when script is finished loading - */ - function getScript(url, callback) { - var headEl = fabric.document.getElementsByTagName('head')[0], - scriptEl = fabric.document.createElement('script'), - loading = true; - - /** @ignore */ - scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { - if (loading) { - if (typeof this.readyState === 'string' && - this.readyState !== 'loaded' && - this.readyState !== 'complete') { - return; - } - loading = false; - callback(e || fabric.window.event); - scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; - } - }; - scriptEl.src = url; - headEl.appendChild(scriptEl); - // causes issue in Opera - // headEl.removeChild(scriptEl); - } - - fabric.util.getScript = getScript; - })(); - - fabric.util.getById = getById; - fabric.util.toArray = toArray; - fabric.util.makeElement = makeElement; - fabric.util.addClass = addClass; - fabric.util.wrapElement = wrapElement; - fabric.util.getScrollLeftTop = getScrollLeftTop; - fabric.util.getElementOffset = getElementOffset; - fabric.util.getElementStyle = getElementStyle; - -})(); - - -(function() { - - function addParamToUrl(url, param) { - return url + (/\?/.test(url) ? '&' : '?') + param; - } - - var makeXHR = (function() { - var factories = [ - function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, - function() { return new XMLHttpRequest(); } - ]; - for (var i = factories.length; i--; ) { - try { - var req = factories[i](); - if (req) { - return factories[i]; - } - } - catch (err) { } - } - })(); - - function emptyFn() { } - - /** - * Cross-browser abstraction for sending XMLHttpRequest - * @memberOf fabric.util - * @param {String} url URL to send XMLHttpRequest to - * @param {Object} [options] Options object - * @param {String} [options.method="GET"] - * @param {Function} options.onComplete Callback to invoke when request is completed - * @return {XMLHttpRequest} request - */ - function request(url, options) { - - options || (options = { }); - - var method = options.method ? options.method.toUpperCase() : 'GET', - onComplete = options.onComplete || function() { }, - xhr = makeXHR(), - body; - - /** @ignore */ - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - onComplete(xhr); - xhr.onreadystatechange = emptyFn; - } - }; - - if (method === 'GET') { - body = null; - if (typeof options.parameters === 'string') { - url = addParamToUrl(url, options.parameters); - } - } - - xhr.open(method, url, true); - - if (method === 'POST' || method === 'PUT') { - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - } - - xhr.send(body); - return xhr; - } - - fabric.util.request = request; -})(); - - -/** - * Wrapper around `console.log` (when available) - * @param {Any} [values] Values to log - */ -fabric.log = function() { }; - -/** - * Wrapper around `console.warn` (when available) - * @param {Any} [values] Values to log as a warning - */ -fabric.warn = function() { }; - -if (typeof console !== 'undefined') { - - ['log', 'warn'].forEach(function(methodName) { - - if (typeof console[methodName] !== 'undefined' && - typeof console[methodName].apply === 'function') { - - fabric[methodName] = function() { - return console[methodName].apply(console, arguments); - }; - } - }); +if (typeof exports !== "undefined") { + exports.fabric = fabric; } +if (typeof document !== "undefined" && typeof window !== "undefined") { + fabric.document = document; + fabric.window = window; + window.fabric = fabric; +} else { + fabric.document = require("jsdom").jsdom(""); + if (fabric.document.createWindow) { + fabric.window = fabric.document.createWindow(); + } else { + fabric.window = fabric.document.parentWindow; + } +} + +fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; + +fabric.isLikelyNode = typeof Buffer !== "undefined" && typeof window === "undefined"; + +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; + +fabric.reNum = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)"; (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; + function _removeEventListener(eventName, handler) { + if (!this.__eventListeners[eventName]) { + return; } - onChange(easing(currentTime, startValue, byValue, duration)); - if (time > finish) { - options.onComplete && options.onComplete(); - return; + if (handler) { + fabric.util.removeFromArray(this.__eventListeners[eventName], handler); + } else { + this.__eventListeners[eventName].length = 0; } - 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 observe(eventName, handler) { + if (!this.__eventListeners) { + this.__eventListeners = {}; + } + if (arguments.length === 1) { + for (var prop in eventName) { + this.on(prop, eventName[prop]); + } + } else { + if (!this.__eventListeners[eventName]) { + this.__eventListeners[eventName] = []; + } + this.__eventListeners[eventName].push(handler); + } + return this; + } + function stopObserving(eventName, handler) { + if (!this.__eventListeners) { + return; + } + if (arguments.length === 0) { + this.__eventListeners = {}; + } else if (arguments.length === 1 && typeof arguments[0] === "object") { + for (var prop in eventName) { + _removeEventListener.call(this, prop, eventName[prop]); + } + } else { + _removeEventListener.call(this, eventName, handler); + } + return this; + } + function fire(eventName, options) { + if (!this.__eventListeners) { + return; + } + var listenersForEvent = this.__eventListeners[eventName]; + if (!listenersForEvent) { + return; + } + for (var i = 0, len = listenersForEvent.length; i < len; i++) { + listenersForEvent[i].call(this, options || {}); + } + return this; + } + fabric.Observable = { + observe: observe, + stopObserving: stopObserving, + fire: fire, + on: observe, + off: stopObserving, + trigger: fire + }; })(); - -(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; +fabric.Collection = { + add: function() { + this._objects.push.apply(this._objects, arguments); + for (var i = 0, length = arguments.length; i < length; i++) { + this._onObjectAdded(arguments[i]); + } + this.renderOnAddRemove && this.renderAll(); + return this; }, - - /** - * Quadratic easing out - * @memberOf fabric.util.ease - */ - easeOutQuad: function(t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; + insertAt: function(object, index, nonSplicing) { + var objects = this.getObjects(); + if (nonSplicing) { + objects[index] = object; + } else { + objects.splice(index, 0, object); + } + this._onObjectAdded(object); + this.renderOnAddRemove && this.renderAll(); + return this; }, - - /** - * 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; + remove: function() { + var objects = this.getObjects(), index; + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + if (index !== -1) { + objects.splice(index, 1); + this._onObjectRemoved(arguments[i]); + } + } + this.renderOnAddRemove && this.renderAll(); + return this; }, - - /** - * Cubic easing in - * @memberOf fabric.util.ease - */ - easeInCubic: function(t, b, c, d) { - return c * (t /= d) * t * t + b; + forEachObject: function(callback, context) { + var objects = this.getObjects(), i = objects.length; + while (i--) { + callback.call(context, objects[i], i, objects); + } + return this; }, - - 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 - }; - -}()); - + getObjects: function(type) { + if (typeof type === "undefined") { + return this._objects; + } + return this._objects.filter(function(o) { + return o.type === type; + }); + }, + item: function(index) { + return this.getObjects()[index]; + }, + isEmpty: function() { + return this.getObjects().length === 0; + }, + size: function() { + return this.getObjects().length; + }, + contains: function(object) { + return this.getObjects().indexOf(object) > -1; + }, + complexity: function() { + return this.getObjects().reduce(function(memo, current) { + memo += current.complexity ? current.complexity() : 0; + return memo; + }, 0); + } +}; (function(global) { - - 'use strict'; - - /** - * @name fabric - * @namespace - */ - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - capitalize = fabric.util.string.capitalize, - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - parseUnit = fabric.util.parseUnit, - multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, - - attributesMap = { - cx: 'left', - x: 'left', - r: 'radius', - cy: 'top', - y: 'top', - display: 'visible', - visibility: 'visible', - transform: 'transformMatrix', - 'fill-opacity': 'fillOpacity', - 'fill-rule': 'fillRule', - 'font-family': 'fontFamily', - 'font-size': 'fontSize', - 'font-style': 'fontStyle', - 'font-weight': 'fontWeight', - 'stroke-dasharray': 'strokeDashArray', - 'stroke-linecap': 'strokeLineCap', - 'stroke-linejoin': 'strokeLineJoin', - 'stroke-miterlimit': 'strokeMiterLimit', - 'stroke-opacity': 'strokeOpacity', - 'stroke-width': 'strokeWidth', - 'text-decoration': 'textDecoration', - 'text-anchor': 'originX' - }, - - colorAttributes = { - stroke: 'strokeOpacity', - fill: 'fillOpacity' - }; - - fabric.cssRules = { }; - fabric.gradientDefs = { }; - - function normalizeAttr(attr) { - // transform attribute names - if (attr in attributesMap) { - return attributesMap[attr]; - } - return attr; - } - - function normalizeValue(attr, value, parentAttributes, fontSize) { - var isArray = Object.prototype.toString.call(value) === '[object Array]', - parsed; - - if ((attr === 'fill' || attr === 'stroke') && value === 'none') { - value = ''; - } - else if (attr === 'strokeDashArray') { - value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) { - return parseFloat(n); - }); - } - else if (attr === 'transformMatrix') { - if (parentAttributes && parentAttributes.transformMatrix) { - value = multiplyTransformMatrices( - parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); - } - else { - value = fabric.parseTransformAttribute(value); - } - } - else if (attr === 'visible') { - value = (value === 'none' || value === 'hidden') ? false : true; - // display=none on parent element always takes precedence over child element - if (parentAttributes && parentAttributes.visible === false) { - value = false; - } - } - else if (attr === 'originX' /* text-anchor */) { - value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; - } - else { - parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); - } - - return (!isArray && isNaN(parsed) ? value : parsed); - } - - /** - * @private - * @param {Object} attributes Array of attributes to parse - */ - function _setStrokeFillOpacity(attributes) { - for (var attr in colorAttributes) { - - if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') { - continue; - } - - if (attributes[attr].indexOf('url(') === 0) { - continue; - } - - var color = new fabric.Color(attributes[attr]); - attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); - } - return attributes; - } - - /** - * Parses "transform" attribute, returning an array of values - * @static - * @function - * @memberOf fabric - * @param {String} attributeValue String containing attribute value - * @return {Array} Array of 6 elements representing transformation matrix - */ - fabric.parseTransformAttribute = (function() { - function rotateMatrix(matrix, args) { - var angle = args[0]; - - matrix[0] = Math.cos(angle); - matrix[1] = Math.sin(angle); - matrix[2] = -Math.sin(angle); - matrix[3] = Math.cos(angle); - } - - function scaleMatrix(matrix, args) { - var multiplierX = args[0], - multiplierY = (args.length === 2) ? args[1] : args[0]; - - matrix[0] = multiplierX; - matrix[3] = multiplierY; - } - - function skewXMatrix(matrix, args) { - matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0])); - } - - function skewYMatrix(matrix, args) { - matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0])); - } - - function translateMatrix(matrix, args) { - matrix[4] = args[0]; - if (args.length === 2) { - matrix[5] = args[1]; - } - } - - // identity matrix - var iMatrix = [ - 1, // a - 0, // b - 0, // c - 1, // d - 0, // e - 0 // f - ], - - // == begin transform regexp - number = fabric.reNum, - - commaWsp = '(?:\\s+,?\\s*|,\\s*)', - - skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', - - skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', - - rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + ')' + - commaWsp + '(' + number + '))?\\s*\\))', - - scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - matrix = '(?:(matrix)\\s*\\(\\s*' + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + - '\\s*\\))', - - transform = '(?:' + - matrix + '|' + - translate + '|' + - scale + '|' + - rotate + '|' + - skewX + '|' + - skewY + - ')', - - transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', - - transformList = '^\\s*(?:' + transforms + '?)\\s*$', - - // http://www.w3.org/TR/SVG/coords.html#TransformAttribute - reTransformList = new RegExp(transformList), - // == end transform regexp - - reTransform = new RegExp(transform, 'g'); - - return function(attributeValue) { - - // start with identity matrix - var matrix = iMatrix.concat(), - matrices = [ ]; - - // return if no argument was given or - // an argument does not match transform attribute regexp - if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { - return matrix; - } - - attributeValue.replace(reTransform, function(match) { - - var m = new RegExp(transform).exec(match).filter(function (match) { - return (match !== '' && match != null); - }), - operation = m[1], - args = m.slice(2).map(parseFloat); - - switch (operation) { - case 'translate': - translateMatrix(matrix, args); - break; - case 'rotate': - args[0] = fabric.util.degreesToRadians(args[0]); - rotateMatrix(matrix, args); - break; - case 'scale': - scaleMatrix(matrix, args); - break; - case 'skewX': - skewXMatrix(matrix, args); - break; - case 'skewY': - skewYMatrix(matrix, args); - break; - case 'matrix': - matrix = args; - break; - } - - // snapshot current matrix into matrices array - matrices.push(matrix.concat()); - // reset - matrix = iMatrix.concat(); - }); - - var combinedMatrix = matrices[0]; - while (matrices.length > 1) { - matrices.shift(); - combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); - } - return combinedMatrix; - }; - })(); - - /** - * @private - */ - function parseStyleString(style, oStyle) { - var attr, value; - style.replace(/;$/, '').split(';').forEach(function (chunk) { - var pair = chunk.split(':'); - - attr = normalizeAttr(pair[0].trim().toLowerCase()); - value = normalizeValue(attr, pair[1].trim()); - - oStyle[attr] = value; - }); - } - - /** - * @private - */ - function parseStyleObject(style, oStyle) { - var attr, value; - for (var prop in style) { - if (typeof style[prop] === 'undefined') { - continue; - } - - attr = normalizeAttr(prop.toLowerCase()); - value = normalizeValue(attr, style[prop]); - - oStyle[attr] = value; - } - } - - /** - * @private - */ - function getGlobalStylesForElement(element, svgUid) { - var styles = { }; - for (var rule in fabric.cssRules[svgUid]) { - if (elementMatchesRule(element, rule.split(' '))) { - for (var property in fabric.cssRules[svgUid][rule]) { - styles[property] = fabric.cssRules[svgUid][rule][property]; - } - } - } - return styles; - } - - /** - * @private - */ - function elementMatchesRule(element, selectors) { - var firstMatching, parentMatching = true; - //start from rightmost selector. - firstMatching = selectorMatches(element, selectors.pop()); - if (firstMatching && selectors.length) { - parentMatching = doesSomeParentMatch(element, selectors); - } - return firstMatching && parentMatching && (selectors.length === 0); - } - - function doesSomeParentMatch(element, selectors) { - var selector, parentMatching = true; - while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { - if (parentMatching) { - selector = selectors.pop(); - } - element = element.parentNode; - parentMatching = selectorMatches(element, selector); - } - return selectors.length === 0; - } - /** - * @private - */ - function selectorMatches(element, selector) { - var nodeName = element.nodeName, - classNames = element.getAttribute('class'), - id = element.getAttribute('id'), matcher; - // i check if a selector matches slicing away part from it. - // if i get empty string i should match - matcher = new RegExp('^' + nodeName, 'i'); - selector = selector.replace(matcher, ''); - if (id && selector.length) { - matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); - selector = selector.replace(matcher, ''); - } - if (classNames && selector.length) { - classNames = classNames.split(' '); - for (var i = classNames.length; i--;) { - matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); - selector = selector.replace(matcher, ''); - } - } - return selector.length === 0; - } - - /** - * @private - */ - function parseUseDirectives(doc) { - var nodelist = doc.getElementsByTagName('use'); - while (nodelist.length) { - var el = nodelist[0], - xlink = el.getAttribute('xlink:href').substr(1), - x = el.getAttribute('x') || 0, - y = el.getAttribute('y') || 0, - el2 = doc.getElementById(xlink).cloneNode(true), - currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', - parentNode; - - for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { - var attr = attrs.item(j); - if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { - continue; - } - - if (attr.nodeName === 'transform') { - currentTrans = attr.nodeValue + ' ' + currentTrans; - } - else { - el2.setAttribute(attr.nodeName, attr.nodeValue); - } - } - - el2.setAttribute('transform', currentTrans); - el2.setAttribute('instantiated_by_use', '1'); - el2.removeAttribute('id'); - parentNode = el.parentNode; - parentNode.replaceChild(el2, el); - } - } - - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute - // matches, e.g.: +14.56e-12, etc. - var reViewBoxAttrValue = new RegExp( - '^' + - '\\s*(' + fabric.reNum + '+)\\s*,?' + - '\\s*(' + fabric.reNum + '+)\\s*,?' + - '\\s*(' + fabric.reNum + '+)\\s*,?' + - '\\s*(' + fabric.reNum + '+)\\s*' + - '$' - ); - - /** - * Add a element that envelop all child elements and makes the viewbox transformMatrix descend on all elements - */ - function addVBTransform(element, widthAttr, heightAttr) { - - var viewBoxAttr = element.getAttribute('viewBox'), - scaleX = 1, - scaleY = 1, - minX = 0, - minY = 0, - viewBoxWidth, viewBoxHeight, matrix, el; - - if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { - minX = -parseFloat(viewBoxAttr[1]), - minY = -parseFloat(viewBoxAttr[2]), - viewBoxWidth = parseFloat(viewBoxAttr[3]), - viewBoxHeight = parseFloat(viewBoxAttr[4]); - } - else { - return; - } - if (widthAttr && widthAttr !== viewBoxWidth) { - scaleX = widthAttr / viewBoxWidth; - } - if (heightAttr && heightAttr !== viewBoxHeight) { - scaleY = heightAttr / viewBoxHeight; - } - - // default is to preserve aspect ratio - // preserveAspectRatio attribute to be implemented - scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX); - - if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) { - return; - } - matrix = ' matrix(' + scaleX + - ' 0' + - ' 0 ' + - scaleY + ' ' + - (minX * scaleX) + ' ' + - (minY * scaleY) + ') '; - - if (element.tagName === 'svg') { - el = element.ownerDocument.createElement('g'); - while (element.firstChild != null) { - el.appendChild(element.firstChild); - } - element.appendChild(el); - } - else { - el = element; - matrix = el.getAttribute('transform') + matrix; - } - - el.setAttribute('transform', matrix); - } - - /** - * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - fabric.parseSVGDocument = (function() { - - var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, - reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/; - - function hasAncestorWithNodeName(element, nodeName) { - while (element && (element = element.parentNode)) { - if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) { - return true; - } - } - return false; - } - - return function(doc, callback, reviver) { - if (!doc) { - return; - } - - parseUseDirectives(doc); - - var startTime = new Date(), - svgUid = fabric.Object.__uid++, - widthAttr, heightAttr, toBeParsed = false; - - if (doc.getAttribute('width') && doc.getAttribute('width') !== '100%') { - widthAttr = parseUnit(doc.getAttribute('width')); - } - if (doc.getAttribute('height') && doc.getAttribute('height') !== '100%') { - heightAttr = parseUnit(doc.getAttribute('height')); - } - - if (!widthAttr || !heightAttr) { - var viewBoxAttr = doc.getAttribute('viewBox'); - if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { - widthAttr = parseFloat(viewBoxAttr[3]), - heightAttr = parseFloat(viewBoxAttr[4]); - } - else { - toBeParsed = true; - } - } - - addVBTransform(doc, widthAttr, heightAttr); - - var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - - if (descendants.length === 0 && fabric.isLikelyNode) { - // we're likely in node, where "o3-xml" library fails to gEBTN("*") - // https://github.com/ajaxorg/node-o3-xml/issues/21 - descendants = doc.selectNodes('//*[name(.)!="svg"]'); - var arr = [ ]; - for (var i = 0, len = descendants.length; i < len; i++) { - arr[i] = descendants[i]; - } - descendants = arr; - } - - var elements = descendants.filter(function(el) { - reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0); - return reAllowedSVGTagNames.test(el.tagName) && - !hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement - }); - - if (!elements || (elements && !elements.length)) { - callback && callback([], {}); - return; - } - - var options = { - width: widthAttr, - height: heightAttr, - svgUid: svgUid, - toBeParsed: toBeParsed - }; - - fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); - fabric.cssRules[svgUid] = fabric.getCSSRules(doc); - // Precedence of rules: style > class > attribute - fabric.parseElements(elements, function(instances) { - fabric.documentParsingTime = new Date() - startTime; - if (callback) { - callback(instances, options); - } - }, clone(options), reviver); - }; - })(); - - /** - * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) - * @namespace - */ - var svgCache = { - - /** - * @param {String} name - * @param {Function} callback - */ - has: function (name, callback) { - callback(false); - }, - - get: function () { - /* NOOP */ - }, - - set: function () { - /* NOOP */ - } - }; - - /** - * @private - */ - function _enlivenCachedObject(cachedObject) { - - var objects = cachedObject.objects, - options = cachedObject.options; - - objects = objects.map(function (o) { - return fabric[capitalize(o.type)].fromObject(o); - }); - - return ({ objects: objects, options: options }); - } - - /** - * @private - */ - function _createSVGPattern(markup, canvas, property) { - if (canvas[property] && canvas[property].toSVG) { - markup.push( - '', - '' - ); - } - } - - var reFontDeclaration = new RegExp( - '(normal|italic)?\\s*(normal|small-caps)?\\s*' + - '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + - fabric.reNum + - '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)'); - - extend(fabric, { - /** - * Parses a short font declaration, building adding its properties to a style object - * @static - * @function - * @memberOf fabric - * @param {String} value font declaration - * @param {Object} oStyle definition - */ - parseFontDeclaration: function(value, oStyle) { - var match = value.match(reFontDeclaration); - - if (!match) { - return; - } - var fontStyle = match[1], - // font variant is not used - // fontVariant = match[2], - fontWeight = match[3], - fontSize = match[4], - lineHeight = match[5], - fontFamily = match[6]; - - if (fontStyle) { - oStyle.fontStyle = fontStyle; - } - if (fontWeight) { - oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); - } - if (fontSize) { - oStyle.fontSize = parseUnit(fontSize); - } - if (fontFamily) { - oStyle.fontFamily = fontFamily; - } - if (lineHeight) { - oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; - } - }, - - /** - * Parses an SVG document, returning all of the gradient declarations found in it - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element - */ - getGradientDefs: function(doc) { - var linearGradientEls = doc.getElementsByTagName('linearGradient'), - radialGradientEls = doc.getElementsByTagName('radialGradient'), - el, i, j = 0, id, xlink, elList = [ ], - gradientDefs = { }, idsToXlinkMap = { }; - - elList.length = linearGradientEls.length + radialGradientEls.length; - i = linearGradientEls.length; - while (i--) { - elList[j++] = linearGradientEls[i]; - } - i = radialGradientEls.length; - while (i--) { - elList[j++] = radialGradientEls[i]; - } - - while (j--) { - el = elList[j]; - xlink = el.getAttribute('xlink:href'); - id = el.getAttribute('id'); - if (xlink) { - idsToXlinkMap[id] = xlink.substr(1); - } - gradientDefs[id] = el; - } - - for (id in idsToXlinkMap) { - var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); - el = gradientDefs[id]; - while (el2.firstChild) { - el.appendChild(el2.firstChild); - } - } - return gradientDefs; - }, - - /** - * Returns an object of attributes' name/value, given element and an array of attribute names; - * Parses parent "g" nodes recursively upwards. - * @static - * @memberOf fabric - * @param {DOMElement} element Element to parse - * @param {Array} attributes Array of attributes to parse - * @return {Object} object containing parsed attributes' names/values - */ - parseAttributes: function(element, attributes, svgUid) { - - if (!element) { - return; - } - - var value, - parentAttributes = { }, - fontSize; - - if (typeof svgUid === 'undefined') { - svgUid = element.getAttribute('svgUid'); - } - // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards - if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) { - parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); - } - fontSize = (parentAttributes && parentAttributes.fontSize ) || - element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE; - - var ownAttributes = attributes.reduce(function(memo, attr) { - value = element.getAttribute(attr); - if (value) { - attr = normalizeAttr(attr); - value = normalizeValue(attr, value, parentAttributes, fontSize); - - memo[attr] = value; - } - return memo; - }, { }); - - // add values parsed from style, which take precedence over attributes - // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) - ownAttributes = extend(ownAttributes, - extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); - if (ownAttributes.font) { - fabric.parseFontDeclaration(ownAttributes.font, ownAttributes); - } - return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); - }, - - /** - * Transforms an array of svg elements to corresponding fabric.* instances - * @static - * @memberOf fabric - * @param {Array} elements Array of elements to parse - * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) - * @param {Object} [options] Options object - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - parseElements: function(elements, callback, options, reviver) { - new fabric.ElementsParser(elements, callback, options, reviver).parse(); - }, - - /** - * Parses "style" attribute, retuning an object with values - * @static - * @memberOf fabric - * @param {SVGElement} element Element to parse - * @return {Object} Objects with values parsed from style attribute of an element - */ - parseStyleAttribute: function(element) { - var oStyle = { }, - style = element.getAttribute('style'); - - if (!style) { - return oStyle; - } - - if (typeof style === 'string') { - parseStyleString(style, oStyle); - } - else { - parseStyleObject(style, oStyle); - } - - return oStyle; - }, - - /** - * Parses "points" attribute, returning an array of values - * @static - * @memberOf fabric - * @param {String} points points attribute string - * @return {Array} array of points - */ - parsePointsAttribute: function(points) { - - // points attribute is required and must not be empty - if (!points) { - return null; - } - - // replace commas with whitespace and remove bookending whitespace - points = points.replace(/,/g, ' ').trim(); - - points = points.split(/\s+/); - var parsedPoints = [ ], i, len; - - i = 0; - len = points.length; - for (; i < len; i+=2) { - parsedPoints.push({ - x: parseFloat(points[i]), - y: parseFloat(points[i + 1]) - }); - } - - // odd number of points is an error - // if (parsedPoints.length % 2 !== 0) { - // return null; - // } - - return parsedPoints; - }, - - /** - * Returns CSS rules for a given SVG document - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} CSS rules of this document - */ - getCSSRules: function(doc) { - var styles = doc.getElementsByTagName('style'), - allRules = { }, rules; - - // very crude parsing of style contents - for (var i = 0, len = styles.length; i < len; i++) { - var styleContents = styles[i].textContent; - - // remove comments - styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); - if (styleContents.trim() === '') { - continue; - } - rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); - rules = rules.map(function(rule) { return rule.trim(); }); - - rules.forEach(function(rule) { - - var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), - ruleObj = { }, declaration = match[2].trim(), - propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); - - for (var i = 0, len = propertyValuePairs.length; i < len; i++) { - var pair = propertyValuePairs[i].split(/\s*:\s*/), - property = normalizeAttr(pair[0]), - value = normalizeValue(property, pair[1], pair[0]); - ruleObj[property] = value; - } - rule = match[1]; - rule.split(',').forEach(function(_rule) { - _rule = _rule.replace(/^svg/i, '').trim(); - if (_rule === '') { - return; + var sqrt = Math.sqrt, atan2 = Math.atan2, PiBy180 = Math.PI / 180; + fabric.util = { + removeFromArray: function(array, value) { + var idx = array.indexOf(value); + if (idx !== -1) { + array.splice(idx, 1); } - allRules[_rule] = fabric.util.object.clone(ruleObj); - }); - }); - } - return allRules; - }, + return array; + }, + getRandomInt: function(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + degreesToRadians: function(degrees) { + return degrees * PiBy180; + }, + radiansToDegrees: function(radians) { + return radians / PiBy180; + }, + rotatePoint: function(point, origin, radians) { + var sin = Math.sin(radians), cos = Math.cos(radians); + point.subtractEquals(origin); + var rx = point.x * cos - point.y * sin, ry = point.x * sin + point.y * cos; + return new fabric.Point(rx, ry).addEquals(origin); + }, + transformPoint: function(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point(t[0] * p.x + t[2] * p.y, t[1] * p.x + t[3] * p.y); + } + return new fabric.Point(t[0] * p.x + t[2] * p.y + t[4], t[1] * p.x + t[3] * p.y + t[5]); + }, + invertTransform: function(t) { + var r = t.slice(), a = 1 / (t[0] * t[3] - t[1] * t[2]); + r = [ a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0 ]; + var o = fabric.util.transformPoint({ + x: t[4], + y: t[5] + }, r); + r[4] = -o.x; + r[5] = -o.y; + return r; + }, + toFixed: function(number, fractionDigits) { + return parseFloat(Number(number).toFixed(fractionDigits)); + }, + parseUnit: function(value, fontSize) { + var unit = /\D{0,2}$/.exec(value), number = parseFloat(value); + if (!fontSize) { + fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + switch (unit[0]) { + case "mm": + return number * fabric.DPI / 25.4; - /** - * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) - * @memberof fabric - * @param {String} url - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromURL: function(url, callback, reviver) { + case "cm": + return number * fabric.DPI / 2.54; - url = url.replace(/^\n\s*/, '').trim(); - svgCache.has(url, function (hasUrl) { - if (hasUrl) { - svgCache.get(url, function (value) { - var enlivedRecord = _enlivenCachedObject(value); - callback(enlivedRecord.objects, enlivedRecord.options); - }); + case "in": + return number * fabric.DPI; + + case "pt": + return number * fabric.DPI / 72; + + case "pc": + return number * fabric.DPI / 72 * 12; + + case "em": + return number * fontSize; + + default: + return number; + } + }, + falseFunction: function() { + return false; + }, + getKlass: function(type, namespace) { + type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); + return fabric.util.resolveNamespace(namespace)[type]; + }, + resolveNamespace: function(namespace) { + if (!namespace) { + return fabric; + } + var parts = namespace.split("."), len = parts.length, obj = global || fabric.window; + for (var i = 0; i < len; ++i) { + obj = obj[parts[i]]; + } + return obj; + }, + loadImage: function(url, callback, context, crossOrigin) { + if (!url) { + callback && callback.call(context, url); + return; + } + var img = fabric.util.createImage(); + img.onload = function() { + callback && callback.call(context, img); + img = img.onload = img.onerror = null; + }; + img.onerror = function() { + fabric.log("Error loading " + img.src); + callback && callback.call(context, null, true); + img = img.onload = img.onerror = null; + }; + if (url.indexOf("data") !== 0 && typeof crossOrigin !== "undefined") { + img.crossOrigin = crossOrigin; + } + img.src = url; + }, + enlivenObjects: function(objects, callback, namespace, reviver) { + objects = objects || []; + function onLoaded() { + if (++numLoadedObjects === numTotalObjects) { + callback && callback(enlivenedObjects); + } + } + var enlivenedObjects = [], numLoadedObjects = 0, numTotalObjects = objects.length; + if (!numTotalObjects) { + callback && callback(enlivenedObjects); + return; + } + objects.forEach(function(o, index) { + if (!o || !o.type) { + onLoaded(); + return; + } + var klass = fabric.util.getKlass(o.type, namespace); + if (klass.async) { + klass.fromObject(o, function(obj, error) { + if (!error) { + enlivenedObjects[index] = obj; + reviver && reviver(o, enlivenedObjects[index]); + } + onLoaded(); + }); + } else { + enlivenedObjects[index] = klass.fromObject(o); + reviver && reviver(o, enlivenedObjects[index]); + onLoaded(); + } + }); + }, + groupSVGElements: function(elements, options, path) { + var object; + object = new fabric.PathGroup(elements, options); + if (typeof path !== "undefined") { + object.setSourcePath(path); + } + return object; + }, + populateWithProperties: function(source, destination, properties) { + if (properties && Object.prototype.toString.call(properties) === "[object Array]") { + for (var i = 0, len = properties.length; i < len; i++) { + if (properties[i] in source) { + destination[properties[i]] = source[properties[i]]; + } + } + } + }, + drawDashedLine: function(ctx, x, y, x2, y2, da) { + var dx = x2 - x, dy = y2 - y, len = sqrt(dx * dx + dy * dy), rot = atan2(dy, dx), dc = da.length, di = 0, draw = true; + ctx.save(); + ctx.translate(x, y); + ctx.moveTo(0, 0); + ctx.rotate(rot); + x = 0; + while (len > x) { + x += da[di++ % dc]; + if (x > len) { + x = len; + } + ctx[draw ? "lineTo" : "moveTo"](x, 0); + draw = !draw; + } + ctx.restore(); + }, + createCanvasElement: function(canvasEl) { + canvasEl || (canvasEl = fabric.document.createElement("canvas")); + if (!canvasEl.getContext && typeof G_vmlCanvasManager !== "undefined") { + G_vmlCanvasManager.initElement(canvasEl); + } + return canvasEl; + }, + createImage: function() { + return fabric.isLikelyNode ? new (require("canvas").Image)() : fabric.document.createElement("img"); + }, + createAccessors: function(klass) { + var proto = klass.prototype; + for (var i = proto.stateProperties.length; i--; ) { + var propName = proto.stateProperties[i], capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), setterName = "set" + capitalizedPropName, getterName = "get" + capitalizedPropName; + if (!proto[getterName]) { + proto[getterName] = function(property) { + return new Function('return this.get("' + property + '")'); + }(propName); + } + if (!proto[setterName]) { + proto[setterName] = function(property) { + return new Function("value", 'return this.set("' + property + '", value)'); + }(propName); + } + } + }, + clipContext: function(receiver, ctx) { + ctx.save(); + ctx.beginPath(); + receiver.clipTo(ctx); + ctx.clip(); + }, + multiplyTransformMatrices: function(a, b) { + return [ a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1], a[0] * b[2] + a[2] * b[3], a[1] * b[2] + a[3] * b[3], a[0] * b[4] + a[2] * b[5] + a[4], a[1] * b[4] + a[3] * b[5] + a[5] ]; + }, + getFunctionBody: function(fn) { + return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; + }, + isTransparent: function(ctx, x, y, tolerance) { + if (tolerance > 0) { + if (x > tolerance) { + x -= tolerance; + } else { + x = 0; + } + if (y > tolerance) { + y -= tolerance; + } else { + y = 0; + } + } + var _isTransparent = true, imageData = ctx.getImageData(x, y, tolerance * 2 || 1, tolerance * 2 || 1); + for (var i = 3, l = imageData.data.length; i < l; i += 4) { + var temp = imageData.data[i]; + _isTransparent = temp <= 0; + if (_isTransparent === false) { + break; + } + } + imageData = null; + return _isTransparent; } - else { - new fabric.util.request(url, { - method: 'get', - onComplete: onComplete - }); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function() { + var arcToSegmentsCache = {}, segmentToBezierCache = {}, boundsOfCurveCache = {}, _join = Array.prototype.join; + function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { + var argsString = _join.call(arguments); + if (arcToSegmentsCache[argsString]) { + return arcToSegmentsCache[argsString]; } - }); - - function onComplete(r) { - - var xml = r.responseXML; - if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { - xml = new ActiveXObject('Microsoft.XMLDOM'); - xml.async = 'false'; - //IE chokes on DOCTYPE - xml.loadXML(r.responseText.replace(//i, '')); + var PI = Math.PI, th = rotateX * PI / 180, sinTh = Math.sin(th), cosTh = Math.cos(th), fromX = 0, fromY = 0; + rx = Math.abs(rx); + ry = Math.abs(ry); + var px = -cosTh * toX * .5 - sinTh * toY * .5, py = -cosTh * toY * .5 + sinTh * toX * .5, rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, root = 0; + if (pl < 0) { + var s = Math.sqrt(1 - pl / (rx2 * ry2)); + rx *= s; + ry *= s; + } else { + root = (large === sweep ? -1 : 1) * Math.sqrt(pl / (rx2 * py2 + ry2 * px2)); } - if (!xml || !xml.documentElement) { - return; + var cx = root * rx * py / ry, cy = -root * ry * px / rx, cx1 = cosTh * cx - sinTh * cy + toX * .5, cy1 = sinTh * cx + cosTh * cy + toY * .5, mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); + if (sweep === 0 && dtheta > 0) { + dtheta -= 2 * PI; + } else if (sweep === 1 && dtheta < 0) { + dtheta += 2 * PI; } - - fabric.parseSVGDocument(xml.documentElement, function (results, options) { - svgCache.set(url, { - objects: fabric.util.array.invoke(results, 'toObject'), - options: options - }); - callback(results, options); - }, reviver); - } - }, - - /** - * Takes string corresponding to an SVG document, and parses it into a set of fabric objects - * @memberof fabric - * @param {String} string - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromString: function(string, callback, reviver) { - string = string.trim(); - var doc; - if (typeof DOMParser !== 'undefined') { - var parser = new DOMParser(); - if (parser && parser.parseFromString) { - doc = parser.parseFromString(string, 'text/xml'); + var segments = Math.ceil(Math.abs(dtheta / PI * 2)), result = [], mDelta = dtheta / segments, mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), th3 = mTheta + mDelta; + for (var i = 0; i < segments; i++) { + result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); + fromX = result[i][4]; + fromY = result[i][5]; + mTheta = th3; + th3 += mDelta; } - } - else if (fabric.window.ActiveXObject) { - doc = new ActiveXObject('Microsoft.XMLDOM'); - doc.async = 'false'; - // IE chokes on DOCTYPE - doc.loadXML(string.replace(//i, '')); - } - - fabric.parseSVGDocument(doc.documentElement, function (results, options) { - callback(results, options); - }, reviver); - }, - - /** - * Creates markup containing SVG font faces - * @param {Array} objects Array of fabric objects - * @return {String} - */ - createSVGFontFacesMarkup: function(objects) { - var markup = ''; - - for (var i = 0, len = objects.length; i < len; i++) { - if (objects[i].type !== 'text' || !objects[i].path) { - continue; - } - - markup += [ - //jscs:disable validateIndentation - '@font-face {', - 'font-family: ', objects[i].fontFamily, '; ', - 'src: url(\'', objects[i].path, '\')', - '}' - //jscs:enable validateIndentation - ].join(''); - } - - if (markup) { - markup = [ - //jscs:disable validateIndentation - '' - //jscs:enable validateIndentation - ].join(''); - } - - return markup; - }, - - /** - * Creates markup containing SVG referenced elements like patterns, gradients etc. - * @param {fabric.Canvas} canvas instance of fabric.Canvas - * @return {String} - */ - createSVGRefElementsMarkup: function(canvas) { - var markup = [ ]; - - _createSVGPattern(markup, canvas, 'backgroundColor'); - _createSVGPattern(markup, canvas, 'overlayColor'); - - return markup.join(''); + arcToSegmentsCache[argsString] = result; + return result; } - }); + function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { + var argsString2 = _join.call(arguments); + if (segmentToBezierCache[argsString2]) { + return segmentToBezierCache[argsString2]; + } + var costh2 = Math.cos(th2), sinth2 = Math.sin(th2), costh3 = Math.cos(th3), sinth3 = Math.sin(th3), toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, cp1X = fromX + mT * (-cosTh * rx * sinth2 - sinTh * ry * costh2), cp1Y = fromY + mT * (-sinTh * rx * sinth2 + cosTh * ry * costh2), cp2X = toX + mT * (cosTh * rx * sinth3 + sinTh * ry * costh3), cp2Y = toY + mT * (sinTh * rx * sinth3 - cosTh * ry * costh3); + segmentToBezierCache[argsString2] = [ cp1X, cp1Y, cp2X, cp2Y, toX, toY ]; + return segmentToBezierCache[argsString2]; + } + function calcVectorAngle(ux, uy, vx, vy) { + var ta = Math.atan2(uy, ux), tb = Math.atan2(vy, vx); + if (tb >= ta) { + return tb - ta; + } else { + return 2 * Math.PI - (ta - tb); + } + } + fabric.util.drawArc = function(ctx, fx, fy, coords) { + var rx = coords[0], ry = coords[1], rot = coords[2], large = coords[3], sweep = coords[4], tx = coords[5], ty = coords[6], segs = [ [], [], [], [] ], segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); + for (var i = 0, len = segsNorm.length; i < len; i++) { + segs[i][0] = segsNorm[i][0] + fx; + segs[i][1] = segsNorm[i][1] + fy; + segs[i][2] = segsNorm[i][2] + fx; + segs[i][3] = segsNorm[i][3] + fy; + segs[i][4] = segsNorm[i][4] + fx; + segs[i][5] = segsNorm[i][5] + fy; + ctx.bezierCurveTo.apply(ctx, segs[i]); + } + }; + fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { + var fromX = 0, fromY = 0, bound = [], bounds = [], segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot), boundCopy = [ [], [] ]; + for (var i = 0, len = segs.length; i < len; i++) { + bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); + boundCopy[0].x = bound[0].x + fx; + boundCopy[0].y = bound[0].y + fy; + boundCopy[1].x = bound[1].x + fx; + boundCopy[1].y = bound[1].y + fy; + bounds.push(boundCopy[0]); + bounds.push(boundCopy[1]); + fromX = segs[i][4]; + fromY = segs[i][5]; + } + return bounds; + }; + function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { + var argsString = _join.call(arguments); + if (boundsOfCurveCache[argsString]) { + return boundsOfCurveCache[argsString]; + } + var sqrt = Math.sqrt, min = Math.min, max = Math.max, abs = Math.abs, tvalues = [], bounds = [ [], [] ], a, b, c, t, t1, t2, b2ac, sqrtb2ac; + b = 6 * x0 - 12 * x1 + 6 * x2; + a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; + c = 3 * x1 - 3 * x0; + for (var i = 0; i < 2; ++i) { + if (i > 0) { + b = 6 * y0 - 12 * y1 + 6 * y2; + a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; + c = 3 * y1 - 3 * y0; + } + if (abs(a) < 1e-12) { + if (abs(b) < 1e-12) { + continue; + } + t = -c / b; + if (0 < t && t < 1) { + tvalues.push(t); + } + continue; + } + b2ac = b * b - 4 * c * a; + if (b2ac < 0) { + continue; + } + sqrtb2ac = sqrt(b2ac); + t1 = (-b + sqrtb2ac) / (2 * a); + if (0 < t1 && t1 < 1) { + tvalues.push(t1); + } + t2 = (-b - sqrtb2ac) / (2 * a); + if (0 < t2 && t2 < 1) { + tvalues.push(t2); + } + } + var x, y, j = tvalues.length, jlen = j, mt; + while (j--) { + t = tvalues[j]; + mt = 1 - t; + x = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3; + bounds[0][j] = x; + y = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3; + bounds[1][j] = y; + } + bounds[0][jlen] = x0; + bounds[1][jlen] = y0; + bounds[0][jlen + 1] = x3; + bounds[1][jlen + 1] = y3; + var result = [ { + x: min.apply(null, bounds[0]), + y: min.apply(null, bounds[1]) + }, { + x: max.apply(null, bounds[0]), + y: max.apply(null, bounds[1]) + } ]; + boundsOfCurveCache[argsString] = result; + return result; + } + fabric.util.getBoundsOfCurve = getBoundsOfCurve; +})(); -})(typeof exports !== 'undefined' ? exports : this); +(function() { + var slice = Array.prototype.slice; + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(searchElement) { + if (this === void 0 || this === null) { + throw new TypeError(); + } + var t = Object(this), len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n !== n) { + n = 0; + } else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (;k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + }; + } + if (!Array.prototype.forEach) { + Array.prototype.forEach = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + fn.call(context, this[i], i, this); + } + } + }; + } + if (!Array.prototype.map) { + Array.prototype.map = function(fn, context) { + var result = []; + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + result[i] = fn.call(context, this[i], i, this); + } + } + return result; + }; + } + if (!Array.prototype.every) { + Array.prototype.every = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this && !fn.call(context, this[i], i, this)) { + return false; + } + } + return true; + }; + } + if (!Array.prototype.some) { + Array.prototype.some = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this && fn.call(context, this[i], i, this)) { + return true; + } + } + return false; + }; + } + if (!Array.prototype.filter) { + Array.prototype.filter = function(fn, context) { + var result = [], val; + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + val = this[i]; + if (fn.call(context, val, i, this)) { + result.push(val); + } + } + } + return result; + }; + } + if (!Array.prototype.reduce) { + Array.prototype.reduce = function(fn) { + var len = this.length >>> 0, i = 0, rv; + if (arguments.length > 1) { + rv = arguments[1]; + } else { + do { + if (i in this) { + rv = this[i++]; + break; + } + if (++i >= len) { + throw new TypeError(); + } + } while (true); + } + for (;i < len; i++) { + if (i in this) { + rv = fn.call(null, rv, this[i], i, this); + } + } + return rv; + }; + } + function invoke(array, method) { + var args = slice.call(arguments, 2), result = []; + for (var i = 0, len = array.length; i < len; i++) { + result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); + } + return result; + } + function max(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 >= value2; + }); + } + function min(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 < value2; + }); + } + function find(array, byProperty, condition) { + if (!array || array.length === 0) { + return; + } + var i = array.length - 1, result = byProperty ? array[i][byProperty] : array[i]; + if (byProperty) { + while (i--) { + if (condition(array[i][byProperty], result)) { + result = array[i][byProperty]; + } + } + } else { + while (i--) { + if (condition(array[i], result)) { + result = array[i]; + } + } + } + return result; + } + fabric.util.array = { + invoke: invoke, + min: min, + max: max + }; +})(); +(function() { + function extend(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; + } + function clone(object) { + return extend({}, object); + } + fabric.util.object = { + extend: extend, + clone: clone + }; +})(); + +(function() { + if (!String.prototype.trim) { + String.prototype.trim = function() { + return this.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""); + }; + } + function camelize(string) { + return string.replace(/-+(.)?/g, function(match, character) { + return character ? character.toUpperCase() : ""; + }); + } + function capitalize(string, firstLetterOnly) { + return string.charAt(0).toUpperCase() + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); + } + function escapeXml(string) { + return string.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(//g, ">"); + } + fabric.util.string = { + camelize: camelize, + capitalize: capitalize, + escapeXml: escapeXml + }; +})(); + +(function() { + var slice = Array.prototype.slice, apply = Function.prototype.apply, Dummy = function() {}; + if (!Function.prototype.bind) { + Function.prototype.bind = function(thisArg) { + var _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 { + bound = function() { + return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); + }; + } + Dummy.prototype = this.prototype; + bound.prototype = new Dummy(); + return bound; + }; + } +})(); + +(function() { + var slice = Array.prototype.slice, emptyFunction = function() {}, IS_DONTENUM_BUGGY = function() { + for (var p in { + toString: 1 + }) { + if (p === "toString") { + return false; + } + } + return true; + }(), addMethods = function(klass, source, parent) { + for (var property in source) { + if (property in klass.prototype && typeof klass.prototype[property] === "function" && (source[property] + "").indexOf("callSuper") > -1) { + klass.prototype[property] = function(property) { + return function() { + var superclass = this.constructor.superclass; + this.constructor.superclass = parent; + var returnValue = source[property].apply(this, arguments); + this.constructor.superclass = superclass; + if (property !== "initialize") { + return returnValue; + } + }; + }(property); + } else { + klass.prototype[property] = source[property]; + } + if (IS_DONTENUM_BUGGY) { + if (source.toString !== Object.prototype.toString) { + klass.prototype.toString = source.toString; + } + if (source.valueOf !== Object.prototype.valueOf) { + klass.prototype.valueOf = source.valueOf; + } + } + } + }; + function Subclass() {} + function callSuper(methodName) { + var fn = this.constructor.superclass.prototype[methodName]; + return arguments.length > 1 ? fn.apply(this, slice.call(arguments, 1)) : fn.call(this); + } + function createClass() { + var parent = null, properties = slice.call(arguments, 0); + if (typeof properties[0] === "function") { + parent = properties.shift(); + } + function klass() { + this.initialize.apply(this, arguments); + } + klass.superclass = parent; + klass.subclasses = []; + if (parent) { + Subclass.prototype = parent.prototype; + klass.prototype = new Subclass(); + parent.subclasses.push(klass); + } + for (var i = 0, length = properties.length; i < length; i++) { + addMethods(klass, properties[i], parent); + } + if (!klass.prototype.initialize) { + klass.prototype.initialize = emptyFunction; + } + klass.prototype.constructor = klass; + klass.prototype.callSuper = callSuper; + return klass; + } + fabric.util.createClass = createClass; +})(); + +(function() { + var unknown = "unknown"; + function areHostMethods(object) { + var methodNames = Array.prototype.slice.call(arguments, 1), t, i, len = methodNames.length; + for (i = 0; i < len; i++) { + t = typeof object[methodNames[i]]; + if (!/^(?:function|object|unknown)$/.test(t)) { + return false; + } + } + return true; + } + var getElement, setElement, getUniqueId = function() { + var uid = 0; + return function(element) { + return element.__uniqueID || (element.__uniqueID = "uniqueID__" + uid++); + }; + }(); + (function() { + var elements = {}; + getElement = function(uid) { + return elements[uid]; + }; + setElement = function(uid, element) { + elements[uid] = element; + }; + })(); + function createListener(uid, handler) { + return { + handler: handler, + wrappedHandler: createWrappedHandler(uid, handler) + }; + } + function createWrappedHandler(uid, handler) { + return function(e) { + handler.call(getElement(uid), e || fabric.window.event); + }; + } + function createDispatcher(uid, eventName) { + return function(e) { + if (handlers[uid] && handlers[uid][eventName]) { + var handlersForEvent = handlers[uid][eventName]; + for (var i = 0, len = handlersForEvent.length; i < len; i++) { + handlersForEvent[i].call(this, e || fabric.window.event); + } + } + }; + } + var shouldUseAddListenerRemoveListener = areHostMethods(fabric.document.documentElement, "addEventListener", "removeEventListener") && areHostMethods(fabric.window, "addEventListener", "removeEventListener"), shouldUseAttachEventDetachEvent = areHostMethods(fabric.document.documentElement, "attachEvent", "detachEvent") && areHostMethods(fabric.window, "attachEvent", "detachEvent"), listeners = {}, handlers = {}, addListener, removeListener; + if (shouldUseAddListenerRemoveListener) { + addListener = function(element, eventName, handler) { + element.addEventListener(eventName, handler, false); + }; + removeListener = function(element, eventName, handler) { + element.removeEventListener(eventName, handler, false); + }; + } else if (shouldUseAttachEventDetachEvent) { + addListener = function(element, eventName, handler) { + var uid = getUniqueId(element); + setElement(uid, element); + if (!listeners[uid]) { + listeners[uid] = {}; + } + if (!listeners[uid][eventName]) { + listeners[uid][eventName] = []; + } + var listener = createListener(uid, handler); + listeners[uid][eventName].push(listener); + element.attachEvent("on" + eventName, listener.wrappedHandler); + }; + removeListener = function(element, eventName, handler) { + var uid = getUniqueId(element), listener; + if (listeners[uid] && listeners[uid][eventName]) { + for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { + listener = listeners[uid][eventName][i]; + if (listener && listener.handler === handler) { + element.detachEvent("on" + eventName, listener.wrappedHandler); + listeners[uid][eventName][i] = null; + } + } + } + }; + } else { + addListener = function(element, eventName, handler) { + var uid = getUniqueId(element); + if (!handlers[uid]) { + handlers[uid] = {}; + } + if (!handlers[uid][eventName]) { + handlers[uid][eventName] = []; + var existingHandler = element["on" + eventName]; + if (existingHandler) { + handlers[uid][eventName].push(existingHandler); + } + element["on" + eventName] = createDispatcher(uid, eventName); + } + handlers[uid][eventName].push(handler); + }; + removeListener = function(element, eventName, handler) { + var uid = getUniqueId(element); + if (handlers[uid] && handlers[uid][eventName]) { + var handlersForEvent = handlers[uid][eventName]; + for (var i = 0, len = handlersForEvent.length; i < len; i++) { + if (handlersForEvent[i] === handler) { + handlersForEvent.splice(i, 1); + } + } + } + }; + } + fabric.util.addListener = addListener; + fabric.util.removeListener = removeListener; + function getPointer(event, upperCanvasEl) { + event || (event = fabric.window.event); + var element = event.target || (typeof event.srcElement !== unknown ? event.srcElement : null), scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); + return { + x: pointerX(event) + scroll.left, + y: pointerY(event) + scroll.top + }; + } + var pointerX = function(event) { + return typeof event.clientX !== unknown ? event.clientX : 0; + }, pointerY = function(event) { + return typeof event.clientY !== unknown ? event.clientY : 0; + }; + function _getPointer(event, pageProp, clientProp) { + var touchProp = event.type === "touchend" ? "changedTouches" : "touches"; + return event[touchProp] && event[touchProp][0] ? event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]) || event[clientProp] : event[clientProp]; + } + if (fabric.isTouchSupported) { + pointerX = function(event) { + return _getPointer(event, "pageX", "clientX"); + }; + pointerY = function(event) { + return _getPointer(event, "pageY", "clientY"); + }; + } + fabric.util.getPointer = getPointer; + fabric.util.object.extend(fabric.util, fabric.Observable); +})(); + +(function() { + function setStyle(element, styles) { + var elementStyle = element.style; + if (!elementStyle) { + return element; + } + if (typeof styles === "string") { + element.style.cssText += ";" + styles; + return styles.indexOf("opacity") > -1 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) { + if (property === "opacity") { + setOpacity(element, styles[property]); + } else { + var normalizedProperty = property === "float" || property === "cssFloat" ? typeof elementStyle.styleFloat === "undefined" ? "cssFloat" : "styleFloat" : property; + elementStyle[normalizedProperty] = styles[property]; + } + } + return element; + } + var parseEl = fabric.document.createElement("div"), supportsOpacity = typeof parseEl.style.opacity === "string", supportsFilters = typeof parseEl.style.filter === "string", reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, setOpacity = function(element) { + return element; + }; + if (supportsOpacity) { + setOpacity = function(element, value) { + element.style.opacity = value; + return element; + }; + } else if (supportsFilters) { + setOpacity = function(element, value) { + var es = element.style; + if (element.currentStyle && !element.currentStyle.hasLayout) { + es.zoom = 1; + } + if (reOpacity.test(es.filter)) { + value = value >= .9999 ? "" : "alpha(opacity=" + value * 100 + ")"; + es.filter = es.filter.replace(reOpacity, value); + } else { + es.filter += " alpha(opacity=" + value * 100 + ")"; + } + return element; + }; + } + fabric.util.setStyle = setStyle; +})(); + +(function() { + var _slice = Array.prototype.slice; + function getById(id) { + return typeof id === "string" ? fabric.document.getElementById(id) : id; + } + var sliceCanConvertNodelists, toArray = function(arrayLike) { + return _slice.call(arrayLike, 0); + }; + try { + sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; + } catch (err) {} + if (!sliceCanConvertNodelists) { + toArray = function(arrayLike) { + var arr = new Array(arrayLike.length), i = arrayLike.length; + while (i--) { + arr[i] = arrayLike[i]; + } + return arr; + }; + } + function makeElement(tagName, attributes) { + var el = fabric.document.createElement(tagName); + for (var prop in attributes) { + if (prop === "class") { + el.className = attributes[prop]; + } else if (prop === "for") { + el.htmlFor = attributes[prop]; + } else { + el.setAttribute(prop, attributes[prop]); + } + } + return el; + } + function addClass(element, className) { + if (element && (" " + element.className + " ").indexOf(" " + className + " ") === -1) { + element.className += (element.className ? " " : "") + className; + } + } + function wrapElement(element, wrapper, attributes) { + if (typeof wrapper === "string") { + wrapper = makeElement(wrapper, attributes); + } + if (element.parentNode) { + element.parentNode.replaceChild(wrapper, element); + } + wrapper.appendChild(element); + return wrapper; + } + function getScrollLeftTop(element, upperCanvasEl) { + var firstFixedAncestor, origElement, left = 0, top = 0, docElement = fabric.document.documentElement, body = fabric.document.body || { + scrollLeft: 0, + scrollTop: 0 + }; + origElement = element; + while (element && element.parentNode && !firstFixedAncestor) { + element = element.parentNode; + if (element.nodeType === 1 && fabric.util.getElementStyle(element, "position") === "fixed") { + firstFixedAncestor = element; + } + if (element.nodeType === 1 && origElement !== upperCanvasEl && fabric.util.getElementStyle(element, "position") === "absolute") { + left = 0; + top = 0; + } else if (element === fabric.document) { + left = body.scrollLeft || docElement.scrollLeft || 0; + top = body.scrollTop || docElement.scrollTop || 0; + } else { + left += element.scrollLeft || 0; + top += element.scrollTop || 0; + } + } + return { + left: left, + top: top + }; + } + function getElementOffset(element) { + var docElem, doc = element && element.ownerDocument, box = { + left: 0, + top: 0 + }, offset = { + left: 0, + top: 0 + }, scrollLeftTop, offsetAttributes = { + borderLeftWidth: "left", + borderTopWidth: "top", + paddingLeft: "left", + paddingTop: "top" + }; + if (!doc) { + return { + left: 0, + top: 0 + }; + } + for (var attr in offsetAttributes) { + offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; + } + docElem = doc.documentElement; + if (typeof element.getBoundingClientRect !== "undefined") { + box = element.getBoundingClientRect(); + } + scrollLeftTop = fabric.util.getScrollLeftTop(element, null); + return { + left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, + top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top + }; + } + var getElementStyle; + if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + getElementStyle = function(element, attr) { + var style = fabric.document.defaultView.getComputedStyle(element, null); + return style ? style[attr] : undefined; + }; + } else { + getElementStyle = function(element, attr) { + var value = element.style[attr]; + if (!value && element.currentStyle) { + value = element.currentStyle[attr]; + } + return value; + }; + } + (function() { + var style = fabric.document.documentElement.style, selectProp = "userSelect" in style ? "userSelect" : "MozUserSelect" in style ? "MozUserSelect" : "WebkitUserSelect" in style ? "WebkitUserSelect" : "KhtmlUserSelect" in style ? "KhtmlUserSelect" : ""; + function makeElementUnselectable(element) { + if (typeof element.onselectstart !== "undefined") { + element.onselectstart = fabric.util.falseFunction; + } + if (selectProp) { + element.style[selectProp] = "none"; + } else if (typeof element.unselectable === "string") { + element.unselectable = "on"; + } + return element; + } + function makeElementSelectable(element) { + if (typeof element.onselectstart !== "undefined") { + element.onselectstart = null; + } + if (selectProp) { + element.style[selectProp] = ""; + } else if (typeof element.unselectable === "string") { + element.unselectable = ""; + } + return element; + } + fabric.util.makeElementUnselectable = makeElementUnselectable; + fabric.util.makeElementSelectable = makeElementSelectable; + })(); + (function() { + function getScript(url, callback) { + var headEl = fabric.document.getElementsByTagName("head")[0], scriptEl = fabric.document.createElement("script"), loading = true; + scriptEl.onload = scriptEl.onreadystatechange = function(e) { + if (loading) { + if (typeof this.readyState === "string" && this.readyState !== "loaded" && this.readyState !== "complete") { + return; + } + loading = false; + callback(e || fabric.window.event); + scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; + } + }; + scriptEl.src = url; + headEl.appendChild(scriptEl); + } + fabric.util.getScript = getScript; + })(); + fabric.util.getById = getById; + fabric.util.toArray = toArray; + fabric.util.makeElement = makeElement; + fabric.util.addClass = addClass; + fabric.util.wrapElement = wrapElement; + fabric.util.getScrollLeftTop = getScrollLeftTop; + fabric.util.getElementOffset = getElementOffset; + fabric.util.getElementStyle = getElementStyle; +})(); + +(function() { + function addParamToUrl(url, param) { + return url + (/\?/.test(url) ? "&" : "?") + param; + } + var makeXHR = function() { + var factories = [ function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }, function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }, function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }, function() { + return new XMLHttpRequest(); + } ]; + for (var i = factories.length; i--; ) { + try { + var req = factories[i](); + if (req) { + return factories[i]; + } + } catch (err) {} + } + }(); + function emptyFn() {} + function request(url, options) { + options || (options = {}); + var method = options.method ? options.method.toUpperCase() : "GET", onComplete = options.onComplete || function() {}, xhr = makeXHR(), body; + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + onComplete(xhr); + xhr.onreadystatechange = emptyFn; + } + }; + if (method === "GET") { + body = null; + if (typeof options.parameters === "string") { + url = addParamToUrl(url, options.parameters); + } + } + xhr.open(method, url, true); + if (method === "POST" || method === "PUT") { + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + } + xhr.send(body); + return xhr; + } + fabric.util.request = request; +})(); + +fabric.log = function() {}; + +fabric.warn = function() {}; + +if (typeof console !== "undefined") { + [ "log", "warn" ].forEach(function(methodName) { + if (typeof console[methodName] !== "undefined" && typeof console[methodName].apply === "function") { + fabric[methodName] = function() { + return console[methodName].apply(console, arguments); + }; + } + }); +} + +(function() { + 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, 1e3 / 60); + }; + 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); + } + function easeOutCubic(t, b, c, d) { + return c * ((t = t / d - 1) * t * t + 1) + b; + } + function easeInOutCubic(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t + 2) + b; + } + function easeInQuart(t, b, c, d) { + return c * (t /= d) * t * t * t + b; + } + function easeOutQuart(t, b, c, d) { + return -c * ((t = t / d - 1) * t * t * t - 1) + b; + } + function easeInOutQuart(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t + b; + } + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; + } + function easeInQuint(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + } + function easeOutQuint(t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + } + function easeInOutQuint(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + } + function easeInSine(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + function easeOutSine(t, b, c, d) { + return c * Math.sin(t / d * (Math.PI / 2)) + b; + } + function easeInOutSine(t, b, c, d) { + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; + } + function easeInExpo(t, b, c, d) { + return t === 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; + } + function easeOutExpo(t, b, c, d) { + return t === d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; + } + function easeInOutExpo(t, b, c, d) { + if (t === 0) { + return b; + } + if (t === d) { + return b + c; + } + t /= d / 2; + if (t < 1) { + return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; + } + function easeInCirc(t, b, c, d) { + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; + } + function easeOutCirc(t, b, c, d) { + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; + } + function easeInOutCirc(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + } + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; + } + function easeInElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * .3; + } + var opts = normalize(a, c, p, s); + return -elastic(opts, t, d) + b; + } + 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 * .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; + } + 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 * (.3 * 1.5); + } + var opts = normalize(a, c, p, s); + if (t < 1) { + return -.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) * .5 + opts.c + b; + } + function easeInBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * (t /= d) * t * ((s + 1) * t - s) + b; + } + function easeOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + } + function easeInOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + t /= d / 2; + if (t < 1) { + return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; + } + function easeInBounce(t, b, c, d) { + return c - easeOutBounce(d - t, 0, c, d) + b; + } + function easeOutBounce(t, b, c, d) { + if ((t /= d) < 1 / 2.75) { + return c * (7.5625 * t * t) + b; + } else if (t < 2 / 2.75) { + return c * (7.5625 * (t -= 1.5 / 2.75) * t + .75) + b; + } else if (t < 2.5 / 2.75) { + return c * (7.5625 * (t -= 2.25 / 2.75) * t + .9375) + b; + } else { + return c * (7.5625 * (t -= 2.625 / 2.75) * t + .984375) + b; + } + } + function easeInOutBounce(t, b, c, d) { + if (t < d / 2) { + return easeInBounce(t * 2, 0, c, d) * .5 + b; + } + return easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b; + } + fabric.util.ease = { + easeInQuad: function(t, b, c, d) { + return c * (t /= d) * t + b; + }, + easeOutQuad: function(t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + 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; + }, + 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"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, parseUnit = fabric.util.parseUnit, multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, attributesMap = { + cx: "left", + x: "left", + r: "radius", + cy: "top", + y: "top", + display: "visible", + visibility: "visible", + transform: "transformMatrix", + "fill-opacity": "fillOpacity", + "fill-rule": "fillRule", + "font-family": "fontFamily", + "font-size": "fontSize", + "font-style": "fontStyle", + "font-weight": "fontWeight", + "stroke-dasharray": "strokeDashArray", + "stroke-linecap": "strokeLineCap", + "stroke-linejoin": "strokeLineJoin", + "stroke-miterlimit": "strokeMiterLimit", + "stroke-opacity": "strokeOpacity", + "stroke-width": "strokeWidth", + "text-decoration": "textDecoration", + "text-anchor": "originX" + }, colorAttributes = { + stroke: "strokeOpacity", + fill: "fillOpacity" + }; + fabric.cssRules = {}; + fabric.gradientDefs = {}; + function normalizeAttr(attr) { + if (attr in attributesMap) { + return attributesMap[attr]; + } + return attr; + } + function normalizeValue(attr, value, parentAttributes, fontSize) { + var isArray = Object.prototype.toString.call(value) === "[object Array]", parsed; + if ((attr === "fill" || attr === "stroke") && value === "none") { + value = ""; + } else if (attr === "strokeDashArray") { + value = value.replace(/,/g, " ").split(/\s+/).map(function(n) { + return parseFloat(n); + }); + } else if (attr === "transformMatrix") { + if (parentAttributes && parentAttributes.transformMatrix) { + value = multiplyTransformMatrices(parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); + } else { + value = fabric.parseTransformAttribute(value); + } + } else if (attr === "visible") { + value = value === "none" || value === "hidden" ? false : true; + if (parentAttributes && parentAttributes.visible === false) { + value = false; + } + } else if (attr === "originX") { + value = value === "start" ? "left" : value === "end" ? "right" : "center"; + } else { + parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); + } + return !isArray && isNaN(parsed) ? value : parsed; + } + function _setStrokeFillOpacity(attributes) { + for (var attr in colorAttributes) { + if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === "undefined") { + continue; + } + if (attributes[attr].indexOf("url(") === 0) { + continue; + } + var color = new fabric.Color(attributes[attr]); + attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); + } + return attributes; + } + fabric.parseTransformAttribute = function() { + function rotateMatrix(matrix, args) { + var angle = args[0]; + matrix[0] = Math.cos(angle); + matrix[1] = Math.sin(angle); + matrix[2] = -Math.sin(angle); + matrix[3] = Math.cos(angle); + } + function scaleMatrix(matrix, args) { + var multiplierX = args[0], multiplierY = args.length === 2 ? args[1] : args[0]; + matrix[0] = multiplierX; + matrix[3] = multiplierY; + } + function skewXMatrix(matrix, args) { + matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0])); + } + function skewYMatrix(matrix, args) { + matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0])); + } + function translateMatrix(matrix, args) { + matrix[4] = args[0]; + if (args.length === 2) { + matrix[5] = args[1]; + } + } + var iMatrix = [ 1, 0, 0, 1, 0, 0 ], number = fabric.reNum, commaWsp = "(?:\\s+,?\\s*|,\\s*)", skewX = "(?:(skewX)\\s*\\(\\s*(" + number + ")\\s*\\))", skewY = "(?:(skewY)\\s*\\(\\s*(" + number + ")\\s*\\))", rotate = "(?:(rotate)\\s*\\(\\s*(" + number + ")(?:" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + "))?\\s*\\))", scale = "(?:(scale)\\s*\\(\\s*(" + number + ")(?:" + commaWsp + "(" + number + "))?\\s*\\))", translate = "(?:(translate)\\s*\\(\\s*(" + number + ")(?:" + commaWsp + "(" + number + "))?\\s*\\))", matrix = "(?:(matrix)\\s*\\(\\s*" + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + "\\s*\\))", transform = "(?:" + matrix + "|" + translate + "|" + scale + "|" + rotate + "|" + skewX + "|" + skewY + ")", transforms = "(?:" + transform + "(?:" + commaWsp + transform + ")*" + ")", transformList = "^\\s*(?:" + transforms + "?)\\s*$", reTransformList = new RegExp(transformList), reTransform = new RegExp(transform, "g"); + return function(attributeValue) { + var matrix = iMatrix.concat(), matrices = []; + if (!attributeValue || attributeValue && !reTransformList.test(attributeValue)) { + return matrix; + } + attributeValue.replace(reTransform, function(match) { + var m = new RegExp(transform).exec(match).filter(function(match) { + return match !== "" && match != null; + }), operation = m[1], args = m.slice(2).map(parseFloat); + switch (operation) { + case "translate": + translateMatrix(matrix, args); + break; + + case "rotate": + args[0] = fabric.util.degreesToRadians(args[0]); + rotateMatrix(matrix, args); + break; + + case "scale": + scaleMatrix(matrix, args); + break; + + case "skewX": + skewXMatrix(matrix, args); + break; + + case "skewY": + skewYMatrix(matrix, args); + break; + + case "matrix": + matrix = args; + break; + } + matrices.push(matrix.concat()); + matrix = iMatrix.concat(); + }); + var combinedMatrix = matrices[0]; + while (matrices.length > 1) { + matrices.shift(); + combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); + } + return combinedMatrix; + }; + }(); + function parseStyleString(style, oStyle) { + var attr, value; + style.replace(/;$/, "").split(";").forEach(function(chunk) { + var pair = chunk.split(":"); + attr = normalizeAttr(pair[0].trim().toLowerCase()); + value = normalizeValue(attr, pair[1].trim()); + oStyle[attr] = value; + }); + } + function parseStyleObject(style, oStyle) { + var attr, value; + for (var prop in style) { + if (typeof style[prop] === "undefined") { + continue; + } + attr = normalizeAttr(prop.toLowerCase()); + value = normalizeValue(attr, style[prop]); + oStyle[attr] = value; + } + } + function getGlobalStylesForElement(element, svgUid) { + var styles = {}; + for (var rule in fabric.cssRules[svgUid]) { + if (elementMatchesRule(element, rule.split(" "))) { + for (var property in fabric.cssRules[svgUid][rule]) { + styles[property] = fabric.cssRules[svgUid][rule][property]; + } + } + } + return styles; + } + function elementMatchesRule(element, selectors) { + var firstMatching, parentMatching = true; + firstMatching = selectorMatches(element, selectors.pop()); + if (firstMatching && selectors.length) { + parentMatching = doesSomeParentMatch(element, selectors); + } + return firstMatching && parentMatching && selectors.length === 0; + } + function doesSomeParentMatch(element, selectors) { + var selector, parentMatching = true; + while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { + if (parentMatching) { + selector = selectors.pop(); + } + element = element.parentNode; + parentMatching = selectorMatches(element, selector); + } + return selectors.length === 0; + } + function selectorMatches(element, selector) { + var nodeName = element.nodeName, classNames = element.getAttribute("class"), id = element.getAttribute("id"), matcher; + matcher = new RegExp("^" + nodeName, "i"); + selector = selector.replace(matcher, ""); + if (id && selector.length) { + matcher = new RegExp("#" + id + "(?![a-zA-Z\\-]+)", "i"); + selector = selector.replace(matcher, ""); + } + if (classNames && selector.length) { + classNames = classNames.split(" "); + for (var i = classNames.length; i--; ) { + matcher = new RegExp("\\." + classNames[i] + "(?![a-zA-Z\\-]+)", "i"); + selector = selector.replace(matcher, ""); + } + } + return selector.length === 0; + } + function parseUseDirectives(doc) { + var nodelist = doc.getElementsByTagName("use"); + while (nodelist.length) { + var el = nodelist[0], xlink = el.getAttribute("xlink:href").substr(1), x = el.getAttribute("x") || 0, y = el.getAttribute("y") || 0, el2 = doc.getElementById(xlink).cloneNode(true), currentTrans = (el2.getAttribute("transform") || "") + " translate(" + x + ", " + y + ")", parentNode; + for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { + var attr = attrs.item(j); + if (attr.nodeName === "x" || attr.nodeName === "y" || attr.nodeName === "xlink:href") { + continue; + } + if (attr.nodeName === "transform") { + currentTrans = attr.nodeValue + " " + currentTrans; + } else { + el2.setAttribute(attr.nodeName, attr.nodeValue); + } + } + el2.setAttribute("transform", currentTrans); + el2.setAttribute("instantiated_by_use", "1"); + el2.removeAttribute("id"); + parentNode = el.parentNode; + parentNode.replaceChild(el2, el); + } + } + var reViewBoxAttrValue = new RegExp("^" + "\\s*(" + fabric.reNum + "+)\\s*,?" + "\\s*(" + fabric.reNum + "+)\\s*,?" + "\\s*(" + fabric.reNum + "+)\\s*,?" + "\\s*(" + fabric.reNum + "+)\\s*" + "$"); + function addVBTransform(element, widthAttr, heightAttr) { + var viewBoxAttr = element.getAttribute("viewBox"), scaleX = 1, scaleY = 1, minX = 0, minY = 0, viewBoxWidth, viewBoxHeight, matrix, el; + if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { + minX = -parseFloat(viewBoxAttr[1]), minY = -parseFloat(viewBoxAttr[2]), viewBoxWidth = parseFloat(viewBoxAttr[3]), + viewBoxHeight = parseFloat(viewBoxAttr[4]); + } else { + return; + } + if (widthAttr && widthAttr !== viewBoxWidth) { + scaleX = widthAttr / viewBoxWidth; + } + if (heightAttr && heightAttr !== viewBoxHeight) { + scaleY = heightAttr / viewBoxHeight; + } + scaleY = scaleX = scaleX > scaleY ? scaleY : scaleX; + if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) { + return; + } + matrix = " matrix(" + scaleX + " 0" + " 0 " + scaleY + " " + minX * scaleX + " " + minY * scaleY + ") "; + if (element.tagName === "svg") { + el = element.ownerDocument.createElement("g"); + while (element.firstChild != null) { + el.appendChild(element.firstChild); + } + element.appendChild(el); + } else { + el = element; + matrix = el.getAttribute("transform") + matrix; + } + el.setAttribute("transform", matrix); + } + fabric.parseSVGDocument = function() { + var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/; + function hasAncestorWithNodeName(element, nodeName) { + while (element && (element = element.parentNode)) { + if (nodeName.test(element.nodeName) && !element.getAttribute("instantiated_by_use")) { + return true; + } + } + return false; + } + return function(doc, callback, reviver) { + if (!doc) { + return; + } + parseUseDirectives(doc); + var startTime = new Date(), svgUid = fabric.Object.__uid++, widthAttr, heightAttr, toBeParsed = false; + if (doc.getAttribute("width") && doc.getAttribute("width") !== "100%") { + widthAttr = parseUnit(doc.getAttribute("width")); + } + if (doc.getAttribute("height") && doc.getAttribute("height") !== "100%") { + heightAttr = parseUnit(doc.getAttribute("height")); + } + if (!widthAttr || !heightAttr) { + var viewBoxAttr = doc.getAttribute("viewBox"); + if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { + widthAttr = parseFloat(viewBoxAttr[3]), heightAttr = parseFloat(viewBoxAttr[4]); + } else { + toBeParsed = true; + } + } + addVBTransform(doc, widthAttr, heightAttr); + var descendants = fabric.util.toArray(doc.getElementsByTagName("*")); + if (descendants.length === 0 && fabric.isLikelyNode) { + descendants = doc.selectNodes('//*[name(.)!="svg"]'); + var arr = []; + for (var i = 0, len = descendants.length; i < len; i++) { + arr[i] = descendants[i]; + } + descendants = arr; + } + var elements = descendants.filter(function(el) { + reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0); + return reAllowedSVGTagNames.test(el.tagName) && !hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol)$/); + }); + if (!elements || elements && !elements.length) { + callback && callback([], {}); + return; + } + var options = { + width: widthAttr, + height: heightAttr, + svgUid: svgUid, + toBeParsed: toBeParsed + }; + fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); + fabric.cssRules[svgUid] = fabric.getCSSRules(doc); + fabric.parseElements(elements, function(instances) { + fabric.documentParsingTime = new Date() - startTime; + if (callback) { + callback(instances, options); + } + }, clone(options), reviver); + }; + }(); + var svgCache = { + has: function(name, callback) { + callback(false); + }, + get: function() {}, + set: function() {} + }; + function _enlivenCachedObject(cachedObject) { + var objects = cachedObject.objects, options = cachedObject.options; + objects = objects.map(function(o) { + return fabric[capitalize(o.type)].fromObject(o); + }); + return { + objects: objects, + options: options + }; + } + function _createSVGPattern(markup, canvas, property) { + if (canvas[property] && canvas[property].toSVG) { + markup.push('', ''); + } + } + var reFontDeclaration = new RegExp("(normal|italic)?\\s*(normal|small-caps)?\\s*" + "(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(" + fabric.reNum + "(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|" + fabric.reNum + "))?\\s+(.*)"); + extend(fabric, { + parseFontDeclaration: function(value, oStyle) { + var match = value.match(reFontDeclaration); + if (!match) { + return; + } + var fontStyle = match[1], fontWeight = match[3], fontSize = match[4], lineHeight = match[5], fontFamily = match[6]; + if (fontStyle) { + oStyle.fontStyle = fontStyle; + } + if (fontWeight) { + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + } + if (fontSize) { + oStyle.fontSize = parseUnit(fontSize); + } + if (fontFamily) { + oStyle.fontFamily = fontFamily; + } + if (lineHeight) { + oStyle.lineHeight = lineHeight === "normal" ? 1 : lineHeight; + } + }, + getGradientDefs: function(doc) { + var linearGradientEls = doc.getElementsByTagName("linearGradient"), radialGradientEls = doc.getElementsByTagName("radialGradient"), el, i, j = 0, id, xlink, elList = [], gradientDefs = {}, idsToXlinkMap = {}; + elList.length = linearGradientEls.length + radialGradientEls.length; + i = linearGradientEls.length; + while (i--) { + elList[j++] = linearGradientEls[i]; + } + i = radialGradientEls.length; + while (i--) { + elList[j++] = radialGradientEls[i]; + } + while (j--) { + el = elList[j]; + xlink = el.getAttribute("xlink:href"); + id = el.getAttribute("id"); + if (xlink) { + idsToXlinkMap[id] = xlink.substr(1); + } + gradientDefs[id] = el; + } + for (id in idsToXlinkMap) { + var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); + el = gradientDefs[id]; + while (el2.firstChild) { + el.appendChild(el2.firstChild); + } + } + return gradientDefs; + }, + parseAttributes: function(element, attributes, svgUid) { + if (!element) { + return; + } + var value, parentAttributes = {}, fontSize; + if (typeof svgUid === "undefined") { + svgUid = element.getAttribute("svgUid"); + } + if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) { + parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); + } + fontSize = parentAttributes && parentAttributes.fontSize || element.getAttribute("font-size") || fabric.Text.DEFAULT_SVG_FONT_SIZE; + var ownAttributes = attributes.reduce(function(memo, attr) { + value = element.getAttribute(attr); + if (value) { + attr = normalizeAttr(attr); + value = normalizeValue(attr, value, parentAttributes, fontSize); + memo[attr] = value; + } + return memo; + }, {}); + ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); + if (ownAttributes.font) { + fabric.parseFontDeclaration(ownAttributes.font, ownAttributes); + } + return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); + }, + parseElements: function(elements, callback, options, reviver) { + new fabric.ElementsParser(elements, callback, options, reviver).parse(); + }, + parseStyleAttribute: function(element) { + var oStyle = {}, style = element.getAttribute("style"); + if (!style) { + return oStyle; + } + if (typeof style === "string") { + parseStyleString(style, oStyle); + } else { + parseStyleObject(style, oStyle); + } + return oStyle; + }, + parsePointsAttribute: function(points) { + if (!points) { + return null; + } + points = points.replace(/,/g, " ").trim(); + points = points.split(/\s+/); + var parsedPoints = [], i, len; + i = 0; + len = points.length; + for (;i < len; i += 2) { + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); + } + return parsedPoints; + }, + getCSSRules: function(doc) { + var styles = doc.getElementsByTagName("style"), allRules = {}, rules; + for (var i = 0, len = styles.length; i < len; i++) { + var styleContents = styles[i].textContent; + styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ""); + if (styleContents.trim() === "") { + continue; + } + rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); + rules = rules.map(function(rule) { + return rule.trim(); + }); + rules.forEach(function(rule) { + var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), ruleObj = {}, declaration = match[2].trim(), propertyValuePairs = declaration.replace(/;$/, "").split(/\s*;\s*/); + for (var i = 0, len = propertyValuePairs.length; i < len; i++) { + var pair = propertyValuePairs[i].split(/\s*:\s*/), property = normalizeAttr(pair[0]), value = normalizeValue(property, pair[1], pair[0]); + ruleObj[property] = value; + } + rule = match[1]; + rule.split(",").forEach(function(_rule) { + _rule = _rule.replace(/^svg/i, "").trim(); + if (_rule === "") { + return; + } + allRules[_rule] = fabric.util.object.clone(ruleObj); + }); + }); + } + return allRules; + }, + loadSVGFromURL: function(url, callback, reviver) { + url = url.replace(/^\n\s*/, "").trim(); + svgCache.has(url, function(hasUrl) { + if (hasUrl) { + svgCache.get(url, function(value) { + var enlivedRecord = _enlivenCachedObject(value); + callback(enlivedRecord.objects, enlivedRecord.options); + }); + } else { + new fabric.util.request(url, { + method: "get", + onComplete: onComplete + }); + } + }); + function onComplete(r) { + var xml = r.responseXML; + if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { + xml = new ActiveXObject("Microsoft.XMLDOM"); + xml.async = "false"; + xml.loadXML(r.responseText.replace(//i, "")); + } + if (!xml || !xml.documentElement) { + return; + } + fabric.parseSVGDocument(xml.documentElement, function(results, options) { + svgCache.set(url, { + objects: fabric.util.array.invoke(results, "toObject"), + options: options + }); + callback(results, options); + }, reviver); + } + }, + loadSVGFromString: function(string, callback, reviver) { + string = string.trim(); + var doc; + if (typeof DOMParser !== "undefined") { + var parser = new DOMParser(); + if (parser && parser.parseFromString) { + doc = parser.parseFromString(string, "text/xml"); + } + } else if (fabric.window.ActiveXObject) { + doc = new ActiveXObject("Microsoft.XMLDOM"); + doc.async = "false"; + doc.loadXML(string.replace(//i, "")); + } + fabric.parseSVGDocument(doc.documentElement, function(results, options) { + callback(results, options); + }, reviver); + }, + createSVGFontFacesMarkup: function(objects) { + var markup = ""; + for (var i = 0, len = objects.length; i < len; i++) { + if (objects[i].type !== "text" || !objects[i].path) { + continue; + } + markup += [ "@font-face {", "font-family: ", objects[i].fontFamily, "; ", "src: url('", objects[i].path, "')", "}" ].join(""); + } + if (markup) { + markup = [ '" ].join(""); + } + return markup; + }, + createSVGRefElementsMarkup: function(canvas) { + var markup = []; + _createSVGPattern(markup, canvas, "backgroundColor"); + _createSVGPattern(markup, canvas, "overlayColor"); + return markup.join(""); + } + }); +})(typeof exports !== "undefined" ? exports : this); fabric.ElementsParser = function(elements, callback, options, reviver) { - this.elements = elements; - this.callback = callback; - this.options = options; - this.reviver = reviver; - this.svgUid = (options && options.svgUid) || 0; + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; + this.svgUid = options && options.svgUid || 0; }; fabric.ElementsParser.prototype.parse = function() { - this.instances = new Array(this.elements.length); - this.numElements = this.elements.length; - - this.createObjects(); + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; + this.createObjects(); }; fabric.ElementsParser.prototype.createObjects = function() { - for (var i = 0, len = this.elements.length; i < len; i++) { - this.elements[i].setAttribute('svgUid', this.svgUid); - (function(_this, i) { - setTimeout(function() { - _this.createObject(_this.elements[i], i); - }, 0); - })(this, i); - } + for (var i = 0, len = this.elements.length; i < len; i++) { + this.elements[i].setAttribute("svgUid", this.svgUid); + (function(_this, i) { + setTimeout(function() { + _this.createObject(_this.elements[i], i); + }, 0); + })(this, i); + } }; fabric.ElementsParser.prototype.createObject = function(el, index) { - var klass = fabric[fabric.util.string.capitalize(el.tagName)]; - if (klass && klass.fromElement) { - try { - this._createObject(klass, el, index); + var klass = fabric[fabric.util.string.capitalize(el.tagName)]; + if (klass && klass.fromElement) { + try { + this._createObject(klass, el, index); + } catch (err) { + fabric.log(err); + } + } else { + this.checkIfDone(); } - catch (err) { - fabric.log(err); - } - } - else { - this.checkIfDone(); - } }; fabric.ElementsParser.prototype._createObject = function(klass, el, index) { - if (klass.async) { - klass.fromElement(el, this.createCallback(index, el), this.options); - } - else { - var obj = klass.fromElement(el, this.options); - this.resolveGradient(obj, 'fill'); - this.resolveGradient(obj, 'stroke'); - this.reviver && this.reviver(el, obj); - this.instances[index] = obj; - this.checkIfDone(); - } + if (klass.async) { + klass.fromElement(el, this.createCallback(index, el), this.options); + } else { + var obj = klass.fromElement(el, this.options); + this.resolveGradient(obj, "fill"); + this.resolveGradient(obj, "stroke"); + this.reviver && this.reviver(el, obj); + this.instances[index] = obj; + this.checkIfDone(); + } }; fabric.ElementsParser.prototype.createCallback = function(index, el) { - var _this = this; - return function(obj) { - _this.resolveGradient(obj, 'fill'); - _this.resolveGradient(obj, 'stroke'); - _this.reviver && _this.reviver(el, obj); - _this.instances[index] = obj; - _this.checkIfDone(); - }; + var _this = this; + return function(obj) { + _this.resolveGradient(obj, "fill"); + _this.resolveGradient(obj, "stroke"); + _this.reviver && _this.reviver(el, obj); + _this.instances[index] = obj; + _this.checkIfDone(); + }; }; fabric.ElementsParser.prototype.resolveGradient = function(obj, property) { - - var instanceFillValue = obj.get(property); - if (!(/^url\(/).test(instanceFillValue)) { - return; - } - var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); - if (fabric.gradientDefs[this.svgUid][gradientId]) { - obj.set(property, - fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj)); - } + var instanceFillValue = obj.get(property); + if (!/^url\(/.test(instanceFillValue)) { + return; + } + var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); + if (fabric.gradientDefs[this.svgUid][gradientId]) { + obj.set(property, fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj)); + } }; fabric.ElementsParser.prototype.checkIfDone = function() { - if (--this.numElements === 0) { - this.instances = this.instances.filter(function(el) { - return el != null; - }); - this.callback(this.instances); - } + if (--this.numElements === 0) { + this.instances = this.instances.filter(function(el) { + return el != null; + }); + this.callback(this.instances); + } }; - (function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Point) { - fabric.warn('fabric.Point is already defined'); - return; - } - - fabric.Point = Point; - - /** - * Point class - * @class fabric.Point - * @memberOf fabric - * @constructor - * @param {Number} x - * @param {Number} y - * @return {fabric.Point} thisArg - */ - function Point(x, y) { - this.x = x; - this.y = y; - } - - Point.prototype = /** @lends fabric.Point.prototype */ { - - constructor: Point, - - /** - * Adds another point to this one and returns another one - * @param {fabric.Point} that - * @return {fabric.Point} new Point instance with added values - */ - add: function (that) { - return new Point(this.x + that.x, this.y + that.y); - }, - - /** - * Adds another point to this one - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - */ - addEquals: function (that) { - this.x += that.x; - this.y += that.y; - return this; - }, - - /** - * Adds value to this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} new Point with added value - */ - scalarAdd: function (scalar) { - return new Point(this.x + scalar, this.y + scalar); - }, - - /** - * Adds value to this point - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - scalarAddEquals: function (scalar) { - this.x += scalar; - this.y += scalar; - return this; - }, - - /** - * Subtracts another point from this point and returns a new one - * @param {fabric.Point} that - * @return {fabric.Point} new Point object with subtracted values - */ - subtract: function (that) { - return new Point(this.x - that.x, this.y - that.y); - }, - - /** - * Subtracts another point from this point - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - */ - subtractEquals: function (that) { - this.x -= that.x; - this.y -= that.y; - return this; - }, - - /** - * Subtracts value from this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - scalarSubtract: function (scalar) { - return new Point(this.x - scalar, this.y - scalar); - }, - - /** - * Subtracts value from this point - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - scalarSubtractEquals: function (scalar) { - this.x -= scalar; - this.y -= scalar; - return this; - }, - - /** - * Miltiplies this point by a value and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - multiply: function (scalar) { - return new Point(this.x * scalar, this.y * scalar); - }, - - /** - * Miltiplies this point by a value - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - multiplyEquals: function (scalar) { - this.x *= scalar; - this.y *= scalar; - return this; - }, - - /** - * Divides this point by a value and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - divide: function (scalar) { - return new Point(this.x / scalar, this.y / scalar); - }, - - /** - * Divides this point by a value - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - divideEquals: function (scalar) { - this.x /= scalar; - this.y /= scalar; - return this; - }, - - /** - * Returns true if this point is equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - eq: function (that) { - return (this.x === that.x && this.y === that.y); - }, - - /** - * Returns true if this point is less than another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lt: function (that) { - return (this.x < that.x && this.y < that.y); - }, - - /** - * Returns true if this point is less than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lte: function (that) { - return (this.x <= that.x && this.y <= that.y); - }, - - /** - - * Returns true if this point is greater another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gt: function (that) { - return (this.x > that.x && this.y > that.y); - }, - - /** - * Returns true if this point is greater than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gte: function (that) { - return (this.x >= that.x && this.y >= that.y); - }, - - /** - * Returns new point which is the result of linear interpolation with this one and another one - * @param {fabric.Point} that - * @param {Number} t - * @return {fabric.Point} - */ - lerp: function (that, t) { - return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); - }, - - /** - * Returns distance from this point and another one - * @param {fabric.Point} that - * @return {Number} - */ - distanceFrom: function (that) { - var dx = this.x - that.x, - dy = this.y - that.y; - return Math.sqrt(dx * dx + dy * dy); - }, - - /** - * Returns the point between this point and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - midPointFrom: function (that) { - return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2); - }, - - /** - * Returns a new point which is the min of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - min: function (that) { - return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); - }, - - /** - * Returns a new point which is the max of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - max: function (that) { - return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); - }, - - /** - * Returns string representation of this point - * @return {String} - */ - toString: function () { - return this.x + ',' + this.y; - }, - - /** - * Sets x/y of this point - * @param {Number} x - * @param {Number} y - */ - setXY: function (x, y) { - this.x = x; - this.y = y; - }, - - /** - * Sets x/y of this point from another point - * @param {fabric.Point} that - */ - setFromPoint: function (that) { - this.x = that.x; - this.y = that.y; - }, - - /** - * Swaps x/y of this point and another point - * @param {fabric.Point} that - */ - swap: function (that) { - var x = this.x, - y = this.y; - this.x = that.x; - this.y = that.y; - that.x = x; - that.y = y; - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Intersection) { - fabric.warn('fabric.Intersection is already defined'); - return; - } - - /** - * Intersection class - * @class fabric.Intersection - * @memberOf fabric - * @constructor - */ - function Intersection(status) { - this.status = status; - this.points = []; - } - - fabric.Intersection = Intersection; - - fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { - - /** - * Appends a point to intersection - * @param {fabric.Point} point - */ - appendPoint: function (point) { - this.points.push(point); - }, - - /** - * Appends points to intersection - * @param {Array} points - */ - appendPoints: function (points) { - this.points = this.points.concat(points); - } - }; - - /** - * Checks if one line intersects another - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {fabric.Point} b1 - * @param {fabric.Point} b2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { - var result, - uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), - ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), - uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); - if (uB !== 0) { - var ua = uaT / uB, - ub = ubT / uB; - if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { - result = new Intersection('Intersection'); - result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); - } - else { - result = new Intersection(); - } - } - else { - if (uaT === 0 || ubT === 0) { - result = new Intersection('Coincident'); - } - else { - result = new Intersection('Parallel'); - } - } - return result; - }; - - /** - * Checks if line intersects polygon - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {Array} points - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { - var result = new Intersection(), - length = points.length; - - for (var i = 0; i < length; i++) { - var b1 = points[i], - b2 = points[(i + 1) % length], - inter = Intersection.intersectLineLine(a1, a2, b1, b2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects another polygon - * @static - * @param {Array} points1 - * @param {Array} points2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { - var result = new Intersection(), - length = points1.length; - - for (var i = 0; i < length; i++) { - var a1 = points1[i], - a2 = points1[(i + 1) % length], - inter = Intersection.intersectLinePolygon(a1, a2, points2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects rectangle - * @static - * @param {Array} points - * @param {Number} r1 - * @param {Number} r2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { - var min = r1.min(r2), - max = r1.max(r2), - topRight = new fabric.Point(max.x, min.y), - bottomLeft = new fabric.Point(min.x, max.y), - inter1 = Intersection.intersectLinePolygon(min, topRight, points), - inter2 = Intersection.intersectLinePolygon(topRight, max, points), - inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), - inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), - result = new Intersection(); - - result.appendPoints(inter1.points); - result.appendPoints(inter2.points); - result.appendPoints(inter3.points); - result.appendPoints(inter4.points); - - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Color) { - fabric.warn('fabric.Color is already defined.'); - return; - } - - /** - * Color class - * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; - * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. - * - * @class fabric.Color - * @param {String} color optional in hex or rgb(a) format - * @return {fabric.Color} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} - */ - function Color(color) { - if (!color) { - this.setSource([0, 0, 0, 1]); - } - else { - this._tryParsingColor(color); - } - } - - fabric.Color = Color; - - fabric.Color.prototype = /** @lends fabric.Color.prototype */ { - - /** - * @private - * @param {String|Array} color Color value to parse - */ - _tryParsingColor: function(color) { - var source; - - if (color in Color.colorNameMap) { - color = Color.colorNameMap[color]; - } - - if (color === 'transparent') { - this.setSource([255, 255, 255, 0]); + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Point) { + fabric.warn("fabric.Point is already defined"); return; - } - - source = Color.sourceFromHex(color); - - if (!source) { - source = Color.sourceFromRgb(color); - } - if (!source) { - source = Color.sourceFromHsl(color); - } - if (source) { - this.setSource(source); - } - }, - - /** - * Adapted from https://github.com/mjijackson - * @private - * @param {Number} r Red color value - * @param {Number} g Green color value - * @param {Number} b Blue color value - * @return {Array} Hsl color - */ - _rgbToHsl: function(r, g, b) { - r /= 255, g /= 255, b /= 255; - - var h, s, l, - max = fabric.util.array.max([r, g, b]), - min = fabric.util.array.min([r, g, b]); - - l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } - else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; + } + fabric.Point = Point; + function Point(x, y) { + this.x = x; + this.y = y; + } + Point.prototype = { + constructor: Point, + add: function(that) { + return new Point(this.x + that.x, this.y + that.y); + }, + addEquals: function(that) { + this.x += that.x; + this.y += that.y; + return this; + }, + scalarAdd: function(scalar) { + return new Point(this.x + scalar, this.y + scalar); + }, + scalarAddEquals: function(scalar) { + this.x += scalar; + this.y += scalar; + return this; + }, + subtract: function(that) { + return new Point(this.x - that.x, this.y - that.y); + }, + subtractEquals: function(that) { + this.x -= that.x; + this.y -= that.y; + return this; + }, + scalarSubtract: function(scalar) { + return new Point(this.x - scalar, this.y - scalar); + }, + scalarSubtractEquals: function(scalar) { + this.x -= scalar; + this.y -= scalar; + return this; + }, + multiply: function(scalar) { + return new Point(this.x * scalar, this.y * scalar); + }, + multiplyEquals: function(scalar) { + this.x *= scalar; + this.y *= scalar; + return this; + }, + divide: function(scalar) { + return new Point(this.x / scalar, this.y / scalar); + }, + divideEquals: function(scalar) { + this.x /= scalar; + this.y /= scalar; + return this; + }, + eq: function(that) { + return this.x === that.x && this.y === that.y; + }, + lt: function(that) { + return this.x < that.x && this.y < that.y; + }, + lte: function(that) { + return this.x <= that.x && this.y <= that.y; + }, + gt: function(that) { + return this.x > that.x && this.y > that.y; + }, + gte: function(that) { + return this.x >= that.x && this.y >= that.y; + }, + lerp: function(that, t) { + return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); + }, + distanceFrom: function(that) { + var dx = this.x - that.x, dy = this.y - that.y; + return Math.sqrt(dx * dx + dy * dy); + }, + midPointFrom: function(that) { + return new Point(this.x + (that.x - this.x) / 2, this.y + (that.y - this.y) / 2); + }, + min: function(that) { + return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); + }, + max: function(that) { + return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); + }, + toString: function() { + return this.x + "," + this.y; + }, + setXY: function(x, y) { + this.x = x; + this.y = y; + }, + setFromPoint: function(that) { + this.x = that.x; + this.y = that.y; + }, + swap: function(that) { + var x = this.x, y = this.y; + this.x = that.x; + this.y = that.y; + that.x = x; + that.y = y; } - h /= 6; - } + }; +})(typeof exports !== "undefined" ? exports : this); - return [ - Math.round(h * 360), - Math.round(s * 100), - Math.round(l * 100) - ]; - }, - - /** - * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {Array} - */ - getSource: function() { - return this._source; - }, - - /** - * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {Array} source - */ - setSource: function(source) { - this._source = source; - }, - - /** - * Returns color represenation in RGB format - * @return {String} ex: rgb(0-255,0-255,0-255) - */ - toRgb: function() { - var source = this.getSource(); - return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; - }, - - /** - * Returns color represenation in RGBA format - * @return {String} ex: rgba(0-255,0-255,0-255,0-1) - */ - toRgba: function() { - var source = this.getSource(); - return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; - }, - - /** - * Returns color represenation in HSL format - * @return {String} ex: hsl(0-360,0%-100%,0%-100%) - */ - toHsl: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; - }, - - /** - * Returns color represenation in HSLA format - * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) - */ - toHsla: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; - }, - - /** - * Returns color represenation in HEX format - * @return {String} ex: FF5555 - */ - toHex: function() { - var source = this.getSource(), r, g, b; - - r = source[0].toString(16); - r = (r.length === 1) ? ('0' + r) : r; - - g = source[1].toString(16); - g = (g.length === 1) ? ('0' + g) : g; - - b = source[2].toString(16); - b = (b.length === 1) ? ('0' + b) : b; - - return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); - }, - - /** - * Gets value of alpha channel for this color - * @return {Number} 0-1 - */ - getAlpha: function() { - return this.getSource()[3]; - }, - - /** - * Sets value of alpha channel for this color - * @param {Number} alpha Alpha value 0-1 - * @return {fabric.Color} thisArg - */ - setAlpha: function(alpha) { - var source = this.getSource(); - source[3] = alpha; - this.setSource(source); - return this; - }, - - /** - * Transforms color to its grayscale representation - * @return {fabric.Color} thisArg - */ - toGrayscale: function() { - var source = this.getSource(), - average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), - currentAlpha = source[3]; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Transforms color to its black and white representation - * @param {Number} threshold - * @return {fabric.Color} thisArg - */ - toBlackWhite: function(threshold) { - var source = this.getSource(), - average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), - currentAlpha = source[3]; - - threshold = threshold || 127; - - average = (Number(average) < Number(threshold)) ? 0 : 255; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Overlays color with another color - * @param {String|fabric.Color} otherColor - * @return {fabric.Color} thisArg - */ - overlayWith: function(otherColor) { - if (!(otherColor instanceof Color)) { - otherColor = new Color(otherColor); - } - - var result = [], - alpha = this.getAlpha(), - otherAlpha = 0.5, - source = this.getSource(), - otherSource = otherColor.getSource(); - - for (var i = 0; i < 3; i++) { - result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); - } - - result[3] = alpha; - this.setSource(result); - return this; +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Intersection) { + fabric.warn("fabric.Intersection is already defined"); + return; } - }; - - /** - * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HEX format (ex: #FF5555, 010155, aff) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; - - /** - * Map of the 17 basic color names with HEX code - * @static - * @field - * @memberOf fabric.Color - * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units - */ - fabric.Color.colorNameMap = { - aqua: '#00FFFF', - black: '#000000', - blue: '#0000FF', - fuchsia: '#FF00FF', - gray: '#808080', - green: '#008000', - lime: '#00FF00', - maroon: '#800000', - navy: '#000080', - olive: '#808000', - orange: '#FFA500', - purple: '#800080', - red: '#FF0000', - silver: '#C0C0C0', - teal: '#008080', - white: '#FFFFFF', - yellow: '#FFFF00' - }; - - /** - * @private - * @param {Number} p - * @param {Number} q - * @param {Number} t - * @return {Number} - */ - function hue2rgb(p, q, t) { - if (t < 0) { - t += 1; + function Intersection(status) { + this.status = status; + this.points = []; } - if (t > 1) { - t -= 1; + fabric.Intersection = Intersection; + fabric.Intersection.prototype = { + appendPoint: function(point) { + this.points.push(point); + }, + appendPoints: function(points) { + this.points = this.points.concat(points); + } + }; + fabric.Intersection.intersectLineLine = function(a1, a2, b1, b2) { + var result, uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, ub = ubT / uB; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection("Intersection"); + result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } else { + result = new Intersection(); + } + } else { + if (uaT === 0 || ubT === 0) { + result = new Intersection("Coincident"); + } else { + result = new Intersection("Parallel"); + } + } + return result; + }; + fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { + var result = new Intersection(), length = points.length; + for (var i = 0; i < length; i++) { + var b1 = points[i], b2 = points[(i + 1) % length], inter = Intersection.intersectLineLine(a1, a2, b1, b2); + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + fabric.Intersection.intersectPolygonPolygon = function(points1, points2) { + var result = new Intersection(), length = points1.length; + for (var i = 0; i < length; i++) { + var a1 = points1[i], a2 = points1[(i + 1) % length], inter = Intersection.intersectLinePolygon(a1, a2, points2); + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + fabric.Intersection.intersectPolygonRectangle = function(points, r1, r2) { + var min = r1.min(r2), max = r1.max(r2), topRight = new fabric.Point(max.x, min.y), bottomLeft = new fabric.Point(min.x, max.y), inter1 = Intersection.intersectLinePolygon(min, topRight, points), inter2 = Intersection.intersectLinePolygon(topRight, max, points), inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), result = new Intersection(); + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Color) { + fabric.warn("fabric.Color is already defined."); + return; } - if (t < 1/6) { - return p + (q - p) * 6 * t; + function Color(color) { + if (!color) { + this.setSource([ 0, 0, 0, 1 ]); + } else { + this._tryParsingColor(color); + } } - if (t < 1/2) { - return q; + fabric.Color = Color; + fabric.Color.prototype = { + _tryParsingColor: function(color) { + var source; + if (color in Color.colorNameMap) { + color = Color.colorNameMap[color]; + } + if (color === "transparent") { + this.setSource([ 255, 255, 255, 0 ]); + return; + } + source = Color.sourceFromHex(color); + if (!source) { + source = Color.sourceFromRgb(color); + } + if (!source) { + source = Color.sourceFromHsl(color); + } + if (source) { + this.setSource(source); + } + }, + _rgbToHsl: function(r, g, b) { + r /= 255, g /= 255, b /= 255; + var h, s, l, max = fabric.util.array.max([ r, g, b ]), min = fabric.util.array.min([ r, g, b ]); + l = (max + min) / 2; + if (max === min) { + h = s = 0; + } else { + var d = max - min; + s = l > .5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + + case g: + h = (b - r) / d + 2; + break; + + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + return [ Math.round(h * 360), Math.round(s * 100), Math.round(l * 100) ]; + }, + getSource: function() { + return this._source; + }, + setSource: function(source) { + this._source = source; + }, + toRgb: function() { + var source = this.getSource(); + return "rgb(" + source[0] + "," + source[1] + "," + source[2] + ")"; + }, + toRgba: function() { + var source = this.getSource(); + return "rgba(" + source[0] + "," + source[1] + "," + source[2] + "," + source[3] + ")"; + }, + toHsl: function() { + var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); + return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)"; + }, + toHsla: function() { + var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); + return "hsla(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%," + source[3] + ")"; + }, + toHex: function() { + var source = this.getSource(), r, g, b; + r = source[0].toString(16); + r = r.length === 1 ? "0" + r : r; + g = source[1].toString(16); + g = g.length === 1 ? "0" + g : g; + b = source[2].toString(16); + b = b.length === 1 ? "0" + b : b; + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + }, + getAlpha: function() { + return this.getSource()[3]; + }, + setAlpha: function(alpha) { + var source = this.getSource(); + source[3] = alpha; + this.setSource(source); + return this; + }, + toGrayscale: function() { + var source = this.getSource(), average = parseInt((source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), 10), currentAlpha = source[3]; + this.setSource([ average, average, average, currentAlpha ]); + return this; + }, + toBlackWhite: function(threshold) { + var source = this.getSource(), average = (source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), currentAlpha = source[3]; + threshold = threshold || 127; + average = Number(average) < Number(threshold) ? 0 : 255; + this.setSource([ average, average, average, currentAlpha ]); + return this; + }, + overlayWith: function(otherColor) { + if (!(otherColor instanceof Color)) { + otherColor = new Color(otherColor); + } + var result = [], alpha = this.getAlpha(), otherAlpha = .5, source = this.getSource(), otherSource = otherColor.getSource(); + for (var i = 0; i < 3; i++) { + result.push(Math.round(source[i] * (1 - otherAlpha) + otherSource[i] * otherAlpha)); + } + result[3] = alpha; + this.setSource(result); + return this; + } + }; + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; + fabric.Color.colorNameMap = { + aqua: "#00FFFF", + black: "#000000", + blue: "#0000FF", + fuchsia: "#FF00FF", + gray: "#808080", + green: "#008000", + lime: "#00FF00", + maroon: "#800000", + navy: "#000080", + olive: "#808000", + orange: "#FFA500", + purple: "#800080", + red: "#FF0000", + silver: "#C0C0C0", + teal: "#008080", + white: "#FFFFFF", + yellow: "#FFFF00" + }; + function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; } - if (t < 2/3) { - return p + (q - p) * (2/3 - t) * 6; - } - return p; - } - - /** - * Returns new color object, when given a color in RGB format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255) - * @return {fabric.Color} - */ - fabric.Color.fromRgb = function(color) { - return Color.fromSource(Color.sourceFromRgb(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) - * @return {Array} source - */ - fabric.Color.sourceFromRgb = function(color) { - var match = color.match(Color.reRGBa); - if (match) { - var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), - g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), - b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); - - return [ - parseInt(r, 10), - parseInt(g, 10), - parseInt(b, 10), - match[4] ? parseFloat(match[4]) : 1 - ]; - } - }; - - /** - * Returns new color object, when given a color in RGBA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromRgba = Color.fromRgb; - - /** - * Returns new color object, when given a color in HSL format - * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) - * @memberOf fabric.Color - * @return {fabric.Color} - */ - fabric.Color.fromHsl = function(color) { - return Color.fromSource(Color.sourceFromHsl(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. - * Adapted from https://github.com/mjijackson - * @memberOf fabric.Color - * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) - * @return {Array} source - * @see http://http://www.w3.org/TR/css3-color/#hsl-color - */ - fabric.Color.sourceFromHsl = function(color) { - var match = color.match(Color.reHSLa); - if (!match) { - return; - } - - var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, - s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), - l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), - r, g, b; - - if (s === 0) { - r = g = b = l; - } - else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, - p = l * 2 - q; - - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [ - Math.round(r * 255), - Math.round(g * 255), - Math.round(b * 255), - match[4] ? parseFloat(match[4]) : 1 - ]; - }; - - /** - * Returns new color object, when given a color in HSLA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromHsla = Color.fromHsl; - - /** - * Returns new color object, when given a color in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color Color value ex: FF5555 - * @return {fabric.Color} - */ - fabric.Color.fromHex = function(color) { - return Color.fromSource(Color.sourceFromHex(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color ex: FF5555 - * @return {Array} source - */ - fabric.Color.sourceFromHex = function(color) { - if (color.match(Color.reHex)) { - var value = color.slice(color.indexOf('#') + 1), - isShortNotation = (value.length === 3), - r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), - g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), - b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6); - - return [ - parseInt(r, 16), - parseInt(g, 16), - parseInt(b, 16), - 1 - ]; - } - }; - - /** - * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) - * @static - * @memberOf fabric.Color - * @param {Array} source - * @return {fabric.Color} - */ - fabric.Color.fromSource = function(source) { - var oColor = new Color(); - oColor.setSource(source); - return oColor; - }; - -})(typeof exports !== 'undefined' ? exports : this); - + fabric.Color.fromRgb = function(color) { + return Color.fromSource(Color.sourceFromRgb(color)); + }; + fabric.Color.sourceFromRgb = function(color) { + var match = color.match(Color.reRGBa); + if (match) { + var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); + return [ parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), match[4] ? parseFloat(match[4]) : 1 ]; + } + }; + fabric.Color.fromRgba = Color.fromRgb; + fabric.Color.fromHsl = function(color) { + return Color.fromSource(Color.sourceFromHsl(color)); + }; + fabric.Color.sourceFromHsl = function(color) { + var match = color.match(Color.reHSLa); + if (!match) { + return; + } + var h = (parseFloat(match[1]) % 360 + 360) % 360 / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), r, g, b; + if (s === 0) { + r = g = b = l; + } else { + var q = l <= .5 ? l * (s + 1) : l + s - l * s, p = l * 2 - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + return [ Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), match[4] ? parseFloat(match[4]) : 1 ]; + }; + fabric.Color.fromHsla = Color.fromHsl; + fabric.Color.fromHex = function(color) { + return Color.fromSource(Color.sourceFromHex(color)); + }; + fabric.Color.sourceFromHex = function(color) { + if (color.match(Color.reHex)) { + var value = color.slice(color.indexOf("#") + 1), isShortNotation = value.length === 3, r = isShortNotation ? value.charAt(0) + value.charAt(0) : value.substring(0, 2), g = isShortNotation ? value.charAt(1) + value.charAt(1) : value.substring(2, 4), b = isShortNotation ? value.charAt(2) + value.charAt(2) : value.substring(4, 6); + return [ parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1 ]; + } + }; + fabric.Color.fromSource = function(source) { + var oColor = new Color(); + oColor.setSource(source); + return oColor; + }; +})(typeof exports !== "undefined" ? exports : this); (function() { - - /* _FROM_SVG_START_ */ - function getColorStop(el) { - var style = el.getAttribute('style'), - offset = el.getAttribute('offset'), - color, colorAlpha, opacity; - - // convert percents to absolute values - offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); - offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; - if (style) { - var keyValuePairs = style.split(/\s*;\s*/); - - if (keyValuePairs[keyValuePairs.length - 1] === '') { - keyValuePairs.pop(); - } - - for (var i = keyValuePairs.length; i--; ) { - - var split = keyValuePairs[i].split(/\s*:\s*/), - key = split[0].trim(), - value = split[1].trim(); - - if (key === 'stop-color') { - color = value; + function getColorStop(el) { + var style = el.getAttribute("style"), offset = el.getAttribute("offset"), color, colorAlpha, opacity; + offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); + offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; + if (style) { + var keyValuePairs = style.split(/\s*;\s*/); + if (keyValuePairs[keyValuePairs.length - 1] === "") { + keyValuePairs.pop(); + } + for (var i = keyValuePairs.length; i--; ) { + var split = keyValuePairs[i].split(/\s*:\s*/), key = split[0].trim(), value = split[1].trim(); + if (key === "stop-color") { + color = value; + } else if (key === "stop-opacity") { + opacity = value; + } + } } - else if (key === 'stop-opacity') { - opacity = value; + if (!color) { + color = el.getAttribute("stop-color") || "rgb(0,0,0)"; } - } - } - - if (!color) { - color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; - } - if (!opacity) { - opacity = el.getAttribute('stop-opacity'); - } - - color = new fabric.Color(color); - colorAlpha = color.getAlpha(); - opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); - opacity *= colorAlpha; - - return { - offset: offset, - color: color.toRgb(), - opacity: opacity - }; - } - - function getLinearCoords(el) { - return { - x1: el.getAttribute('x1') || 0, - y1: el.getAttribute('y1') || 0, - x2: el.getAttribute('x2') || '100%', - y2: el.getAttribute('y2') || 0 - }; - } - - function getRadialCoords(el) { - return { - x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', - y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', - r1: 0, - x2: el.getAttribute('cx') || '50%', - y2: el.getAttribute('cy') || '50%', - r2: el.getAttribute('r') || '50%' - }; - } - /* _FROM_SVG_END_ */ - - /** - * Gradient class - * @class fabric.Gradient - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients} - * @see {@link fabric.Gradient#initialize} for constructor definition - */ - fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { - - /** - * Horizontal offset for aligning gradients coming from SVG when outside pathgroups - * @type Number - * @default 0 - */ - offsetX: 0, - - /** - * Vertical offset for aligning gradients coming from SVG when outside pathgroups - * @type Number - * @default 0 - */ - offsetY: 0, - - /** - * Constructor - * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops - * @return {fabric.Gradient} thisArg - */ - initialize: function(options) { - options || (options = { }); - - var coords = { }; - - this.id = fabric.Object.__uid++; - this.type = options.type || 'linear'; - - coords = { - x1: options.coords.x1 || 0, - y1: options.coords.y1 || 0, - x2: options.coords.x2 || 0, - y2: options.coords.y2 || 0 - }; - - if (this.type === 'radial') { - coords.r1 = options.coords.r1 || 0; - coords.r2 = options.coords.r2 || 0; - } - this.coords = coords; - this.colorStops = options.colorStops.slice(); - if (options.gradientTransform) { - this.gradientTransform = options.gradientTransform; - } - this.offsetX = options.offsetX || this.offsetX; - this.offsetY = options.offsetY || this.offsetY; - }, - - /** - * Adds another colorStop - * @param {Object} colorStop Object with offset and color - * @return {fabric.Gradient} thisArg - */ - addColorStop: function(colorStop) { - for (var position in colorStop) { - var color = new fabric.Color(colorStop[position]); - this.colorStops.push({ - offset: position, - color: color.toRgb(), - opacity: color.getAlpha() - }); - } - return this; - }, - - /** - * Returns object representation of a gradient - * @return {Object} - */ - toObject: function() { - return { - type: this.type, - coords: this.coords, - colorStops: this.colorStops, - offsetX: this.offsetX, - offsetY: this.offsetY - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an gradient - * @param {Object} object Object to create a gradient for - * @param {Boolean} normalize Whether coords should be normalized - * @return {String} SVG representation of an gradient (linear/radial) - */ - toSVG: function(object) { - var coords = fabric.util.object.clone(this.coords), - markup, commonAttributes; - - // colorStops must be sorted ascending - this.colorStops.sort(function(a, b) { - return a.offset - b.offset; - }); - - if (!(object.group && object.group.type === 'path-group')) { - for (var prop in coords) { - if (prop === 'x1' || prop === 'x2' || prop === 'r2') { - coords[prop] += this.offsetX - object.width / 2; - } - else if (prop === 'y1' || prop === 'y2') { - coords[prop] += this.offsetY - object.height / 2; - } + if (!opacity) { + opacity = el.getAttribute("stop-opacity"); } - } - - commonAttributes = 'id="SVGID_' + this.id + - '" gradientUnits="userSpaceOnUse"'; - if (this.gradientTransform) { - commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" '; - } - if (this.type === 'linear') { - markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - } - else if (this.type === 'radial') { - markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - } - - for (var i = 0; i < this.colorStops.length; i++) { - markup.push( - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ); - } - - markup.push((this.type === 'linear' ? '\n' : '\n')); - - return markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns an instance of CanvasGradient - * @param {CanvasRenderingContext2D} ctx Context to render on - * @return {CanvasGradient} - */ - toLive: function(ctx, object) { - var gradient, prop, coords = fabric.util.object.clone(this.coords); - - if (!this.type) { - return; - } - - if (object.group && object.group.type === 'path-group') { - for (prop in coords) { - if (prop === 'x1' || prop === 'x2') { - coords[prop] += -this.offsetX + object.width / 2; - } - else if (prop === 'y1' || prop === 'y2') { - coords[prop] += -this.offsetY + object.height / 2; - } - } - } - - if (object.type === 'text' || object.type === 'i-text') { - for (prop in coords) { - if (prop === 'x1' || prop === 'x2') { - coords[prop] -= object.width / 2; - } - else if (prop === 'y1' || prop === 'y2') { - coords[prop] -= object.height / 2; - } - } - } - - if (this.type === 'linear') { - gradient = ctx.createLinearGradient( - coords.x1, coords.y1, coords.x2, coords.y2); - } - else if (this.type === 'radial') { - gradient = ctx.createRadialGradient( - coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); - } - - for (var i = 0, len = this.colorStops.length; i < len; i++) { - var color = this.colorStops[i].color, - opacity = this.colorStops[i].opacity, - offset = this.colorStops[i].offset; - - if (typeof opacity !== 'undefined') { - color = new fabric.Color(color).setAlpha(opacity).toRgba(); - } - gradient.addColorStop(parseFloat(offset), color); - } - - return gradient; - } - }); - - fabric.util.object.extend(fabric.Gradient, { - - /* _FROM_SVG_START_ */ - /** - * Returns {@link fabric.Gradient} instance from an SVG element - * @static - * @memberof fabric.Gradient - * @param {SVGGradientElement} el SVG gradient element - * @param {fabric.Object} instance - * @return {fabric.Gradient} Gradient instance - * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement - * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement - */ - fromElement: function(el, instance) { - - /** - * @example: - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * - */ - - var colorStopEls = el.getElementsByTagName('stop'), - type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'), - gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', - gradientTransform = el.getAttribute('gradientTransform'), - colorStops = [], - coords = { }, ellipseMatrix; - - if (type === 'linear') { - coords = getLinearCoords(el); - } - else if (type === 'radial') { - coords = getRadialCoords(el); - } - - for (var i = colorStopEls.length; i--; ) { - colorStops.push(getColorStop(colorStopEls[i])); - } - - ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); - - var gradient = new fabric.Gradient({ - type: type, - coords: coords, - colorStops: colorStops, - offsetX: -instance.left, - offsetY: -instance.top - }); - - if (gradientTransform || ellipseMatrix !== '') { - gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); - } - return gradient; - }, - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Gradient} instance from its object representation - * @static - * @memberof fabric.Gradient - * @param {Object} obj - * @param {Object} [options] Options object - */ - forObject: function(obj, options) { - options || (options = { }); - _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); - return new fabric.Gradient(options); - } - }); - - /** - * @private - */ - function _convertPercentUnitsToValues(object, options, gradientUnits) { - var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; - for (var prop in options) { - propValue = parseFloat(options[prop], 10); - if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { - multFactor = 0.01; - } - else { - multFactor = 1; - } - if (prop === 'x1' || prop === 'x2' || prop === 'r2') { - multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; - addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; - } - else if (prop === 'y1' || prop === 'y2') { - multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; - addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; - } - options[prop] = propValue * multFactor + addFactor; - } - if (object.type === 'ellipse' && - options.r2 !== null && - gradientUnits === 'objectBoundingBox' && - object.rx !== object.ry) { - - var scaleFactor = object.ry/object.rx; - ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; - if (options.y1) { - options.y1 /= scaleFactor; - } - if (options.y2) { - options.y2 /= scaleFactor; - } - } - return ellipseMatrix; - } -})(); - - -/** - * Pattern class - * @class fabric.Pattern - * @see {@link http://fabricjs.com/patterns/|Pattern demo} - * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo} - * @see {@link fabric.Pattern#initialize} for constructor definition - */ -fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { - - /** - * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @type String - * @default - */ - repeat: 'repeat', - - /** - * Pattern horizontal offset from object's left/top corner - * @type Number - * @default - */ - offsetX: 0, - - /** - * Pattern vertical offset from object's left/top corner - * @type Number - * @default - */ - offsetY: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Pattern} thisArg - */ - initialize: function(options) { - options || (options = { }); - - this.id = fabric.Object.__uid++; - - if (options.source) { - if (typeof options.source === 'string') { - // function string - if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { - this.source = new Function(fabric.util.getFunctionBody(options.source)); - } - else { - // img src string - var _this = this; - this.source = fabric.util.createImage(); - fabric.util.loadImage(options.source, function(img) { - _this.source = img; - }); - } - } - else { - // img element - this.source = options.source; - } - } - if (options.repeat) { - this.repeat = options.repeat; - } - if (options.offsetX) { - this.offsetX = options.offsetX; - } - if (options.offsetY) { - this.offsetY = options.offsetY; - } - }, - - /** - * Returns object representation of a pattern - * @return {Object} Object representation of a pattern instance - */ - toObject: function() { - - var source; - - // callback - if (typeof this.source === 'function') { - source = String(this.source); - } - // element - else if (typeof this.source.src === 'string') { - source = this.source.src; - } - - return { - source: source, - repeat: this.repeat, - offsetX: this.offsetX, - offsetY: this.offsetY - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a pattern - * @param {fabric.Object} object - * @return {String} SVG representation of a pattern - */ - toSVG: function(object) { - var patternSource = typeof this.source === 'function' ? this.source() : this.source, - patternWidth = patternSource.width / object.getWidth(), - patternHeight = patternSource.height / object.getHeight(), - patternOffsetX = this.offsetX / object.getWidth(), - patternOffsetY = this.offsetY / object.getHeight(), - patternImgSrc = ''; - if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') { - patternHeight = 1; - } - if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') { - patternWidth = 1; - } - if (patternSource.src) { - patternImgSrc = patternSource.src; - } - else if (patternSource.toDataURL) { - patternImgSrc = patternSource.toDataURL(); - } - - return '\n' + - '\n' + - '\n'; - }, - /* _TO_SVG_END_ */ - - /** - * Returns an instance of CanvasPattern - * @param {CanvasRenderingContext2D} ctx Context to create pattern - * @return {CanvasPattern} - */ - toLive: function(ctx) { - var source = typeof this.source === 'function' - ? this.source() - : this.source; - - // if the image failed to load, return, and allow rest to continue loading - if (!source) { - return ''; - } - - // if an image - if (typeof source.src !== 'undefined') { - if (!source.complete) { - return ''; - } - if (source.naturalWidth === 0 || source.naturalHeight === 0) { - return ''; - } - } - return ctx.createPattern(source, this.repeat); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - toFixed = fabric.util.toFixed; - - if (fabric.Shadow) { - fabric.warn('fabric.Shadow is already defined.'); - return; - } - - /** - * Shadow class - * @class fabric.Shadow - * @see {@link http://fabricjs.com/shadows/|Shadow demo} - * @see {@link fabric.Shadow#initialize} for constructor definition - */ - fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { - - /** - * Shadow color - * @type String - * @default - */ - color: 'rgb(0,0,0)', - - /** - * Shadow blur - * @type Number - */ - blur: 0, - - /** - * Shadow horizontal offset - * @type Number - * @default - */ - offsetX: 0, - - /** - * Shadow vertical offset - * @type Number - * @default - */ - offsetY: 0, - - /** - * Whether the shadow should affect stroke operations - * @type Boolean - * @default - */ - affectStroke: false, - - /** - * Indicates whether toObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Constructor - * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Shadow} thisArg - */ - initialize: function(options) { - - if (typeof options === 'string') { - options = this._parseShadow(options); - } - - for (var prop in options) { - this[prop] = options[prop]; - } - - this.id = fabric.Object.__uid++; - }, - - /** - * @private - * @param {String} shadow Shadow value to parse - * @return {Object} Shadow object with color, offsetX, offsetY and blur - */ - _parseShadow: function(shadow) { - var shadowStr = shadow.trim(), - offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], - color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; - - return { - color: color.trim(), - offsetX: parseInt(offsetsAndBlur[1], 10) || 0, - offsetY: parseInt(offsetsAndBlur[2], 10) || 0, - blur: parseInt(offsetsAndBlur[3], 10) || 0 - }; - }, - - /** - * Returns a string representation of an instance - * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow - * @return {String} Returns CSS3 text-shadow declaration - */ - toString: function() { - return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a shadow - * @param {fabric.Object} object - * @return {String} SVG representation of a shadow - */ - toSVG: function(object) { - var mode = 'SourceAlpha', fBoxX = 40, fBoxY = 40; - - if (object && (object.fill === this.color || object.stroke === this.color)) { - mode = 'SourceGraphic'; - } - - if (object.width && object.height) { - //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion - // we add some extra space to filter box to contain the blur ( 20 ) - fBoxX = toFixed(Math.abs(this.offsetX / object.getWidth()), 2) * 100 + 20; - fBoxY = toFixed(Math.abs(this.offsetY / object.getHeight()), 2) * 100 + 20; - } - - return ( - '\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\t\n' + - '\t\t\n' + - '\t\n' + - '\n'); - }, - /* _TO_SVG_END_ */ - - /** - * Returns object representation of a shadow - * @return {Object} Object representation of a shadow instance - */ - toObject: function() { - if (this.includeDefaultValues) { + color = new fabric.Color(color); + colorAlpha = color.getAlpha(); + opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); + opacity *= colorAlpha; return { - color: this.color, - blur: this.blur, - offsetX: this.offsetX, - offsetY: this.offsetY + offset: offset, + color: color.toRgb(), + opacity: opacity }; - } - var obj = { }, proto = fabric.Shadow.prototype; - if (this.color !== proto.color) { - obj.color = this.color; - } - if (this.blur !== proto.blur) { - obj.blur = this.blur; - } - if (this.offsetX !== proto.offsetX) { - obj.offsetX = this.offsetX; - } - if (this.offsetY !== proto.offsetY) { - obj.offsetY = this.offsetY; - } - return obj; } - }); - - /** - * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") - * @static - * @field - * @memberOf fabric.Shadow - */ - fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function () { - - 'use strict'; - - if (fabric.StaticCanvas) { - fabric.warn('fabric.StaticCanvas is already defined.'); - return; - } - - // aliases for faster resolution - var extend = fabric.util.object.extend, - getElementOffset = fabric.util.getElementOffset, - removeFromArray = fabric.util.removeFromArray, - - CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); - - /** - * Static canvas class - * @class fabric.StaticCanvas - * @mixes fabric.Collection - * @mixes fabric.Observable - * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo} - * @see {@link fabric.StaticCanvas#initialize} for constructor definition - * @fires before:render - * @fires after:render - * @fires canvas:cleared - * @fires object:added - * @fires object:removed - */ - fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { - - /** - * Constructor - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(el, options) { - options || (options = { }); - - this._initStatic(el, options); - fabric.StaticCanvas.activeInstance = this; - }, - - /** - * Background color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. - * @type {(String|fabric.Pattern)} - * @default - */ - backgroundColor: '', - - /** - * Background image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. - * Backwards incompatibility note: The "backgroundImageOpacity" - * and "backgroundImageStretch" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. - * @type fabric.Image - * @default - */ - backgroundImage: null, - - /** - * Overlay color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayColor} - * @since 1.3.9 - * @type {(String|fabric.Pattern)} - * @default - */ - overlayColor: '', - - /** - * Overlay image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. - * Backwards incompatibility note: The "overlayImageLeft" - * and "overlayImageTop" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#left} and {@link fabric.Image#top}. - * @type fabric.Image - * @default - */ - overlayImage: null, - - /** - * Indicates whether toObject/toDatalessObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Indicates whether objects' state should be saved - * @type Boolean - * @default - */ - stateful: true, - - /** - * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. - * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once - * (followed by a manual rendering after addition/deletion) - * @type Boolean - * @default - */ - renderOnAddRemove: true, - - /** - * Function that determines clipping of entire canvas area - * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} - * @type Function - * @default - */ - clipTo: null, - - /** - * Indicates whether object controls (borders/controls) are rendered above overlay image - * @type Boolean - * @default - */ - controlsAboveOverlay: false, - - /** - * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas - * @type Boolean - * @default - */ - allowTouchScrolling: false, - - /** - * Indicates whether this canvas will use image smoothing, this is on by default in browsers - * @type Boolean - * @default - */ - imageSmoothingEnabled: true, - - /** - * Indicates whether objects should remain in current stack position when selected. When false objects are brought to top and rendered as part of the selection group - * @type Boolean - * @default - */ - preserveObjectStacking: false, - - /** - * The transformation (in the format of Canvas transform) which focuses the viewport - * @type Array - * @default - */ - viewportTransform: [1, 0, 0, 1, 0, 0], - - /** - * Callback; invoked right before object is about to be scaled/rotated - */ - onBeforeScaleRotate: function () { - /* NOOP */ - }, - - /** - * @private - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - */ - _initStatic: function(el, options) { - this._objects = []; - - this._createLowerCanvas(el); - this._initOptions(options); - this._setImageSmoothing(); - - if (options.overlayImage) { - this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); - } - if (options.backgroundImage) { - this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); - } - if (options.backgroundColor) { - this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); - } - if (options.overlayColor) { - this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); - } - this.calcOffset(); - }, - - /** - * Calculates canvas element offset relative to the document - * This method is also attached as "resize" event handler of window - * @return {fabric.Canvas} instance - * @chainable - */ - calcOffset: function () { - this._offset = getElementOffset(this.lowerCanvasEl); - return this; - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to - * @param {Function} callback callback to invoke when image is loaded and set as an overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} - * @example Normal overlayImage with left/top = 0 - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example overlayImage with different properties - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched overlayImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched overlayImage #2 - width/height correspond to canvas width/height - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example overlayImage loaded from cross-origin - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top', - * crossOrigin: 'anonymous' - * }); - */ - setOverlayImage: function (image, callback, options) { - return this.__setBgOverlayImage('overlayImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to - * @param {Function} callback Callback to invoke when image is loaded and set as background - * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} - * @example Normal backgroundImage with left/top = 0 - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example backgroundImage with different properties - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example backgroundImage loaded from cross-origin - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top', - * crossOrigin: 'anonymous' - * }); - */ - setBackgroundImage: function (image, callback, options) { - return this.__setBgOverlayImage('backgroundImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas - * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} - * @example Normal overlayColor - color value - * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor with repeat and offset - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setOverlayColor: function(overlayColor, callback) { - return this.__setBgOverlayColor('overlayColor', overlayColor, callback); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas - * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} - * @example Normal backgroundColor - color value - * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor with repeat and offset - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setBackgroundColor: function(backgroundColor, callback) { - return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); - }, - - /** - * @private - * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} - */ - _setImageSmoothing: function() { - var ctx = this.getContext(); - - ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} - * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) - * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to - * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. - */ - __setBgOverlayImage: function(property, image, callback, options) { - if (typeof image === 'string') { - fabric.util.loadImage(image, function(img) { - this[property] = new fabric.Image(img, options); - callback && callback(); - }, this, options && options.crossOrigin); - } - else { - options && image.setOptions(options); - this[property] = image; - callback && callback(); - } - - return this; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} - * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) - * @param {(Object|String|null)} color Object with pattern information, color value or null - * @param {Function} [callback] Callback is invoked when color is set - */ - __setBgOverlayColor: function(property, color, callback) { - if (color && color.source) { - var _this = this; - fabric.util.loadImage(color.source, function(img) { - _this[property] = new fabric.Pattern({ - source: img, - repeat: color.repeat, - offsetX: color.offsetX, - offsetY: color.offsetY - }); - callback && callback(); - }); - } - else { - this[property] = color; - callback && callback(); - } - - return this; - }, - - /** - * @private - */ - _createCanvasElement: function() { - var element = fabric.document.createElement('canvas'); - if (!element.style) { - element.style = { }; - } - if (!element) { - throw CANVAS_INIT_ERROR; - } - this._initCanvasElement(element); - return element; - }, - - /** - * @private - * @param {HTMLElement} element - */ - _initCanvasElement: function(element) { - fabric.util.createCanvasElement(element); - - if (typeof element.getContext === 'undefined') { - throw CANVAS_INIT_ERROR; - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initOptions: function (options) { - for (var prop in options) { - this[prop] = options[prop]; - } - - this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; - this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; - - if (!this.lowerCanvasEl.style) { - return; - } - - this.lowerCanvasEl.width = this.width; - this.lowerCanvasEl.height = this.height; - - this.lowerCanvasEl.style.width = this.width + 'px'; - this.lowerCanvasEl.style.height = this.height + 'px'; - - this.viewportTransform = this.viewportTransform.slice(); - }, - - /** - * Creates a bottom canvas - * @private - * @param {HTMLElement} [canvasEl] - */ - _createLowerCanvas: function (canvasEl) { - this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); - this._initCanvasElement(this.lowerCanvasEl); - - fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); - - if (this.interactive) { - this._applyCanvasStyle(this.lowerCanvasEl); - } - - this.contextContainer = this.lowerCanvasEl.getContext('2d'); - }, - - /** - * Returns canvas width (in px) - * @return {Number} - */ - getWidth: function () { - return this.width; - }, - - /** - * Returns canvas height (in px) - * @return {Number} - */ - getHeight: function () { - return this.height; - }, - - /** - * Sets width of this canvas instance - * @param {Number|String} value Value to set width to - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} instance - * @chainable true - */ - setWidth: function (value, options) { - return this.setDimensions({ width: value }, options); - }, - - /** - * Sets height of this canvas instance - * @param {Number|String} value Value to set height to - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} instance - * @chainable true - */ - setHeight: function (value, options) { - return this.setDimensions({ height: value }, options); - }, - - /** - * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) - * @param {Object} dimensions Object with width/height properties - * @param {Number|String} [dimensions.width] Width of canvas element - * @param {Number|String} [dimensions.height] Height of canvas element - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} thisArg - * @chainable - */ - setDimensions: function (dimensions, options) { - var cssValue; - - options = options || {}; - - for (var prop in dimensions) { - cssValue = dimensions[prop]; - - if (!options.cssOnly) { - this._setBackstoreDimension(prop, dimensions[prop]); - cssValue += 'px'; - } - - if (!options.backstoreOnly) { - this._setCssDimension(prop, cssValue); - } - } - - if (!options.cssOnly) { - this.renderAll(); - } - - this.calcOffset(); - - return this; - }, - - /** - * Helper for setting width/height - * @private - * @param {String} prop property (width|height) - * @param {Number} value value to set property to - * @return {fabric.Canvas} instance - * @chainable true - */ - _setBackstoreDimension: function (prop, value) { - this.lowerCanvasEl[prop] = value; - - if (this.upperCanvasEl) { - this.upperCanvasEl[prop] = value; - } - - if (this.cacheCanvasEl) { - this.cacheCanvasEl[prop] = value; - } - - this[prop] = value; - - return this; - }, - - /** - * Helper for setting css width/height - * @private - * @param {String} prop property (width|height) - * @param {String} value value to set property to - * @return {fabric.Canvas} instance - * @chainable true - */ - _setCssDimension: function (prop, value) { - this.lowerCanvasEl.style[prop] = value; - - if (this.upperCanvasEl) { - this.upperCanvasEl.style[prop] = value; - } - - if (this.wrapperEl) { - this.wrapperEl.style[prop] = value; - } - - return this; - }, - - /** - * Returns canvas zoom level - * @return {Number} - */ - getZoom: function () { - return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); - }, - - /** - * Sets viewport transform of this canvas instance - * @param {Array} vpt the transform in the form of context.transform - * @return {fabric.Canvas} instance - * @chainable true - */ - setViewportTransform: function (vpt) { - var activeGroup = this.getActiveGroup(); - this.viewportTransform = vpt; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - if (activeGroup) { - activeGroup.setCoords(); - } - return this; - }, - - /** - * Sets zoom level of this canvas instance, zoom centered around point - * @param {fabric.Point} point to zoom with respect to - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - zoomToPoint: function (point, value) { - // TODO: just change the scale, preserve other transformations - var before = point; - point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); - this.viewportTransform[0] = value; - this.viewportTransform[3] = value; - var after = fabric.util.transformPoint(point, this.viewportTransform); - this.viewportTransform[4] += before.x - after.x; - this.viewportTransform[5] += before.y - after.y; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Sets zoom level of this canvas instance - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - setZoom: function (value) { - this.zoomToPoint(new fabric.Point(0, 0), value); - return this; - }, - - /** - * Pan viewport so as to place point at top left corner of canvas - * @param {fabric.Point} point to move to - * @return {fabric.Canvas} instance - * @chainable true - */ - absolutePan: function (point) { - this.viewportTransform[4] = -point.x; - this.viewportTransform[5] = -point.y; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Pans viewpoint relatively - * @param {fabric.Point} point (position vector) to move by - * @return {fabric.Canvas} instance - * @chainable true - */ - relativePan: function (point) { - return this.absolutePan(new fabric.Point( - -point.x - this.viewportTransform[4], - -point.y - this.viewportTransform[5] - )); - }, - - /** - * Returns <canvas> element corresponding to this instance - * @return {HTMLCanvasElement} - */ - getElement: function () { - return this.lowerCanvasEl; - }, - - /** - * Returns currently selected object, if any - * @return {fabric.Object} - */ - getActiveObject: function() { - return null; - }, - - /** - * Returns currently selected group of object, if any - * @return {fabric.Group} - */ - getActiveGroup: function() { - return null; - }, - - /** - * Given a context, renders an object on that context - * @param {CanvasRenderingContext2D} ctx Context to render object on - * @param {fabric.Object} object Object to render - * @private - */ - _draw: function (ctx, object) { - if (!object) { - return; - } - - ctx.save(); - var v = this.viewportTransform; - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - if (this._shouldRenderObject(object)) { - object.render(ctx); - } - ctx.restore(); - if (!this.controlsAboveOverlay) { - object._renderControls(ctx); - } - }, - - _shouldRenderObject: function(object) { - if (!object) { - return false; - } - return (object !== this.getActiveGroup() || !this.preserveObjectStacking); - }, - - /** - * @private - * @param {fabric.Object} obj Object that was added - */ - _onObjectAdded: function(obj) { - this.stateful && obj.setupState(); - obj.canvas = this; - obj.setCoords(); - this.fire('object:added', { target: obj }); - obj.fire('added'); - }, - - /** - * @private - * @param {fabric.Object} obj Object that was removed - */ - _onObjectRemoved: function(obj) { - // removing active object should fire "selection:cleared" events - if (this.getActiveObject() === obj) { - this.fire('before:selection:cleared', { target: obj }); - this._discardActiveObject(); - this.fire('selection:cleared'); - } - - this.fire('object:removed', { target: obj }); - obj.fire('removed'); - }, - - /** - * Clears specified context of canvas element - * @param {CanvasRenderingContext2D} ctx Context to clear - * @return {fabric.Canvas} thisArg - * @chainable - */ - clearContext: function(ctx) { - ctx.clearRect(0, 0, this.width, this.height); - return this; - }, - - /** - * Returns context of canvas where objects are drawn - * @return {CanvasRenderingContext2D} - */ - getContext: function () { - return this.contextContainer; - }, - - /** - * Clears all contexts (background, main, top) of an instance - * @return {fabric.Canvas} thisArg - * @chainable - */ - clear: function () { - this._objects.length = 0; - if (this.discardActiveGroup) { - this.discardActiveGroup(); - } - if (this.discardActiveObject) { - this.discardActiveObject(); - } - this.clearContext(this.contextContainer); - if (this.contextTop) { - this.clearContext(this.contextTop); - } - this.fire('canvas:cleared'); - this.renderAll(); - return this; - }, - - /** - * Renders both the top canvas and the secondary container canvas. - * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas - * @return {fabric.Canvas} instance - * @chainable - */ - renderAll: function (allOnTop) { - var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], - activeGroup = this.getActiveGroup(); - - if (this.contextTop && this.selection && !this._groupSelector) { - this.clearContext(this.contextTop); - } - - if (!allOnTop) { - this.clearContext(canvasToDrawOn); - } - - this.fire('before:render'); - - if (this.clipTo) { - fabric.util.clipContext(this, canvasToDrawOn); - } - - this._renderBackground(canvasToDrawOn); - this._renderObjects(canvasToDrawOn, activeGroup); - this._renderActiveGroup(canvasToDrawOn, activeGroup); - - if (this.clipTo) { - canvasToDrawOn.restore(); - } - - this._renderOverlay(canvasToDrawOn); - - if (this.controlsAboveOverlay && this.interactive) { - this.drawControls(canvasToDrawOn); - } - - this.fire('after:render'); - - return this; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {fabric.Group} activeGroup - */ - _renderObjects: function(ctx, activeGroup) { - var i, length; - - // fast path - if (!activeGroup || this.preserveObjectStacking) { - for (i = 0, length = this._objects.length; i < length; ++i) { - this._draw(ctx, this._objects[i]); - } - } - else { - for (i = 0, length = this._objects.length; i < length; ++i) { - if (this._objects[i] && !activeGroup.contains(this._objects[i])) { - this._draw(ctx, this._objects[i]); - } - } - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {fabric.Group} activeGroup - */ - _renderActiveGroup: function(ctx, activeGroup) { - - // delegate rendering to group selection (if one exists) - if (activeGroup) { - - //Store objects in group preserving order, then replace - var sortedObjects = []; - this.forEachObject(function (object) { - if (activeGroup.contains(object)) { - sortedObjects.push(object); - } - }); - activeGroup._set('objects', sortedObjects); - this._draw(ctx, activeGroup); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderBackground: function(ctx) { - if (this.backgroundColor) { - ctx.fillStyle = this.backgroundColor.toLive - ? this.backgroundColor.toLive(ctx) - : this.backgroundColor; - - ctx.fillRect( - this.backgroundColor.offsetX || 0, - this.backgroundColor.offsetY || 0, - this.width, - this.height); - } - if (this.backgroundImage) { - this._draw(ctx, this.backgroundImage); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderOverlay: function(ctx) { - if (this.overlayColor) { - ctx.fillStyle = this.overlayColor.toLive - ? this.overlayColor.toLive(ctx) - : this.overlayColor; - - ctx.fillRect( - this.overlayColor.offsetX || 0, - this.overlayColor.offsetY || 0, - this.width, - this.height); - } - if (this.overlayImage) { - this._draw(ctx, this.overlayImage); - } - }, - - /** - * Method to render only the top canvas. - * Also used to render the group selection box. - * @return {fabric.Canvas} thisArg - * @chainable - */ - renderTop: function () { - var ctx = this.contextTop || this.contextContainer; - this.clearContext(ctx); - - // we render the top context - last object - if (this.selection && this._groupSelector) { - this._drawSelection(); - } - - // delegate rendering to group selection if one exists - // used for drawing selection borders/controls - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - activeGroup.render(ctx); - } - - this._renderOverlay(ctx); - - this.fire('after:render'); - - return this; - }, - - /** - * Returns coordinates of a center of canvas. - * Returned value is an object with top and left properties - * @return {Object} object with "top" and "left" number values - */ - getCenter: function () { - return { - top: this.getHeight() / 2, - left: this.getWidth() / 2 - }; - }, - - /** - * Centers object horizontally. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center horizontally - * @return {fabric.Canvas} thisArg - */ - centerObjectH: function (object) { - this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); - this.renderAll(); - return this; - }, - - /** - * Centers object vertically. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObjectV: function (object) { - this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); - this.renderAll(); - return this; - }, - - /** - * Centers object vertically and horizontally. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically and horizontally - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObject: function(object) { - var center = this.getCenter(); - - this._centerObject(object, new fabric.Point(center.left, center.top)); - this.renderAll(); - return this; - }, - - /** - * @private - * @param {fabric.Object} object Object to center - * @param {fabric.Point} center Center point - * @return {fabric.Canvas} thisArg - * @chainable - */ - _centerObject: function(object, center) { - object.setPositionByOrigin(center, 'center', 'center'); - return this; - }, - - /** - * Returs dataless JSON representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} json string - */ - toDatalessJSON: function (propertiesToInclude) { - return this.toDatalessObject(propertiesToInclude); - }, - - /** - * Returns object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function (propertiesToInclude) { - return this._toObjectMethod('toObject', propertiesToInclude); - }, - - /** - * Returns dataless object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function (propertiesToInclude) { - return this._toObjectMethod('toDatalessObject', propertiesToInclude); - }, - - /** - * @private - */ - _toObjectMethod: function (methodName, propertiesToInclude) { - - var data = { - objects: this._toObjects(methodName, propertiesToInclude) - }; - - extend(data, this.__serializeBgOverlay()); - - fabric.util.populateWithProperties(this, data, propertiesToInclude); - - return data; - }, - - /** - * @private - */ - _toObjects: function(methodName, propertiesToInclude) { - return this.getObjects().map(function(instance) { - return this._toObject(instance, methodName, propertiesToInclude); - }, this); - }, - - /** - * @private - */ - _toObject: function(instance, methodName, propertiesToInclude) { - var originalValue; - - if (!this.includeDefaultValues) { - originalValue = instance.includeDefaultValues; - instance.includeDefaultValues = false; - } - - //If the object is part of the current selection group, it should - //be transformed appropriately - //i.e. it should be serialised as it would appear if the selection group - //were to be destroyed. - var originalProperties = this._realizeGroupTransformOnObject(instance), - object = instance[methodName](propertiesToInclude); - if (!this.includeDefaultValues) { - instance.includeDefaultValues = originalValue; - } - - //Undo the damage we did by changing all of its properties - this._unwindGroupTransformOnObject(instance, originalProperties); - - return object; - }, - - /** - * Realises an object's group transformation on it - * @private - * @param {fabric.Object} [instance] the object to transform (gets mutated) - * @returns the original values of instance which were changed - */ - _realizeGroupTransformOnObject: function(instance) { - var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width']; - if (instance.group && instance.group === this.getActiveGroup()) { - //Copy all the positionally relevant properties across now - var originalValues = {}; - layoutProps.forEach(function(prop) { - originalValues[prop] = instance[prop]; - }); - this.getActiveGroup().realizeTransform(instance); - return originalValues; - } - else { - return null; - } - }, - - /* - * Restores the changed properties of instance - * @private - * @param {fabric.Object} [instance] the object to un-transform (gets mutated) - * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject - */ - _unwindGroupTransformOnObject: function(instance, originalValues) { - if (originalValues) { - instance.set(originalValues); - } - }, - - /** - * @private - */ - __serializeBgOverlay: function() { - var data = { - background: (this.backgroundColor && this.backgroundColor.toObject) - ? this.backgroundColor.toObject() - : this.backgroundColor - }; - - if (this.overlayColor) { - data.overlay = this.overlayColor.toObject - ? this.overlayColor.toObject() - : this.overlayColor; - } - if (this.backgroundImage) { - data.backgroundImage = this.backgroundImage.toObject(); - } - if (this.overlayImage) { - data.overlayImage = this.overlayImage.toObject(); - } - - return data; - }, - - /* _TO_SVG_START_ */ - /** - * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, - * a zoomed canvas will then produce zoomed SVG output. - * @type Boolean - * @default - */ - svgViewportTransformation: true, - - /** - * Returns SVG representation of canvas - * @function - * @param {Object} [options] Options object for SVG output - * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included - * @param {Object} [options.viewBox] SVG viewbox object - * @param {Number} [options.viewBox.x] x-cooridnate of viewbox - * @param {Number} [options.viewBox.y] y-coordinate of viewbox - * @param {Number} [options.viewBox.width] Width of viewbox - * @param {Number} [options.viewBox.height] Height of viewbox - * @param {String} [options.encoding=UTF-8] Encoding of SVG output - * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. - * @return {String} SVG string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} - * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} - * @example Normal SVG output - * var svg = canvas.toSVG(); - * @example SVG output without preamble (without <?xml ../>) - * var svg = canvas.toSVG({suppressPreamble: true}); - * @example SVG output with viewBox attribute - * var svg = canvas.toSVG({ - * viewBox: { - * x: 100, - * y: 100, - * width: 200, - * height: 300 - * } - * }); - * @example SVG output with different encoding (default: UTF-8) - * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); - * @example Modify SVG output with reviver function - * var svg = canvas.toSVG(null, function(svg) { - * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); - * }); - */ - toSVG: function(options, reviver) { - options || (options = { }); - - var markup = []; - - this._setSVGPreamble(markup, options); - this._setSVGHeader(markup, options); - - this._setSVGBgOverlayColor(markup, 'backgroundColor'); - this._setSVGBgOverlayImage(markup, 'backgroundImage'); - - this._setSVGObjects(markup, reviver); - - this._setSVGBgOverlayColor(markup, 'overlayColor'); - this._setSVGBgOverlayImage(markup, 'overlayImage'); - - markup.push(''); - - return markup.join(''); - }, - - /** - * @private - */ - _setSVGPreamble: function(markup, options) { - if (!options.suppressPreamble) { - markup.push( - '', - '\n' - ); - } - }, - - /** - * @private - */ - _setSVGHeader: function(markup, options) { - var width, height, vpt; - - if (options.viewBox) { - width = options.viewBox.width; - height = options.viewBox.height; - } - else { - width = this.width; - height = this.height; - if (!this.svgViewportTransformation) { - vpt = this.viewportTransform; - width /= vpt[0]; - height /= vpt[3]; - } - } - - markup.push( - '', - 'Created with Fabric.js ', fabric.version, '', - '', - fabric.createSVGFontFacesMarkup(this.getObjects()), - fabric.createSVGRefElementsMarkup(this), - '' - ); - }, - - /** - * @private - */ - _setSVGObjects: function(markup, reviver) { - for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { - var instance = objects[i], - //If the object is in a selection group, simulate what would happen to that - //object when the group is deselected - originalProperties = this._realizeGroupTransformOnObject(instance); - markup.push(instance.toSVG(reviver)); - this._unwindGroupTransformOnObject(instance, originalProperties); - } - }, - - /** - * @private - */ - _setSVGBgOverlayImage: function(markup, property) { - if (this[property] && this[property].toSVG) { - markup.push(this[property].toSVG()); - } - }, - - /** - * @private - */ - _setSVGBgOverlayColor: function(markup, property) { - if (this[property] && this[property].source) { - markup.push( - '' - ); - } - else if (this[property] && property === 'overlayColor') { - markup.push( - '' - ); - } - }, - /* _TO_SVG_END_ */ - - /** - * Moves an object to the bottom of the stack of drawn objects - * @param {fabric.Object} object Object to send to back - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendToBack: function (object) { - removeFromArray(this._objects, object); - this._objects.unshift(object); - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object to the top of the stack of drawn objects - * @param {fabric.Object} object Object to send - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringToFront: function (object) { - removeFromArray(this._objects, object); - this._objects.push(object); - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object down in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendBackwards: function (object, intersecting) { - var idx = this._objects.indexOf(object); - - // if object is not on the bottom of stack - if (idx !== 0) { - var newIdx = this._findNewLowerIndex(object, idx, intersecting); - - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - this.renderAll && this.renderAll(); - } - return this; - }, - - /** - * @private - */ - _findNewLowerIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse down the stack looking for the nearest intersecting object - for (var i = idx - 1; i >= 0; --i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx - 1; - } - - return newIdx; - }, - - /** - * Moves an object up in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringForward: function (object, intersecting) { - var idx = this._objects.indexOf(object); - - // if object is not on top of stack (last item in an array) - if (idx !== this._objects.length - 1) { - var newIdx = this._findNewUpperIndex(object, idx, intersecting); - - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - this.renderAll && this.renderAll(); - } - return this; - }, - - /** - * @private - */ - _findNewUpperIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse up the stack looking for the nearest intersecting object - for (var i = idx + 1; i < this._objects.length; ++i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx + 1; - } - - return newIdx; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Number} index Position to move to - * @return {fabric.Canvas} thisArg - * @chainable - */ - moveTo: function (object, index) { - removeFromArray(this._objects, object); - this._objects.splice(index, 0, object); - return this.renderAll && this.renderAll(); - }, - - /** - * Clears a canvas element and removes all event listeners - * @return {fabric.Canvas} thisArg - * @chainable - */ - dispose: function () { - this.clear(); - this.interactive && this.removeListeners(); - return this; - }, - - /** - * Returns a string representation of an instance - * @return {String} string representation of an instance - */ - toString: function () { - return '#'; + function getLinearCoords(el) { + return { + x1: el.getAttribute("x1") || 0, + y1: el.getAttribute("y1") || 0, + x2: el.getAttribute("x2") || "100%", + y2: el.getAttribute("y2") || 0 + }; } - }); - - extend(fabric.StaticCanvas.prototype, fabric.Observable); - extend(fabric.StaticCanvas.prototype, fabric.Collection); - extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); - - extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { - - /** - * @static - * @type String - * @default - */ - EMPTY_JSON: '{"objects": [], "background": "white"}', - - /** - * Provides a way to check support of some of the canvas methods - * (either those of HTMLCanvasElement itself, or rendering context) - * - * @param {String} methodName Method to check support for; - * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" - * @return {Boolean | null} `true` if method is supported (or at least exists), - * `null` if canvas element or context can not be initialized - */ - supports: function (methodName) { - var el = fabric.util.createCanvasElement(); - - if (!el || !el.getContext) { - return null; - } - - var ctx = el.getContext('2d'); - if (!ctx) { - return null; - } - - switch (methodName) { - - case 'getImageData': - return typeof ctx.getImageData !== 'undefined'; - - case 'setLineDash': - return typeof ctx.setLineDash !== 'undefined'; - - case 'toDataURL': - return typeof el.toDataURL !== 'undefined'; - - case 'toDataURLWithQuality': - try { - el.toDataURL('image/jpeg', 0); - return true; - } - catch (e) { } - return false; - - default: - return null; - } + function getRadialCoords(el) { + return { + x1: el.getAttribute("fx") || el.getAttribute("cx") || "50%", + y1: el.getAttribute("fy") || el.getAttribute("cy") || "50%", + r1: 0, + x2: el.getAttribute("cx") || "50%", + y2: el.getAttribute("cy") || "50%", + r2: el.getAttribute("r") || "50%" + }; } - }); - - /** - * Returns JSON representation of canvas - * @function - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} JSON string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} - * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} - * @example JSON without additional properties - * var json = canvas.toJSON(); - * @example JSON with additional properties included - * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); - * @example JSON without default values - * canvas.includeDefaultValues = false; - * var json = canvas.toJSON(); - */ - fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; - -})(); - - -/** - * BaseBrush class - * @class fabric.BaseBrush - * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} - */ -fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { - - /** - * Color of a brush - * @type String - * @default - */ - color: 'rgb(0, 0, 0)', - - /** - * Width of a brush - * @type Number - * @default - */ - width: 1, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), - * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Line endings style of a brush (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'round', - - /** - * Corner style of a brush (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'round', - - /** - * Stroke Dash Array. - * @type Array - * @default - */ - strokeDashArray: null, - - /** - * Sets shadow of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Object} thisArg - * @chainable - */ - setShadow: function(options) { - this.shadow = new fabric.Shadow(options); - return this; - }, - - /** - * Sets brush styles - * @private - */ - _setBrushStyles: function() { - var ctx = this.canvas.contextTop; - - ctx.strokeStyle = this.color; - ctx.lineWidth = this.width; - ctx.lineCap = this.strokeLineCap; - ctx.lineJoin = this.strokeLineJoin; - if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) { - ctx.setLineDash(this.strokeDashArray); - } - }, - - /** - * Sets brush shadow styles - * @private - */ - _setShadow: function() { - if (!this.shadow) { - return; - } - - var ctx = this.canvas.contextTop; - - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur; - ctx.shadowOffsetX = this.shadow.offsetX; - ctx.shadowOffsetY = this.shadow.offsetY; - }, - - /** - * Removes brush shadow styles - * @private - */ - _resetShadow: function() { - var ctx = this.canvas.contextTop; - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - } -}); - - -(function() { - - /** - * PencilBrush class - * @class fabric.PencilBrush - * @extends fabric.BaseBrush - */ - fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.PencilBrush} Instance of a pencil brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this._points = [ ]; - }, - - /** - * Inovoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this._prepareForDrawing(pointer); - // capture coordinates immediately - // this allows to draw dots (when movement never occurs) - this._captureDrawingPath(pointer); - this._render(); - }, - - /** - * Inovoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this._captureDrawingPath(pointer); - // redraw curve - // clear top canvas - this.canvas.clearContext(this.canvas.contextTop); - this._render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - this._finalizeAndAddPath(); - }, - - /** - * @private - * @param {Object} pointer Actual mouse position related to the canvas. - */ - _prepareForDrawing: function(pointer) { - - var p = new fabric.Point(pointer.x, pointer.y); - - this._reset(); - this._addPoint(p); - - this.canvas.contextTop.moveTo(p.x, p.y); - }, - - /** - * @private - * @param {fabric.Point} point Point to be added to points array - */ - _addPoint: function(point) { - this._points.push(point); - }, - - /** - * Clear points array and set contextTop canvas style. - * @private - */ - _reset: function() { - this._points.length = 0; - - this._setBrushStyles(); - this._setShadow(); - }, - - /** - * @private - * @param {Object} pointer Actual mouse position related to the canvas. - */ - _captureDrawingPath: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); - this._addPoint(pointerPoint); - }, - - /** - * Draw a smooth path on the topCanvas using quadraticCurveTo - * @private - */ - _render: function() { - var ctx = this.canvas.contextTop, - v = this.canvas.viewportTransform, - p1 = this._points[0], - p2 = this._points[1]; - - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - ctx.beginPath(); - - //if we only have 2 points in the path and they are the same - //it means that the user only clicked the canvas without moving the mouse - //then we should be drawing a dot. A path isn't drawn between two identical dots - //that's why we set them apart a bit - if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { - p1.x -= 0.5; - p2.x += 0.5; - } - ctx.moveTo(p1.x, p1.y); - - for (var i = 1, len = this._points.length; i < len; i++) { - // we pick the point between pi + 1 & pi + 2 as the - // end point and p1 as our control point. - var midPoint = p1.midPointFrom(p2); - ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); - - p1 = this._points[i]; - p2 = this._points[i + 1]; - } - // Draw last line as a straight line while - // we wait for the next point to be able to calculate - // the bezier control point - ctx.lineTo(p1.x, p1.y); - ctx.stroke(); - ctx.restore(); - }, - - /** - * Converts points to SVG path - * @param {Array} points Array of points - * @param {Number} minX - * @param {Number} minY - * @return {String} SVG path - */ - convertPointsToSVGPath: function(points) { - var path = [], - p1 = new fabric.Point(points[0].x, points[0].y), - p2 = new fabric.Point(points[1].x, points[1].y); - - path.push('M ', points[0].x, ' ', points[0].y, ' '); - for (var i = 1, len = points.length; i < len; i++) { - var midPoint = p1.midPointFrom(p2); - // p1 is our bezier control point - // midpoint is our endpoint - // start point is p(i-1) value. - path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); - p1 = new fabric.Point(points[i].x, points[i].y); - if ((i + 1) < points.length) { - p2 = new fabric.Point(points[i + 1].x, points[i + 1].y); - } - } - path.push('L ', p1.x, ' ', p1.y, ' '); - return path; - }, - - /** - * Creates fabric.Path object to add on canvas - * @param {String} pathData Path data - * @return {fabric.Path} Path to add on canvas - */ - createPath: function(pathData) { - var path = new fabric.Path(pathData, { - fill: null, - stroke: this.color, - strokeWidth: this.width, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeDashArray: this.strokeDashArray, - originX: 'center', - originY: 'center' - }); - - if (this.shadow) { - this.shadow.affectStroke = true; - path.setShadow(this.shadow); - } - - return path; - }, - - /** - * On mouseup after drawing the path on contextTop canvas - * we use the points captured to create an new fabric path object - * and add it to the fabric canvas. - */ - _finalizeAndAddPath: function() { - var ctx = this.canvas.contextTop; - ctx.closePath(); - - var pathData = this.convertPointsToSVGPath(this._points).join(''); - if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { - // do not create 0 width/height paths, as they are - // rendered inconsistently across browsers - // Firefox 4, for example, renders a dot, - // whereas Chrome 10 renders nothing - this.canvas.renderAll(); - return; - } - - var path = this.createPath(pathData); - - this.canvas.add(path); - path.setCoords(); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderAll(); - - // fire event 'path' created - this.canvas.fire('path:created', { path: path }); - } - }); -})(); - - -/** - * CircleBrush class - * @class fabric.CircleBrush - */ -fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { - - /** - * Width of a brush - * @type Number - * @default - */ - width: 10, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.CircleBrush} Instance of a circle brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.points = [ ]; - }, - /** - * Invoked inside on mouse down and mouse move - * @param {Object} pointer - */ - drawDot: function(pointer) { - var point = this.addPoint(pointer), - ctx = this.canvas.contextTop, - v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - ctx.fillStyle = point.fill; - ctx.beginPath(); - ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); - ctx.closePath(); - ctx.fill(); - - ctx.restore(); - }, - - /** - * Invoked on mouse down - */ - onMouseDown: function(pointer) { - this.points.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - this.drawDot(pointer); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.drawDot(pointer); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var circles = [ ]; - - for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i], - circle = new fabric.Circle({ - radius: point.radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: point.fill - }); - - this.shadow && circle.setShadow(this.shadow); - - circles.push(circle); - } - var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @param {Object} pointer - * @return {fabric.Point} Just added pointer point - */ - addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y), - - circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2, - - circleColor = new fabric.Color(this.color) - .setAlpha(fabric.util.getRandomInt(0, 100) / 100) - .toRgba(); - - pointerPoint.radius = circleRadius; - pointerPoint.fill = circleColor; - - this.points.push(pointerPoint); - - return pointerPoint; - } -}); - - -/** - * SprayBrush class - * @class fabric.SprayBrush - */ -fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { - - /** - * Width of a spray - * @type Number - * @default - */ - width: 10, - - /** - * Density of a spray (number of dots per chunk) - * @type Number - * @default - */ - density: 20, - - /** - * Width of spray dots - * @type Number - * @default - */ - dotWidth: 1, - - /** - * Width variance of spray dots - * @type Number - * @default - */ - dotWidthVariance: 1, - - /** - * Whether opacity of a dot should be random - * @type Boolean - * @default - */ - randomOpacity: false, - - /** - * Whether overlapping dots (rectangles) should be removed (for performance reasons) - * @type Boolean - * @default - */ - optimizeOverlapping: true, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.SprayBrush} Instance of a spray brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.sprayChunks = [ ]; - }, - - /** - * Invoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this.sprayChunks.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var rects = [ ]; - - for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - var sprayChunk = this.sprayChunks[i]; - - for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { - - var rect = new fabric.Rect({ - width: sprayChunk[j].width, - height: sprayChunk[j].width, - left: sprayChunk[j].x + 1, - top: sprayChunk[j].y + 1, - originX: 'center', - originY: 'center', - fill: this.color - }); - - this.shadow && rect.setShadow(this.shadow); - rects.push(rect); - } - } - - if (this.optimizeOverlapping) { - rects = this._getOptimizedRects(rects); - } - - var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @private - * @param {Array} rects - */ - _getOptimizedRects: function(rects) { - - // avoid creating duplicate rects at the same coordinates - var uniqueRects = { }, key; - - for (var i = 0, len = rects.length; i < len; i++) { - key = rects[i].left + '' + rects[i].top; - if (!uniqueRects[key]) { - uniqueRects[key] = rects[i]; - } - } - var uniqueRectsArray = [ ]; - for (key in uniqueRects) { - uniqueRectsArray.push(uniqueRects[key]); - } - - return uniqueRectsArray; - }, - - /** - * Renders brush - */ - render: function() { - var ctx = this.canvas.contextTop; - ctx.fillStyle = this.color; - - var v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { - var point = this.sprayChunkPoints[i]; - if (typeof point.opacity !== 'undefined') { - ctx.globalAlpha = point.opacity; - } - ctx.fillRect(point.x, point.y, point.width, point.width); - } - ctx.restore(); - }, - - /** - * @param {Object} pointer - */ - addSprayChunk: function(pointer) { - this.sprayChunkPoints = [ ]; - - var x, y, width, radius = this.width / 2; - - for (var i = 0; i < this.density; i++) { - - x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); - y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); - - if (this.dotWidthVariance) { - width = fabric.util.getRandomInt( - // bottom clamp width to 1 - Math.max(1, this.dotWidth - this.dotWidthVariance), - this.dotWidth + this.dotWidthVariance); - } - else { - width = this.dotWidth; - } - - var point = new fabric.Point(x, y); - point.width = width; - - if (this.randomOpacity) { - point.opacity = fabric.util.getRandomInt(0, 100) / 100; - } - - this.sprayChunkPoints.push(point); - } - - this.sprayChunks.push(this.sprayChunkPoints); - } -}); - - -/** - * PatternBrush class - * @class fabric.PatternBrush - * @extends fabric.BaseBrush - */ -fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { - - getPatternSrc: function() { - - var dotWidth = 20, - dotDistance = 5, - patternCanvas = fabric.document.createElement('canvas'), - patternCtx = patternCanvas.getContext('2d'); - - patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; - - patternCtx.fillStyle = this.color; - patternCtx.beginPath(); - patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); - patternCtx.closePath(); - patternCtx.fill(); - - return patternCanvas; - }, - - getPatternSrcFunction: function() { - return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); - }, - - /** - * Creates "pattern" instance property - */ - getPattern: function() { - return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); - }, - - /** - * Sets brush styles - */ - _setBrushStyles: function() { - this.callSuper('_setBrushStyles'); - this.canvas.contextTop.strokeStyle = this.getPattern(); - }, - - /** - * Creates path - */ - createPath: function(pathData) { - var path = this.callSuper('createPath', pathData); - path.stroke = new fabric.Pattern({ - source: this.source || this.getPatternSrcFunction() - }); - return path; - } -}); - - -(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; + fabric.Gradient = fabric.util.createClass({ + offsetX: 0, + offsetY: 0, + initialize: function(options) { + options || (options = {}); + var coords = {}; + this.id = fabric.Object.__uid++; + this.type = options.type || "linear"; + coords = { + x1: options.coords.x1 || 0, + y1: options.coords.y1 || 0, + x2: options.coords.x2 || 0, + y2: options.coords.y2 || 0 + }; + if (this.type === "radial") { + coords.r1 = options.coords.r1 || 0; + coords.r2 = options.coords.r2 || 0; } - else { - t.mouseXSign = 1; + this.coords = coords; + this.colorStops = options.colorStops.slice(); + if (options.gradientTransform) { + this.gradientTransform = options.gradientTransform; } - } - if (t.originY !== 'center') { - if (t.originY === 'bottom') { - t.mouseYSign = -1; + this.offsetX = options.offsetX || this.offsetX; + this.offsetY = options.offsetY || this.offsetY; + }, + addColorStop: function(colorStop) { + for (var position in colorStop) { + var color = new fabric.Color(colorStop[position]); + this.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); } - else { - t.mouseYSign = 1; + return this; + }, + toObject: function() { + return { + type: this.type, + coords: this.coords, + colorStops: this.colorStops, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + }, + toSVG: function(object) { + var coords = fabric.util.object.clone(this.coords), markup, commonAttributes; + this.colorStops.sort(function(a, b) { + return a.offset - b.offset; + }); + if (!(object.group && object.group.type === "path-group")) { + for (var prop in coords) { + if (prop === "x1" || prop === "x2" || prop === "r2") { + coords[prop] += this.offsetX - object.width / 2; + } else if (prop === "y1" || prop === "y2") { + coords[prop] += this.offsetY - object.height / 2; + } + } } - } - - t.originX = 'center'; - t.originY = 'center'; + commonAttributes = 'id="SVGID_' + this.id + '" gradientUnits="userSpaceOnUse"'; + if (this.gradientTransform) { + commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(" ") + ')" '; + } + if (this.type === "linear") { + markup = [ "\n' ]; + } else if (this.type === "radial") { + markup = [ "\n' ]; + } + for (var i = 0; i < this.colorStops.length; i++) { + markup.push("\n'); + } + markup.push(this.type === "linear" ? "\n" : "\n"); + return markup.join(""); + }, + toLive: function(ctx, object) { + var gradient, prop, coords = fabric.util.object.clone(this.coords); + if (!this.type) { + return; + } + if (object.group && object.group.type === "path-group") { + for (prop in coords) { + if (prop === "x1" || prop === "x2") { + coords[prop] += -this.offsetX + object.width / 2; + } else if (prop === "y1" || prop === "y2") { + coords[prop] += -this.offsetY + object.height / 2; + } + } + } + if (object.type === "text" || object.type === "i-text") { + for (prop in coords) { + if (prop === "x1" || prop === "x2") { + coords[prop] -= object.width / 2; + } else if (prop === "y1" || prop === "y2") { + coords[prop] -= object.height / 2; + } + } + } + if (this.type === "linear") { + gradient = ctx.createLinearGradient(coords.x1, coords.y1, coords.x2, coords.y2); + } else if (this.type === "radial") { + gradient = ctx.createRadialGradient(coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); + } + for (var i = 0, len = this.colorStops.length; i < len; i++) { + var color = this.colorStops[i].color, opacity = this.colorStops[i].opacity, offset = this.colorStops[i].offset; + if (typeof opacity !== "undefined") { + color = new fabric.Color(color).setAlpha(opacity).toRgba(); + } + gradient.addColorStop(parseFloat(offset), color); + } + return gradient; } - } - 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, - strokeWidth = target.stroke ? target.strokeWidth : 0; - - transform.newScaleX = localMouse.x / (target.width + strokeWidth / 2); - transform.newScaleY = localMouse.y / (target.height + strokeWidth / 2); - - if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) { - forbidScalingX = true; - } - - if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) { - forbidScalingY = true; - } - - if (by === 'equally' && !lockScalingX && !lockScalingY) { - forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform); - } - else if (!by) { - forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); - forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); - } - else if (by === 'x' && !target.get('lockUniScaling')) { - forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); - } - else if (by === 'y' && !target.get('lockUniScaling')) { - forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); - } - - forbidScalingX || forbidScalingY || this._flipObject(transform, by); - - }, - - /** - * @private - */ - _scaleObjectEqually: function(localMouse, target, transform) { - - var dist = localMouse.y + localMouse.x, - strokeWidth = target.stroke ? target.strokeWidth : 0, - lastDist = (target.height + (strokeWidth / 2)) * transform.original.scaleY + - (target.width + (strokeWidth / 2)) * transform.original.scaleX; - - // We use transform.scaleX/Y instead of target.scaleX/Y - // because the object may have a min scale and we'll loose the 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, by) { - if (transform.newScaleX < 0 && by !== 'y') { - if (transform.originX === 'left') { - transform.originX = 'right'; + }); + fabric.util.object.extend(fabric.Gradient, { + fromElement: function(el, instance) { + var colorStopEls = el.getElementsByTagName("stop"), type = el.nodeName === "linearGradient" ? "linear" : "radial", gradientUnits = el.getAttribute("gradientUnits") || "objectBoundingBox", gradientTransform = el.getAttribute("gradientTransform"), colorStops = [], coords = {}, ellipseMatrix; + if (type === "linear") { + coords = getLinearCoords(el); + } else if (type === "radial") { + coords = getRadialCoords(el); + } + for (var i = colorStopEls.length; i--; ) { + colorStops.push(getColorStop(colorStopEls[i])); + } + ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); + var gradient = new fabric.Gradient({ + type: type, + coords: coords, + colorStops: colorStops, + offsetX: -instance.left, + offsetY: -instance.top + }); + if (gradientTransform || ellipseMatrix !== "") { + gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || "") + ellipseMatrix); + } + return gradient; + }, + forObject: function(obj, options) { + options || (options = {}); + _convertPercentUnitsToValues(obj, options.coords, "userSpaceOnUse"); + return new fabric.Gradient(options); } - else if (transform.originX === 'right') { - transform.originX = 'left'; + }); + function _convertPercentUnitsToValues(object, options, gradientUnits) { + var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ""; + for (var prop in options) { + propValue = parseFloat(options[prop], 10); + if (typeof options[prop] === "string" && /^\d+%$/.test(options[prop])) { + multFactor = .01; + } else { + multFactor = 1; + } + if (prop === "x1" || prop === "x2" || prop === "r2") { + multFactor *= gradientUnits === "objectBoundingBox" ? object.width : 1; + addFactor = gradientUnits === "objectBoundingBox" ? object.left || 0 : 0; + } else if (prop === "y1" || prop === "y2") { + multFactor *= gradientUnits === "objectBoundingBox" ? object.height : 1; + addFactor = gradientUnits === "objectBoundingBox" ? object.top || 0 : 0; + } + options[prop] = propValue * multFactor + addFactor; } - } - - if (transform.newScaleY < 0 && by !== 'x') { - if (transform.originY === 'top') { - transform.originY = 'bottom'; + 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; + } } - 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 % 360; - }, - - /** - * Set the cursor type of the canvas element - * @param {String} value Cursor type of the canvas element. - * @see http://www.w3.org/TR/css3-ui/#cursor - */ - setCursor: function (value) { - this.upperCanvasEl.style.cursor = value; - }, - - /** - * @private - */ - _resetObjectTransform: function (target) { - target.scaleX = 1; - target.scaleY = 1; - target.setAngle(0); - }, - - /** - * @private - */ - _drawSelection: function () { - var ctx = this.contextTop, - groupSelector = this._groupSelector, - left = groupSelector.left, - top = groupSelector.top, - aleft = abs(left), - atop = abs(top); - - ctx.fillStyle = this.selectionColor; - - ctx.fillRect( - groupSelector.ex - ((left > 0) ? 0 : -left), - groupSelector.ey - ((top > 0) ? 0 : -top), - aleft, - atop - ); - - ctx.lineWidth = this.selectionLineWidth; - ctx.strokeStyle = this.selectionBorderColor; - - // selection 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; - // Do not check for currently grouped objects, since we check the parent group itself. - while (i--) { - if (!this._objects[i].group && this._checkTarget(e, this._objects[i], pointer)){ - this.relatedTarget = this._objects[i]; - target = this._objects[i]; - break; - } - } - - return target; - }, - - /** - * Returns pointer coordinates relative to canvas. - * @param {Event} e - * @return {Object} object with "x" and "y" number values - */ - getPointer: function (e, ignoreZoom, upperCanvasEl) { - if (!upperCanvasEl) { - upperCanvasEl = this.upperCanvasEl; - } - var pointer = getPointer(e, upperCanvasEl), - bounds = upperCanvasEl.getBoundingClientRect(), - boundsWidth = bounds.width || 0, - boundsHeight = bounds.height || 0, - cssScale; - - if (!boundsWidth || !boundsHeight ) { - if ('top' in bounds && 'bottom' in bounds) { - boundsHeight = Math.abs( bounds.top - bounds.bottom ); - } - if ('right' in bounds && 'left' in bounds) { - boundsWidth = Math.abs( bounds.right - bounds.left ); - } - } - - this.calcOffset(); - - pointer.x = pointer.x - this._offset.left; - pointer.y = pointer.y - this._offset.top; - if (!ignoreZoom) { - pointer = fabric.util.transformPoint( - pointer, - fabric.util.invertTransform(this.viewportTransform) - ); - } - - if (boundsWidth === 0 || boundsHeight === 0) { - // If bounds are not available (i.e. not visible), do not apply scale. - cssScale = { width: 1, height: 1 }; - } - else { - cssScale = { - width: upperCanvasEl.width / boundsWidth, - height: upperCanvasEl.height / boundsHeight - }; - } - - return { - x: pointer.x * cssScale.width, - y: pointer.y * cssScale.height - }; - }, - - /** - * @private - * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized - */ - _createUpperCanvas: function () { - var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); - - this.upperCanvasEl = this._createCanvasElement(); - fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); - - this.wrapperEl.appendChild(this.upperCanvasEl); - - this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); - this._applyCanvasStyle(this.upperCanvasEl); - this.contextTop = this.upperCanvasEl.getContext('2d'); - }, - - /** - * @private - */ - _createCacheCanvas: function () { - this.cacheCanvasEl = this._createCanvasElement(); - this.cacheCanvasEl.setAttribute('width', this.width); - this.cacheCanvasEl.setAttribute('height', this.height); - this.contextCache = this.cacheCanvasEl.getContext('2d'); - }, - - /** - * @private - */ - _initWrapperElement: function () { - this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { - 'class': this.containerClass - }); - fabric.util.setStyle(this.wrapperEl, { - width: this.getWidth() + 'px', - height: this.getHeight() + 'px', - position: 'relative' - }); - fabric.util.makeElementUnselectable(this.wrapperEl); - }, - - /** - * @private - * @param {HTMLElement} element canvas element to apply styles on - */ - _applyCanvasStyle: function (element) { - var width = this.getWidth() || element.width, - height = this.getHeight() || element.height; - - fabric.util.setStyle(element, { - position: 'absolute', - width: width + 'px', - height: height + 'px', - left: 0, - top: 0 - }); - element.width = width; - element.height = height; - fabric.util.makeElementUnselectable(element); - }, - - /** - * Copys the the entire inline style from one element (fromEl) to another (toEl) - * @private - * @param {Element} fromEl Element style is copied from - * @param {Element} toEl Element copied style is applied to - */ - _copyCanvasStyle: function (fromEl, toEl) { - toEl.style.cssText = fromEl.style.cssText; - }, - - /** - * Returns context of canvas where object selection is drawn - * @return {CanvasRenderingContext2D} - */ - getSelectionContext: function() { - return this.contextTop; - }, - - /** - * Returns <canvas> element on which object selection is drawn - * @return {HTMLCanvasElement} - */ - getSelectionElement: function () { - return this.upperCanvasEl; - }, - - /** - * @private - * @param {Object} object - */ - _setActiveObject: function(object) { - if (this._activeObject) { - this._activeObject.set('active', false); - } - this._activeObject = object; - object.set('active', true); - }, - - /** - * Sets given object as the only active object on canvas - * @param {fabric.Object} object Object to set as an active one - * @param {Event} [e] Event (passed along when firing "object:selected") - * @return {fabric.Canvas} thisArg - * @chainable - */ - setActiveObject: function (object, e) { - this._setActiveObject(object); - this.renderAll(); - this.fire('object:selected', { target: object, e: e }); - object.fire('selected', { e: e }); - return this; - }, - - /** - * Returns currently active object - * @return {fabric.Object} active object - */ - getActiveObject: function () { - return this._activeObject; - }, - - /** - * @private - */ - _discardActiveObject: function() { - if (this._activeObject) { - this._activeObject.set('active', false); - } - this._activeObject = null; - }, - - /** - * Discards currently active object - * @return {fabric.Canvas} thisArg - * @chainable - */ - discardActiveObject: function (e) { - this._discardActiveObject(); - this.renderAll(); - this.fire('selection:cleared', { e: e }); - return this; - }, - - /** - * @private - * @param {fabric.Group} group - */ - _setActiveGroup: function(group) { - this._activeGroup = group; - if (group) { - group.set('active', true); - } - }, - - /** - * Sets active group to a speicified one - * @param {fabric.Group} group Group to set as a current one - * @return {fabric.Canvas} thisArg - * @chainable - */ - setActiveGroup: function (group, e) { - this._setActiveGroup(group); - if (group) { - this.fire('object:selected', { target: group, e: e }); - group.fire('selected', { e: e }); - } - return this; - }, - - /** - * Returns currently active group - * @return {fabric.Group} Current group - */ - getActiveGroup: function () { - return this._activeGroup; - }, - - /** - * @private - */ - _discardActiveGroup: function() { - var g = this.getActiveGroup(); - if (g) { - g.destroy(); - } - this.setActiveGroup(null); - }, - - /** - * Discards currently active group - * @return {fabric.Canvas} thisArg - */ - discardActiveGroup: function (e) { - this._discardActiveGroup(); - this.fire('selection:cleared', { e: e }); - return this; - }, - - /** - * Deactivates all objects on canvas, removing any active group or object - * @return {fabric.Canvas} thisArg - */ - deactivateAll: function () { - var allObjects = this.getObjects(), - i = 0, - len = allObjects.length; - for ( ; i < len; i++) { - allObjects[i].set('active', false); - } - this._discardActiveGroup(); - this._discardActiveObject(); - return this; - }, - - /** - * Deactivates all objects and dispatches appropriate events - * @return {fabric.Canvas} thisArg - */ - deactivateAllWithDispatch: function (e) { - var activeObject = this.getActiveGroup() || this.getActiveObject(); - if (activeObject) { - this.fire('before:selection:cleared', { target: activeObject, e: e }); - } - this.deactivateAll(); - if (activeObject) { - this.fire('selection:cleared', { e: e }); - } - return this; - }, - - /** - * Draws objects' controls (borders/controls) - * @param {CanvasRenderingContext2D} ctx Context to render controls on - */ - drawControls: function(ctx) { - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - this._drawGroupControls(ctx, activeGroup); - } - else { - this._drawObjectsControls(ctx); - } - }, - - /** - * @private - */ - _drawGroupControls: function(ctx, activeGroup) { - activeGroup._renderControls(ctx); - }, - - /** - * @private - */ - _drawObjectsControls: function(ctx) { - for (var i = 0, len = this._objects.length; i < len; ++i) { - if (!this._objects[i] || !this._objects[i].active) { - continue; - } - this._objects[i]._renderControls(ctx); - this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; - } + return ellipseMatrix; } - }); - - // 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 eventjs !== 'undefined' && 'add' in eventjs) { - eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture); - eventjs.add(this.upperCanvasEl, 'drag', this._onDrag); - eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange); - eventjs.add(this.upperCanvasEl, 'shake', this._onShake); - eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress); - } +fabric.Pattern = fabric.util.createClass({ + repeat: "repeat", + offsetX: 0, + offsetY: 0, + initialize: function(options) { + options || (options = {}); + this.id = fabric.Object.__uid++; + if (options.source) { + if (typeof options.source === "string") { + if (typeof fabric.util.getFunctionBody(options.source) !== "undefined") { + this.source = new Function(fabric.util.getFunctionBody(options.source)); + } else { + var _this = this; + this.source = fabric.util.createImage(); + fabric.util.loadImage(options.source, function(img) { + _this.source = img; + }); + } + } else { + this.source = options.source; + } + } + if (options.repeat) { + this.repeat = options.repeat; + } + if (options.offsetX) { + this.offsetX = options.offsetX; + } + if (options.offsetY) { + this.offsetY = options.offsetY; + } }, - - /** - * @private - */ - _bindEvents: function() { - this._onMouseDown = this._onMouseDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); - this._onMouseUp = this._onMouseUp.bind(this); - this._onResize = this._onResize.bind(this); - this._onGesture = this._onGesture.bind(this); - this._onDrag = this._onDrag.bind(this); - this._onShake = this._onShake.bind(this); - this._onLongPress = this._onLongPress.bind(this); - this._onOrientationChange = this._onOrientationChange.bind(this); - this._onMouseWheel = this._onMouseWheel.bind(this); - }, - - /** - * Removes all event listeners - */ - removeListeners: function() { - removeListener(fabric.window, 'resize', this._onResize); - - removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); - - removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); - removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (typeof eventjs !== 'undefined' && 'remove' in eventjs) { - eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture); - eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag); - eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange); - eventjs.remove(this.upperCanvasEl, 'shake', this._onShake); - eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress); - } - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js gesture - * @param {Event} [self] Inner Event object - */ - _onGesture: function(e, self) { - this.__onTransformGesture && this.__onTransformGesture(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js drag - * @param {Event} [self] Inner Event object - */ - _onDrag: function(e, self) { - this.__onDrag && this.__onDrag(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js wheel event - * @param {Event} [self] Inner Event object - */ - _onMouseWheel: function(e, self) { - this.__onMouseWheel && this.__onMouseWheel(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js orientation change - * @param {Event} [self] Inner Event object - */ - _onOrientationChange: function(e, self) { - this.__onOrientationChange && this.__onOrientationChange(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js shake - * @param {Event} [self] Inner Event object - */ - _onShake: function(e, self) { - this.__onShake && this.__onShake(e, self); - }, - /** - * @private - * @param {Event} [e] Event object fired on Event.js shake - * @param {Event} [self] Inner Event object - */ - _onLongPress: function(e, self) { - this.__onLongPress && this.__onLongPress(e, self); - }, - - /** - * @private - * @param {Event} e Event object fired on mousedown - */ - _onMouseDown: function (e) { - this.__onMouseDown(e); - - addListener(fabric.document, 'touchend', this._onMouseUp); - addListener(fabric.document, 'touchmove', this._onMouseMove); - - removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (e.type === 'touchstart') { - // Unbind mousedown to prevent double triggers from touch 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 }); - - var target = this.findTarget(e); - if (typeof target !== 'undefined') { - target.fire('mousedown', { e: e, target: target }); - } - }, - - /** - * @private - * @param {Event} e Event object fired on mousemove - */ - _onMouseMoveInDrawingMode: function(e) { - if (this._isCurrentlyDrawing) { - var ivt = fabric.util.invertTransform(this.viewportTransform), - pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); - this.freeDrawingBrush.onMouseMove(pointer); - } - this.setCursor(this.freeDrawingCursor); - this.fire('mouse:move', { e: e }); - - var target = this.findTarget(e); - if (typeof target !== 'undefined') { - target.fire('mousemove', { e: e, target: target }); - } - }, - - /** - * @private - * @param {Event} e Event object fired on mouseup - */ - _onMouseUpInDrawingMode: function(e) { - this._isCurrentlyDrawing = false; - if (this.clipTo) { - this.contextTop.restore(); - } - this.freeDrawingBrush.onMouseUp(); - this.fire('mouse:up', { e: e }); - - var target = this.findTarget(e); - if (typeof target !== 'undefined') { - target.fire('mouseup', { e: e, target: target }); - } - }, - - /** - * Method that defines the actions when mouse is clic ked on canvas. - * The method inits the currentTransform parameters and renders all the - * canvas so the current image can be placed on the top canvas and the rest - * in on the container one. - * @private - * @param {Event} e Event object fired on mousedown - */ - __onMouseDown: function (e) { - - // accept only left 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) { - this.stateful && target.saveState(); - - // determine if it's a drag or rotate case - if (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 + toObject: function() { + var source; + if (typeof this.source === "function") { + source = String(this.source); + } else if (typeof this.source.src === "string") { + source = this.source.src; + } + return { + source: source, + repeat: this.repeat, + offsetX: this.offsetX, + offsetY: this.offsetY }; - } }, - - /** - * @private - * @param {Object} target Object for that origin is set to center - */ - _setOriginToCenter: function(target) { - this._previousOriginX = this._currentTransform.target.originX; - this._previousOriginY = this._currentTransform.target.originY; - - var center = target.getCenterPoint(); - - target.originX = 'center'; - target.originY = 'center'; - - target.left = center.x; - target.top = center.y; - - this._currentTransform.left = target.left; - this._currentTransform.top = target.top; - }, - - /** - * @private - * @param {Object} target Object for that center is set to origin - */ - _setCenterToOrigin: function(target) { - var originPoint = target.translateToOriginPoint( - target.getCenterPoint(), - this._previousOriginX, - this._previousOriginY); - - target.originX = this._previousOriginX; - target.originY = this._previousOriginY; - - target.left = originPoint.x; - target.top = originPoint.y; - - this._previousOriginX = null; - this._previousOriginY = null; - }, - - /** - * Method that defines the actions when mouse is hovering the canvas. - * The currentTransform parameter will definde whether the user is rotating/scaling/translating - * an image or neither of them (only hovering). A group selection is also possible and would cancel - * all any other type of action. - * In case of an image transformation only the top canvas will be rendered. - * @private - * @param {Event} e Event object fired on mousemove - */ - __onMouseMove: function (e) { - - var target, pointer; - - if (this.isDrawingMode) { - this._onMouseMoveInDrawingMode(e); - return; - } - if (typeof e.touches !== 'undefined' && e.touches.length > 1) { - return; - } - - var groupSelector = this._groupSelector; - - // We initially clicked in an empty area, so we draw a box for multiple 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); + toSVG: function(object) { + var patternSource = typeof this.source === "function" ? this.source() : this.source, patternWidth = patternSource.width / object.getWidth(), patternHeight = patternSource.height / object.getHeight(), patternOffsetX = this.offsetX / object.getWidth(), patternOffsetY = this.offsetY / object.getHeight(), patternImgSrc = ""; + if (this.repeat === "repeat-x" || this.repeat === "no-repeat") { + patternHeight = 1; } - else { - this._setCursorFromEvent(e, target); + if (this.repeat === "repeat-y" || this.repeat === "no-repeat") { + patternWidth = 1; } - } - 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; + if (patternSource.src) { + patternImgSrc = patternSource.src; + } else if (patternSource.toDataURL) { + patternImgSrc = patternSource.toDataURL(); } - } + return '\n' + '\n' + "\n"; }, - - /** - * @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); + toLive: function(ctx) { + var source = typeof this.source === "function" ? this.source() : this.source; + if (!source) { + return ""; } - - 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); + if (typeof source.src !== "undefined") { + if (!source.complete) { + return ""; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ""; + } } - 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]; + return ctx.createPattern(source, this.repeat); } - }); -})(); - - -(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, { - 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(), { - 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 */ { - - /** - * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately - * @param {Object} [options] Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} - * @example Generate jpeg dataURL with lower quality - * var dataURL = canvas.toDataURL({ - * format: 'jpeg', - * quality: 0.8 - * }); - * @example Generate cropped png dataURL (clipping of canvas) - * var dataURL = canvas.toDataURL({ - * format: 'png', - * left: 100, - * top: 100, - * width: 200, - * height: 200 - * }); - * @example Generate double scaled png dataURL - * var dataURL = canvas.toDataURL({ - * format: 'png', - * multiplier: 2 - * }); - */ - toDataURL: function (options) { - options || (options = { }); - - var format = options.format || 'png', - quality = options.quality || 1, - multiplier = options.multiplier || 1, - cropping = { - left: options.left, - top: options.top, - width: options.width, - height: options.height - }; - - if (multiplier !== 1) { - return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); - } - else { - return this.__toDataURL(format, quality, cropping); - } - }, - - /** - * @private - */ - __toDataURL: function(format, quality, cropping) { - - this.renderAll(true); - - var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, - croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); - - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (format === 'jpg') { - format = 'jpeg'; - } - - var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) - ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) - : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - - if (croppedCanvasEl) { - croppedCanvasEl = null; - } - - return data; - }, - - /** - * @private - */ - __getCroppedCanvas: function(canvasEl, cropping) { - - var croppedCanvasEl, - croppedCtx, - shouldCrop = 'left' in cropping || - 'top' in cropping || - 'width' in cropping || - 'height' in cropping; - - if (shouldCrop) { - - croppedCanvasEl = fabric.util.createCanvasElement(); - croppedCtx = croppedCanvasEl.getContext('2d'); - - croppedCanvasEl.width = cropping.width || this.width; - croppedCanvasEl.height = cropping.height || this.height; - - croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); - } - - return croppedCanvasEl; - }, - - /** - * @private - */ - __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { - - var origWidth = this.getWidth(), - origHeight = this.getHeight(), - scaledWidth = origWidth * multiplier, - scaledHeight = origHeight * multiplier, - activeObject = this.getActiveObject(), - activeGroup = this.getActiveGroup(), - - ctx = this.contextTop || this.contextContainer; - - if (multiplier > 1) { - this.setWidth(scaledWidth).setHeight(scaledHeight); - } - ctx.scale(multiplier, multiplier); - - if (cropping.left) { - cropping.left *= multiplier; - } - if (cropping.top) { - cropping.top *= multiplier; - } - if (cropping.width) { - cropping.width *= multiplier; - } - else if (multiplier < 1) { - cropping.width = scaledWidth; - } - if (cropping.height) { - cropping.height *= multiplier; - } - else if (multiplier < 1) { - cropping.height = scaledHeight; - } - - if (activeGroup) { - // not removing group due to complications with restoring it with correct state afterwords - this._tempRemoveBordersControlsFromGroup(activeGroup); - } - else if (activeObject && this.deactivateAll) { - this.deactivateAll(); - } - - this.renderAll(true); - - var data = this.__toDataURL(format, quality, cropping); - - // restoring width, height for `renderAll` to draw - // background properly (while context is scaled) - this.width = origWidth; - this.height = origHeight; - - ctx.scale(1 / multiplier, 1 / multiplier); - this.setWidth(origWidth).setHeight(origHeight); - - if (activeGroup) { - this._restoreBordersControlsOnGroup(activeGroup); - } - else if (activeObject && this.setActiveObject) { - this.setActiveObject(activeObject); - } - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - - return data; - }, - - /** - * Exports canvas element to a dataurl image (allowing to change image size via multiplier). - * @deprecated since 1.0.13 - * @param {String} format (png|jpeg) - * @param {Number} multiplier - * @param {Number} quality (0..1) - * @return {String} - */ - toDataURLWithMultiplier: function (format, multiplier, quality) { - return this.toDataURL({ - format: format, - multiplier: multiplier, - quality: quality - }); - }, - - /** - * @private - */ - _tempRemoveBordersControlsFromGroup: function(group) { - group.origHasControls = group.hasControls; - group.origBorderColor = group.borderColor; - - group.hasControls = true; - group.borderColor = 'rgba(0,0,0,0)'; - - group.forEachObject(function(o) { - o.origBorderColor = o.borderColor; - o.borderColor = 'rgba(0,0,0,0)'; - }); - }, - - /** - * @private - */ - _restoreBordersControlsOnGroup: function(group) { - group.hideControls = group.origHideControls; - group.borderColor = group.origBorderColor; - - group.forEachObject(function(o) { - o.borderColor = o.origBorderColor; - delete o.origBorderColor; - }); - } }); - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Populates canvas with data from the specified dataless JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} - * @deprecated since 1.2.2 - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} - */ - loadFromDatalessJSON: function (json, callback, reviver) { - return this.loadFromJSON(json, callback, reviver); - }, - - /** - * Populates canvas with data from the specified JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toJSON} - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} - * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} - * @example loadFromJSON - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); - * @example loadFromJSON with reviver - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { - * // `o` = json object - * // `object` = fabric.Object instance - * // ... do some stuff ... - * }); - */ - loadFromJSON: function (json, callback, reviver) { - if (!json) { - return; - } - - // serialize if it wasn't already - var serialized = (typeof json === 'string') - ? JSON.parse(json) - : json; - - this.clear(); - - var _this = this; - this._enlivenObjects(serialized.objects, function () { - _this._setBgOverlay(serialized, callback); - }, reviver); - - return this; - }, - - /** - * @private - * @param {Object} serialized Object with background and overlay information - * @param {Function} callback Invoked after all background and overlay images/patterns loaded - */ - _setBgOverlay: function(serialized, callback) { - var _this = this, - loaded = { - backgroundColor: false, - overlayColor: false, - backgroundImage: false, - overlayImage: false - }; - - if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { - callback && callback(); - return; - } - - var cbIfLoaded = function () { - if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { - _this.renderAll(); - callback && callback(); - } - }; - - this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); - this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); - this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); - this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); - - cbIfLoaded(); - }, - - /** - * @private - * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) - * @param {(Object|String)} value Value to set - * @param {Object} loaded Set loaded property to true if property is set - * @param {Object} callback Callback function to invoke after property is set - */ - __setBgOverlay: function(property, value, loaded, callback) { - var _this = this; - - if (!value) { - loaded[property] = true; - return; - } - - if (property === 'backgroundImage' || property === 'overlayImage') { - fabric.Image.fromObject(value, function(img) { - _this[property] = img; - loaded[property] = true; - callback && callback(); - }); - } - else { - this['set' + fabric.util.string.capitalize(property, true)](value, function() { - loaded[property] = true; - callback && callback(); - }); - } - }, - - /** - * @private - * @param {Array} objects - * @param {Function} callback - * @param {Function} [reviver] - */ - _enlivenObjects: function (objects, callback, reviver) { - var _this = this; - - if (!objects || objects.length === 0) { - callback && callback(); - return; - } - - var renderOnAddRemove = this.renderOnAddRemove; - this.renderOnAddRemove = false; - - fabric.util.enlivenObjects(objects, function(enlivenedObjects) { - enlivenedObjects.forEach(function(obj, index) { - _this.insertAt(obj, index, true); - }); - - _this.renderOnAddRemove = renderOnAddRemove; - callback && callback(); - }, null, reviver); - }, - - /** - * @private - * @param {String} format - * @param {Function} callback - */ - _toDataURL: function (format, callback) { - this.clone(function (clone) { - callback(clone.toDataURL(format)); - }); - }, - - /** - * @private - * @param {String} format - * @param {Number} multiplier - * @param {Function} callback - */ - _toDataURLWithMultiplier: function (format, multiplier, callback) { - this.clone(function (clone) { - callback(clone.toDataURLWithMultiplier(format, multiplier)); - }); - }, - - /** - * Clones canvas instance - * @param {Object} [callback] Receives cloned instance as a first argument - * @param {Array} [properties] Array of properties to include in the cloned canvas and children - */ - clone: function (callback, properties) { - var data = JSON.stringify(this.toJSON(properties)); - this.cloneWithoutData(function(clone) { - clone.loadFromJSON(data, function() { - callback && callback(clone); - }); - }); - }, - - /** - * Clones canvas instance without cloning existing data. - * This essentially copies canvas dimensions, clipping properties, etc. - * but leaves data empty (so that you can populate it with your own) - * @param {Object} [callback] Receives cloned instance as a first argument - */ - cloneWithoutData: function(callback) { - var el = fabric.document.createElement('canvas'); - - el.width = this.getWidth(); - el.height = this.getHeight(); - - var clone = new fabric.Canvas(el); - clone.clipTo = this.clipTo; - if (this.backgroundImage) { - clone.setBackgroundImage(this.backgroundImage.src, function() { - clone.renderAll(); - callback && callback(clone); - }); - clone.backgroundImageOpacity = this.backgroundImageOpacity; - clone.backgroundImageStretch = this.backgroundImageStretch; - } - else { - callback && callback(clone); - } - } -}); - - (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - toFixed = fabric.util.toFixed, - capitalize = fabric.util.string.capitalize, - degreesToRadians = fabric.util.degreesToRadians, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Object) { - return; - } - - /** - * Root object class from which all 2d shape classes inherit from - * @class fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} - * @see {@link fabric.Object#initialize} for constructor definition - * - * @fires added - * @fires removed - * - * @fires selected - * @fires modified - * @fires rotating - * @fires scaling - * @fires moving - * - * @fires mousedown - * @fires mouseup - */ - fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { - - /** - * Retrieves object's {@link fabric.Object#clipTo|clipping function} - * @method getClipTo - * @memberOf fabric.Object.prototype - * @return {Function} - */ - - /** - * Sets object's {@link fabric.Object#clipTo|clipping function} - * @method setClipTo - * @memberOf fabric.Object.prototype - * @param {Function} clipTo Clipping function - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method getTransformMatrix - * @memberOf fabric.Object.prototype - * @return {Array} transformMatrix - */ - - /** - * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method setTransformMatrix - * @memberOf fabric.Object.prototype - * @param {Array} transformMatrix - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#visible|visible} state - * @method getVisible - * @memberOf fabric.Object.prototype - * @return {Boolean} True if visible - */ - - /** - * Sets object's {@link fabric.Object#visible|visible} state - * @method setVisible - * @memberOf fabric.Object.prototype - * @param {Boolean} value visible value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#shadow|shadow} - * @method getShadow - * @memberOf fabric.Object.prototype - * @return {Object} Shadow instance - */ - - /** - * Retrieves object's {@link fabric.Object#stroke|stroke} - * @method getStroke - * @memberOf fabric.Object.prototype - * @return {String} stroke value - */ - - /** - * Sets object's {@link fabric.Object#stroke|stroke} - * @method setStroke - * @memberOf fabric.Object.prototype - * @param {String} value stroke value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method getStrokeWidth - * @memberOf fabric.Object.prototype - * @return {Number} strokeWidth value - */ - - /** - * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method setStrokeWidth - * @memberOf fabric.Object.prototype - * @param {Number} value strokeWidth value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originX|originX} - * @method getOriginX - * @memberOf fabric.Object.prototype - * @return {String} originX value - */ - - /** - * Sets object's {@link fabric.Object#originX|originX} - * @method setOriginX - * @memberOf fabric.Object.prototype - * @param {String} value originX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originY|originY} - * @method getOriginY - * @memberOf fabric.Object.prototype - * @return {String} originY value - */ - - /** - * Sets object's {@link fabric.Object#originY|originY} - * @method setOriginY - * @memberOf fabric.Object.prototype - * @param {String} value originY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#fill|fill} - * @method getFill - * @memberOf fabric.Object.prototype - * @return {String} Fill value - */ - - /** - * Sets object's {@link fabric.Object#fill|fill} - * @method setFill - * @memberOf fabric.Object.prototype - * @param {String} value Fill value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#opacity|opacity} - * @method getOpacity - * @memberOf fabric.Object.prototype - * @return {Number} Opacity value (0-1) - */ - - /** - * Sets object's {@link fabric.Object#opacity|opacity} - * @method setOpacity - * @memberOf fabric.Object.prototype - * @param {Number} value Opacity value (0-1) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) - * @method getAngle - * @memberOf fabric.Object.prototype - * @return {Number} - */ - - /** - * Retrieves object's {@link fabric.Object#top|top position} - * @method getTop - * @memberOf fabric.Object.prototype - * @return {Number} Top value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#top|top position} - * @method setTop - * @memberOf fabric.Object.prototype - * @param {Number} value Top value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#left|left position} - * @method getLeft - * @memberOf fabric.Object.prototype - * @return {Number} Left value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#left|left position} - * @method setLeft - * @memberOf fabric.Object.prototype - * @param {Number} value Left value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleX|scaleX} value - * @method getScaleX - * @memberOf fabric.Object.prototype - * @return {Number} scaleX value - */ - - /** - * Sets object's {@link fabric.Object#scaleX|scaleX} value - * @method setScaleX - * @memberOf fabric.Object.prototype - * @param {Number} value scaleX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleY|scaleY} value - * @method getScaleY - * @memberOf fabric.Object.prototype - * @return {Number} scaleY value - */ - - /** - * Sets object's {@link fabric.Object#scaleY|scaleY} value - * @method setScaleY - * @memberOf fabric.Object.prototype - * @param {Number} value scaleY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipX|flipX} value - * @method getFlipX - * @memberOf fabric.Object.prototype - * @return {Boolean} flipX value - */ - - /** - * Sets object's {@link fabric.Object#flipX|flipX} value - * @method setFlipX - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipY|flipY} value - * @method getFlipY - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value - */ - - /** - * Sets object's {@link fabric.Object#flipY|flipY} value - * @method setFlipY - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Type of an object (rect, circle, path, etc.). - * Note that this property is meant to be read-only and not meant to be modified. - * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly. - * @type String - * @default - */ - type: 'object', - - /** - * Horizontal origin of transformation of an object (one of "left", "right", "center") - * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups - * @type String - * @default - */ - originX: 'left', - - /** - * Vertical origin of transformation of an object (one of "top", "bottom", "center") - * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups - * @type String - * @default - */ - originY: 'top', - - /** - * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} - * @type Number - * @default - */ - top: 0, - - /** - * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} - * @type Number - * @default - */ - left: 0, - - /** - * Object width - * @type Number - * @default - */ - width: 0, - - /** - * Object height - * @type Number - * @default - */ - height: 0, - - /** - * Object scale factor (horizontal) - * @type Number - * @default - */ - scaleX: 1, - - /** - * Object scale factor (vertical) - * @type Number - * @default - */ - scaleY: 1, - - /** - * When true, an object is rendered as flipped horizontally - * @type Boolean - * @default - */ - flipX: false, - - /** - * When true, an object is rendered as flipped vertically - * @type Boolean - * @default - */ - flipY: false, - - /** - * Opacity of an object - * @type Number - * @default - */ - opacity: 1, - - /** - * Angle of rotation of an object (in degrees) - * @type Number - * @default - */ - angle: 0, - - /** - * Size of object's controlling corners (in pixels) - * @type Number - * @default - */ - cornerSize: 12, - - /** - * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) - * @type Boolean - * @default - */ - transparentCorners: true, - - /** - * Default cursor value used when hovering over this object on canvas - * @type String - * @default - */ - hoverCursor: null, - - /** - * Padding between object and its controlling borders (in pixels) - * @type Number - * @default - */ - padding: 0, - - /** - * Color of controlling borders of an object (when it's active) - * @type String - * @default - */ - borderColor: 'rgba(102,153,255,0.75)', - - /** - * Color of controlling corners of an object (when it's active) - * @type String - * @default - */ - cornerColor: 'rgba(102,153,255,0.5)', - - /** - * When true, this object will use center point as the origin of transformation - * when being scaled via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredScaling: false, - - /** - * When true, this object will use center point as the origin of transformation - * when being rotated via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredRotation: true, - - /** - * Color of object's fill - * @type String - * @default - */ - fill: 'rgb(0,0,0)', - - /** - * Fill rule used to fill an object - * accepted values are nonzero, evenodd - * Backwards incompatibility note: This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) - * @type String - * @default - */ - fillRule: 'nonzero', - - /** - * Composite rule used for canvas globalCompositeOperation - * @type String - * @default - */ - globalCompositeOperation: 'source-over', - - /** - * Background color of an object. Only works with text objects at the moment. - * @type String - * @default - */ - backgroundColor: '', - - /** - * When defined, an object is rendered via stroke and this property specifies its color - * @type String - * @default - */ - stroke: null, - - /** - * Width of a stroke used to render this object - * @type Number - * @default - */ - strokeWidth: 1, - - /** - * Array specifying dash pattern of an object's stroke (stroke must be defined) - * @type Array - */ - strokeDashArray: null, - - /** - * Line endings style of an object's stroke (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'butt', - - /** - * Corner style of an object's stroke (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'miter', - - /** - * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke - * @type Number - * @default - */ - strokeMiterLimit: 10, - - /** - * Shadow object representing shadow of this shape - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Opacity of object's controlling borders when object is active and moving - * @type Number - * @default - */ - borderOpacityWhenMoving: 0.4, - - /** - * Scale factor of object's controlling borders - * @type Number - * @default - */ - borderScaleFactor: 1, - - /** - * Transform matrix (similar to SVG's transform matrix) - * @type Array - */ - transformMatrix: null, - - /** - * Minimum allowed scale value of an object - * @type Number - * @default - */ - minScaleLimit: 0.01, - - /** - * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). - * But events still fire on it. - * @type Boolean - * @default - */ - selectable: true, - - /** - * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 - * @type Boolean - * @default - */ - evented: true, - - /** - * When set to `false`, an object is not rendered on canvas - * @type Boolean - * @default - */ - visible: true, - - /** - * When set to `false`, object's controls are not displayed and can not be used to manipulate object - * @type Boolean - * @default - */ - hasControls: true, - - /** - * When set to `false`, object's controlling borders are not rendered - * @type Boolean - * @default - */ - hasBorders: true, - - /** - * When set to `false`, object's controlling rotating point will not be visible or selectable - * @type Boolean - * @default - */ - hasRotatingPoint: true, - - /** - * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) - * @type Number - * @default - */ - rotatingPointOffset: 40, - - /** - * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box - * @type Boolean - * @default - */ - perPixelTargetFind: false, - - /** - * When `false`, default object's values are not included in its serialization - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Function that determines clipping of an object (context is passed as a first argument) - * Note that context origin is at the object's center point (not left/top corner) - * @type Function - */ - clipTo: null, - - /** - * When `true`, object horizontal movement is locked - * @type Boolean - * @default - */ - lockMovementX: false, - - /** - * When `true`, object vertical movement is locked - * @type Boolean - * @default - */ - lockMovementY: false, - - /** - * When `true`, object rotation is locked - * @type Boolean - * @default - */ - lockRotation: false, - - /** - * When `true`, object horizontal scaling is locked - * @type Boolean - * @default - */ - lockScalingX: false, - - /** - * When `true`, object vertical scaling is locked - * @type Boolean - * @default - */ - lockScalingY: false, - - /** - * When `true`, object non-uniform scaling is locked - * @type Boolean - * @default - */ - lockUniScaling: false, - - /** - * When `true`, object cannot be flipped by scaling into negative values - * @type Boolean - * @default - */ - - lockScalingFlip: false, - /** - * List of properties to consider when checking if state - * of an object is changed (fabric.Object#hasStateChanged) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: ( - 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + - 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + - 'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor' - ).split(' '), - - /** - * Constructor - * @param {Object} [options] Options object - */ - initialize: function(options) { - if (options) { - this.setOptions(options); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initGradient: function(options) { - if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { - this.set('fill', new fabric.Gradient(options.fill)); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initPattern: function(options) { - if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { - this.set('fill', new fabric.Pattern(options.fill)); - } - if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { - this.set('stroke', new fabric.Pattern(options.stroke)); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initClipping: function(options) { - if (!options.clipTo || typeof options.clipTo !== 'string') { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), toFixed = fabric.util.toFixed; + if (fabric.Shadow) { + fabric.warn("fabric.Shadow is already defined."); return; - } - - var functionBody = fabric.util.getFunctionBody(options.clipTo); - if (typeof functionBody !== 'undefined') { - this.clipTo = new Function('ctx', functionBody); - } - }, - - /** - * Sets object's properties from options - * @param {Object} [options] Options object - */ - setOptions: function(options) { - for (var prop in options) { - this.set(prop, options[prop]); - } - this._initGradient(options); - this._initPattern(options); - this._initClipping(options); - }, - - /** - * Transforms context when rendering an object - * @param {CanvasRenderingContext2D} ctx Context - * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node - */ - transform: function(ctx, fromLeft) { - var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - ctx.scale( - this.scaleX * (this.flipX ? -1 : 1), - this.scaleY * (this.flipY ? -1 : 1) - ); - }, - - /** - * Returns an object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - object = { - type: this.type, - originX: this.originX, - originY: this.originY, - left: toFixed(this.left, NUM_FRACTION_DIGITS), - top: toFixed(this.top, NUM_FRACTION_DIGITS), - width: toFixed(this.width, NUM_FRACTION_DIGITS), - height: toFixed(this.height, NUM_FRACTION_DIGITS), - fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, - stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), - strokeDashArray: this.strokeDashArray, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), - scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), - scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), - angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), - flipX: this.flipX, - flipY: this.flipY, - opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), - shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, - visible: this.visible, - clipTo: this.clipTo && String(this.clipTo), - backgroundColor: this.backgroundColor, - fillRule: this.fillRule, - globalCompositeOperation: this.globalCompositeOperation - }; - - if (!this.includeDefaultValues) { - object = this._removeDefaultValues(object); - } - - fabric.util.populateWithProperties(this, object, propertiesToInclude); - - return object; - }, - - /** - * Returns (dataless) object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - // will be overwritten by subclasses - return this.toObject(propertiesToInclude); - }, - - /** - * @private - * @param {Object} object - */ - _removeDefaultValues: function(object) { - var prototype = fabric.util.getKlass(object.type).prototype, - stateProperties = prototype.stateProperties; - - stateProperties.forEach(function(prop) { - if (object[prop] === prototype[prop]) { - delete object[prop]; + } + fabric.Shadow = fabric.util.createClass({ + color: "rgb(0,0,0)", + blur: 0, + offsetX: 0, + offsetY: 0, + affectStroke: false, + includeDefaultValues: true, + initialize: function(options) { + if (typeof options === "string") { + options = this._parseShadow(options); + } + for (var prop in options) { + this[prop] = options[prop]; + } + this.id = fabric.Object.__uid++; + }, + _parseShadow: function(shadow) { + var shadowStr = shadow.trim(), offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, "") || "rgb(0,0,0)"; + return { + color: color.trim(), + offsetX: parseInt(offsetsAndBlur[1], 10) || 0, + offsetY: parseInt(offsetsAndBlur[2], 10) || 0, + blur: parseInt(offsetsAndBlur[3], 10) || 0 + }; + }, + toString: function() { + return [ this.offsetX, this.offsetY, this.blur, this.color ].join("px "); + }, + toSVG: function(object) { + var mode = "SourceAlpha", fBoxX = 40, fBoxY = 40; + if (object && (object.fill === this.color || object.stroke === this.color)) { + mode = "SourceGraphic"; + } + if (object.width && object.height) { + fBoxX = toFixed(Math.abs(this.offsetX / object.getWidth()), 2) * 100 + 20; + fBoxY = toFixed(Math.abs(this.offsetY / object.getHeight()), 2) * 100 + 20; + } + return '\n" + ' \n' + ' \n' + ' \n' + " \n" + " \n" + ' \n' + " \n" + "\n"; + }, + toObject: function() { + if (this.includeDefaultValues) { + return { + color: this.color, + blur: this.blur, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + } + var obj = {}, proto = fabric.Shadow.prototype; + if (this.color !== proto.color) { + obj.color = this.color; + } + if (this.blur !== proto.blur) { + obj.blur = this.blur; + } + if (this.offsetX !== proto.offsetX) { + obj.offsetX = this.offsetX; + } + if (this.offsetY !== proto.offsetY) { + obj.offsetY = this.offsetY; + } + return obj; } - }); + }); + fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; +})(typeof exports !== "undefined" ? exports : this); - return object; - }, - - /** - * Returns a string representation of an instance - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Basic getter - * @param {String} property Property name - * @return {Any} value of a property - */ - get: function(property) { - return this[property]; - }, - - /** - * @private - */ - _setObject: function(obj) { - for (var prop in obj) { - this._set(prop, obj[prop]); - } - }, - - /** - * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. - * @param {String|Object} key Property name or object (if object, iterate over the object properties) - * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) - * @return {fabric.Object} thisArg - * @chainable - */ - set: function(key, value) { - if (typeof key === 'object') { - this._setObject(key); - } - else { - if (typeof value === 'function' && key !== 'clipTo') { - this._set(key, value(this.get(key))); - } - else { - this._set(key, value); - } - } - return this; - }, - - /** - * @private - * @param {String} key - * @param {Any} value - * @return {fabric.Object} thisArg - */ - _set: function(key, value) { - var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); - - if (shouldConstrainValue) { - value = this._constrainScale(value); - } - if (key === 'scaleX' && value < 0) { - this.flipX = !this.flipX; - value *= -1; - } - else if (key === 'scaleY' && value < 0) { - this.flipY = !this.flipY; - value *= -1; - } - else if (key === 'width' || key === 'height') { - this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); - } - else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { - value = new fabric.Shadow(value); - } - - this[key] = value; - - return this; - }, - - /** - * Toggles specified property from `true` to `false` or from `false` to `true` - * @param {String} property Property to toggle - * @return {fabric.Object} thisArg - * @chainable - */ - toggle: function(property) { - var value = this.get(property); - if (typeof value === 'boolean') { - this.set(property, !value); - } - return this; - }, - - /** - * Sets sourcePath of an object - * @param {String} value Value to set sourcePath to - * @return {fabric.Object} thisArg - * @chainable - */ - setSourcePath: function(value) { - this.sourcePath = value; - return this; - }, - - /** - * Retrieves viewportTransform from Object's canvas if possible - * @method getViewportTransform - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value // TODO - */ - getViewportTransform: function() { - if (this.canvas && this.canvas.viewportTransform) { - return this.canvas.viewportTransform; - } - return [1, 0, 0, 1, 0, 0]; - }, - - /** - * Renders an object on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if width/height are zeros or object is not visible - if ((this.width === 0 && this.height === 0) || !this.visible) { +(function() { + "use strict"; + if (fabric.StaticCanvas) { + fabric.warn("fabric.StaticCanvas is already defined."); return; - } + } + var extend = fabric.util.object.extend, getElementOffset = fabric.util.getElementOffset, removeFromArray = fabric.util.removeFromArray, CANVAS_INIT_ERROR = new Error("Could not initialize `canvas` element"); + fabric.StaticCanvas = fabric.util.createClass({ + initialize: function(el, options) { + options || (options = {}); + this._initStatic(el, options); + fabric.StaticCanvas.activeInstance = this; + }, + backgroundColor: "", + backgroundImage: null, + overlayColor: "", + overlayImage: null, + includeDefaultValues: true, + stateful: true, + renderOnAddRemove: true, + clipTo: null, + controlsAboveOverlay: false, + allowTouchScrolling: false, + imageSmoothingEnabled: true, + preserveObjectStacking: false, + viewportTransform: [ 1, 0, 0, 1, 0, 0 ], + onBeforeScaleRotate: function() {}, + _initStatic: function(el, options) { + this._objects = []; + this._createLowerCanvas(el); + this._initOptions(options); + this._setImageSmoothing(); + if (options.overlayImage) { + this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); + } + if (options.backgroundImage) { + this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); + } + if (options.backgroundColor) { + this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); + } + if (options.overlayColor) { + this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); + } + this.calcOffset(); + }, + calcOffset: function() { + this._offset = getElementOffset(this.lowerCanvasEl); + return this; + }, + setOverlayImage: function(image, callback, options) { + return this.__setBgOverlayImage("overlayImage", image, callback, options); + }, + setBackgroundImage: function(image, callback, options) { + return this.__setBgOverlayImage("backgroundImage", image, callback, options); + }, + setOverlayColor: function(overlayColor, callback) { + return this.__setBgOverlayColor("overlayColor", overlayColor, callback); + }, + setBackgroundColor: function(backgroundColor, callback) { + return this.__setBgOverlayColor("backgroundColor", backgroundColor, callback); + }, + _setImageSmoothing: function() { + var ctx = this.getContext(); + ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; + }, + __setBgOverlayImage: function(property, image, callback, options) { + if (typeof image === "string") { + fabric.util.loadImage(image, function(img) { + this[property] = new fabric.Image(img, options); + callback && callback(); + }, this, options && options.crossOrigin); + } else { + options && image.setOptions(options); + this[property] = image; + callback && callback(); + } + return this; + }, + __setBgOverlayColor: function(property, color, callback) { + if (color && color.source) { + var _this = this; + fabric.util.loadImage(color.source, function(img) { + _this[property] = new fabric.Pattern({ + source: img, + repeat: color.repeat, + offsetX: color.offsetX, + offsetY: color.offsetY + }); + callback && callback(); + }); + } else { + this[property] = color; + callback && callback(); + } + return this; + }, + _createCanvasElement: function() { + var element = fabric.document.createElement("canvas"); + if (!element.style) { + element.style = {}; + } + if (!element) { + throw CANVAS_INIT_ERROR; + } + this._initCanvasElement(element); + return element; + }, + _initCanvasElement: function(element) { + fabric.util.createCanvasElement(element); + if (typeof element.getContext === "undefined") { + throw CANVAS_INIT_ERROR; + } + }, + _initOptions: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; + if (!this.lowerCanvasEl.style) { + return; + } + this.lowerCanvasEl.width = this.width; + this.lowerCanvasEl.height = this.height; + this.lowerCanvasEl.style.width = this.width + "px"; + this.lowerCanvasEl.style.height = this.height + "px"; + this.viewportTransform = this.viewportTransform.slice(); + }, + _createLowerCanvas: function(canvasEl) { + this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); + this._initCanvasElement(this.lowerCanvasEl); + fabric.util.addClass(this.lowerCanvasEl, "lower-canvas"); + if (this.interactive) { + this._applyCanvasStyle(this.lowerCanvasEl); + } + this.contextContainer = this.lowerCanvasEl.getContext("2d"); + }, + getWidth: function() { + return this.width; + }, + getHeight: function() { + return this.height; + }, + setWidth: function(value, options) { + return this.setDimensions({ + width: value + }, options); + }, + setHeight: function(value, options) { + return this.setDimensions({ + height: value + }, options); + }, + setDimensions: function(dimensions, options) { + var cssValue; + options = options || {}; + for (var prop in dimensions) { + cssValue = dimensions[prop]; + if (!options.cssOnly) { + this._setBackstoreDimension(prop, dimensions[prop]); + cssValue += "px"; + } + if (!options.backstoreOnly) { + this._setCssDimension(prop, cssValue); + } + } + if (!options.cssOnly) { + this.renderAll(); + } + this.calcOffset(); + return this; + }, + _setBackstoreDimension: function(prop, value) { + this.lowerCanvasEl[prop] = value; + if (this.upperCanvasEl) { + this.upperCanvasEl[prop] = value; + } + if (this.cacheCanvasEl) { + this.cacheCanvasEl[prop] = value; + } + this[prop] = value; + return this; + }, + _setCssDimension: function(prop, value) { + this.lowerCanvasEl.style[prop] = value; + if (this.upperCanvasEl) { + this.upperCanvasEl.style[prop] = value; + } + if (this.wrapperEl) { + this.wrapperEl.style[prop] = value; + } + return this; + }, + getZoom: function() { + return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + }, + setViewportTransform: function(vpt) { + var activeGroup = this.getActiveGroup(); + this.viewportTransform = vpt; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + if (activeGroup) { + activeGroup.setCoords(); + } + return this; + }, + zoomToPoint: function(point, value) { + var before = point; + point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + var after = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[4] += before.x - after.x; + this.viewportTransform[5] += before.y - after.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + setZoom: function(value) { + this.zoomToPoint(new fabric.Point(0, 0), value); + return this; + }, + absolutePan: function(point) { + this.viewportTransform[4] = -point.x; + this.viewportTransform[5] = -point.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + relativePan: function(point) { + return this.absolutePan(new fabric.Point(-point.x - this.viewportTransform[4], -point.y - this.viewportTransform[5])); + }, + getElement: function() { + return this.lowerCanvasEl; + }, + getActiveObject: function() { + return null; + }, + getActiveGroup: function() { + return null; + }, + _draw: function(ctx, object) { + if (!object) { + return; + } + ctx.save(); + var v = this.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (this._shouldRenderObject(object)) { + object.render(ctx); + } + ctx.restore(); + if (!this.controlsAboveOverlay) { + object._renderControls(ctx); + } + }, + _shouldRenderObject: function(object) { + if (!object) { + return false; + } + return object !== this.getActiveGroup() || !this.preserveObjectStacking; + }, + _onObjectAdded: function(obj) { + this.stateful && obj.setupState(); + obj.canvas = this; + obj.setCoords(); + this.fire("object:added", { + target: obj + }); + obj.fire("added"); + }, + _onObjectRemoved: function(obj) { + if (this.getActiveObject() === obj) { + this.fire("before:selection:cleared", { + target: obj + }); + this._discardActiveObject(); + this.fire("selection:cleared"); + } + this.fire("object:removed", { + target: obj + }); + obj.fire("removed"); + }, + clearContext: function(ctx) { + ctx.clearRect(0, 0, this.width, this.height); + return this; + }, + getContext: function() { + return this.contextContainer; + }, + clear: function() { + this._objects.length = 0; + if (this.discardActiveGroup) { + this.discardActiveGroup(); + } + if (this.discardActiveObject) { + this.discardActiveObject(); + } + this.clearContext(this.contextContainer); + if (this.contextTop) { + this.clearContext(this.contextTop); + } + this.fire("canvas:cleared"); + this.renderAll(); + return this; + }, + renderAll: function(allOnTop) { + var canvasToDrawOn = this[allOnTop === true && this.interactive ? "contextTop" : "contextContainer"], activeGroup = this.getActiveGroup(); + if (this.contextTop && this.selection && !this._groupSelector) { + this.clearContext(this.contextTop); + } + if (!allOnTop) { + this.clearContext(canvasToDrawOn); + } + this.fire("before:render"); + if (this.clipTo) { + fabric.util.clipContext(this, canvasToDrawOn); + } + this._renderBackground(canvasToDrawOn); + this._renderObjects(canvasToDrawOn, activeGroup); + this._renderActiveGroup(canvasToDrawOn, activeGroup); + if (this.clipTo) { + canvasToDrawOn.restore(); + } + this._renderOverlay(canvasToDrawOn); + if (this.controlsAboveOverlay && this.interactive) { + this.drawControls(canvasToDrawOn); + } + this.fire("after:render"); + return this; + }, + _renderObjects: function(ctx, activeGroup) { + var i, length; + if (!activeGroup || this.preserveObjectStacking) { + for (i = 0, length = this._objects.length; i < length; ++i) { + this._draw(ctx, this._objects[i]); + } + } else { + for (i = 0, length = this._objects.length; i < length; ++i) { + if (this._objects[i] && !activeGroup.contains(this._objects[i])) { + this._draw(ctx, this._objects[i]); + } + } + } + }, + _renderActiveGroup: function(ctx, activeGroup) { + if (activeGroup) { + var sortedObjects = []; + this.forEachObject(function(object) { + if (activeGroup.contains(object)) { + sortedObjects.push(object); + } + }); + activeGroup._set("objects", sortedObjects); + this._draw(ctx, activeGroup); + } + }, + _renderBackground: function(ctx) { + if (this.backgroundColor) { + ctx.fillStyle = this.backgroundColor.toLive ? this.backgroundColor.toLive(ctx) : this.backgroundColor; + ctx.fillRect(this.backgroundColor.offsetX || 0, this.backgroundColor.offsetY || 0, this.width, this.height); + } + if (this.backgroundImage) { + this._draw(ctx, this.backgroundImage); + } + }, + _renderOverlay: function(ctx) { + if (this.overlayColor) { + ctx.fillStyle = this.overlayColor.toLive ? this.overlayColor.toLive(ctx) : this.overlayColor; + ctx.fillRect(this.overlayColor.offsetX || 0, this.overlayColor.offsetY || 0, this.width, this.height); + } + if (this.overlayImage) { + this._draw(ctx, this.overlayImage); + } + }, + renderTop: function() { + var ctx = this.contextTop || this.contextContainer; + this.clearContext(ctx); + if (this.selection && this._groupSelector) { + this._drawSelection(); + } + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.render(ctx); + } + this._renderOverlay(ctx); + this.fire("after:render"); + return this; + }, + getCenter: function() { + return { + top: this.getHeight() / 2, + left: this.getWidth() / 2 + }; + }, + centerObjectH: function(object) { + this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); + this.renderAll(); + return this; + }, + centerObjectV: function(object) { + this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); + this.renderAll(); + return this; + }, + centerObject: function(object) { + var center = this.getCenter(); + this._centerObject(object, new fabric.Point(center.left, center.top)); + this.renderAll(); + return this; + }, + _centerObject: function(object, center) { + object.setPositionByOrigin(center, "center", "center"); + return this; + }, + toDatalessJSON: function(propertiesToInclude) { + return this.toDatalessObject(propertiesToInclude); + }, + toObject: function(propertiesToInclude) { + return this._toObjectMethod("toObject", propertiesToInclude); + }, + toDatalessObject: function(propertiesToInclude) { + return this._toObjectMethod("toDatalessObject", propertiesToInclude); + }, + _toObjectMethod: function(methodName, propertiesToInclude) { + var data = { + objects: this._toObjects(methodName, propertiesToInclude) + }; + extend(data, this.__serializeBgOverlay()); + fabric.util.populateWithProperties(this, data, propertiesToInclude); + return data; + }, + _toObjects: function(methodName, propertiesToInclude) { + return this.getObjects().map(function(instance) { + return this._toObject(instance, methodName, propertiesToInclude); + }, this); + }, + _toObject: function(instance, methodName, propertiesToInclude) { + var originalValue; + if (!this.includeDefaultValues) { + originalValue = instance.includeDefaultValues; + instance.includeDefaultValues = false; + } + var originalProperties = this._realizeGroupTransformOnObject(instance), object = instance[methodName](propertiesToInclude); + if (!this.includeDefaultValues) { + instance.includeDefaultValues = originalValue; + } + this._unwindGroupTransformOnObject(instance, originalProperties); + return object; + }, + _realizeGroupTransformOnObject: function(instance) { + var layoutProps = [ "angle", "flipX", "flipY", "height", "left", "scaleX", "scaleY", "top", "width" ]; + if (instance.group && instance.group === this.getActiveGroup()) { + var originalValues = {}; + layoutProps.forEach(function(prop) { + originalValues[prop] = instance[prop]; + }); + this.getActiveGroup().realizeTransform(instance); + return originalValues; + } else { + return null; + } + }, + _unwindGroupTransformOnObject: function(instance, originalValues) { + if (originalValues) { + instance.set(originalValues); + } + }, + __serializeBgOverlay: function() { + var data = { + background: this.backgroundColor && this.backgroundColor.toObject ? this.backgroundColor.toObject() : this.backgroundColor + }; + if (this.overlayColor) { + data.overlay = this.overlayColor.toObject ? this.overlayColor.toObject() : this.overlayColor; + } + if (this.backgroundImage) { + data.backgroundImage = this.backgroundImage.toObject(); + } + if (this.overlayImage) { + data.overlayImage = this.overlayImage.toObject(); + } + return data; + }, + svgViewportTransformation: true, + toSVG: function(options, reviver) { + options || (options = {}); + var markup = []; + this._setSVGPreamble(markup, options); + this._setSVGHeader(markup, options); + this._setSVGBgOverlayColor(markup, "backgroundColor"); + this._setSVGBgOverlayImage(markup, "backgroundImage"); + this._setSVGObjects(markup, reviver); + this._setSVGBgOverlayColor(markup, "overlayColor"); + this._setSVGBgOverlayImage(markup, "overlayImage"); + markup.push(""); + return markup.join(""); + }, + _setSVGPreamble: function(markup, options) { + if (!options.suppressPreamble) { + markup.push('', '\n'); + } + }, + _setSVGHeader: function(markup, options) { + var width, height, vpt; + if (options.viewBox) { + width = options.viewBox.width; + height = options.viewBox.height; + } else { + width = this.width; + height = this.height; + if (!this.svgViewportTransformation) { + vpt = this.viewportTransform; + width /= vpt[0]; + height /= vpt[3]; + } + } + markup.push("', "Created with Fabric.js ", fabric.version, "", "", fabric.createSVGFontFacesMarkup(this.getObjects()), fabric.createSVGRefElementsMarkup(this), ""); + }, + _setSVGObjects: function(markup, reviver) { + for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { + var instance = objects[i], originalProperties = this._realizeGroupTransformOnObject(instance); + markup.push(instance.toSVG(reviver)); + this._unwindGroupTransformOnObject(instance, originalProperties); + } + }, + _setSVGBgOverlayImage: function(markup, property) { + if (this[property] && this[property].toSVG) { + markup.push(this[property].toSVG()); + } + }, + _setSVGBgOverlayColor: function(markup, property) { + if (this[property] && this[property].source) { + markup.push('"); + } else if (this[property] && property === "overlayColor") { + markup.push('"); + } + }, + sendToBack: function(object) { + removeFromArray(this._objects, object); + this._objects.unshift(object); + return this.renderAll && this.renderAll(); + }, + bringToFront: function(object) { + removeFromArray(this._objects, object); + this._objects.push(object); + return this.renderAll && this.renderAll(); + }, + sendBackwards: function(object, intersecting) { + var idx = this._objects.indexOf(object); + if (idx !== 0) { + var newIdx = this._findNewLowerIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + _findNewLowerIndex: function(object, idx, intersecting) { + var newIdx; + if (intersecting) { + newIdx = idx; + for (var i = idx - 1; i >= 0; --i) { + var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); + if (isIntersecting) { + newIdx = i; + break; + } + } + } else { + newIdx = idx - 1; + } + return newIdx; + }, + bringForward: function(object, intersecting) { + var idx = this._objects.indexOf(object); + if (idx !== this._objects.length - 1) { + var newIdx = this._findNewUpperIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + _findNewUpperIndex: function(object, idx, intersecting) { + var newIdx; + if (intersecting) { + newIdx = idx; + for (var i = idx + 1; i < this._objects.length; ++i) { + var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); + if (isIntersecting) { + newIdx = i; + break; + } + } + } else { + newIdx = idx + 1; + } + return newIdx; + }, + moveTo: function(object, index) { + removeFromArray(this._objects, object); + this._objects.splice(index, 0, object); + return this.renderAll && this.renderAll(); + }, + dispose: function() { + this.clear(); + this.interactive && this.removeListeners(); + return this; + }, + toString: function() { + return "#"; + } + }); + extend(fabric.StaticCanvas.prototype, fabric.Observable); + extend(fabric.StaticCanvas.prototype, fabric.Collection); + extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); + extend(fabric.StaticCanvas, { + EMPTY_JSON: '{"objects": [], "background": "white"}', + supports: function(methodName) { + var el = fabric.util.createCanvasElement(); + if (!el || !el.getContext) { + return null; + } + var ctx = el.getContext("2d"); + if (!ctx) { + return null; + } + switch (methodName) { + case "getImageData": + return typeof ctx.getImageData !== "undefined"; - ctx.save(); + case "setLineDash": + return typeof ctx.setLineDash !== "undefined"; - //setup fill rule for current object - this._setupCompositeOperation(ctx); - if (!noTransform) { - this.transform(ctx); - } - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - if (this.transformMatrix) { - ctx.transform.apply(ctx, this.transformMatrix); - } - this._setOpacity(ctx); - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - this._render(ctx, noTransform); - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - this._restoreCompositeOperation(ctx); + case "toDataURL": + return typeof el.toDataURL !== "undefined"; - ctx.restore(); + case "toDataURLWithQuality": + try { + el.toDataURL("image/jpeg", 0); + return true; + } catch (e) {} + return false; + + default: + return null; + } + } + }); + fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; +})(); + +fabric.BaseBrush = fabric.util.createClass({ + color: "rgb(0, 0, 0)", + width: 1, + shadow: null, + strokeLineCap: "round", + strokeLineJoin: "round", + strokeDashArray: null, + setShadow: function(options) { + this.shadow = new fabric.Shadow(options); + return this; }, - - /* @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setOpacity: function(ctx) { - if (this.group) { - this.group._setOpacity(ctx); - } - ctx.globalAlpha *= this.opacity; - }, - - _setStrokeStyles: function(ctx) { - if (this.stroke) { - ctx.lineWidth = this.strokeWidth; + _setBrushStyles: function() { + var ctx = this.canvas.contextTop; + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; ctx.lineCap = this.strokeLineCap; ctx.lineJoin = this.strokeLineJoin; - ctx.miterLimit = this.strokeMiterLimit; - ctx.strokeStyle = this.stroke.toLive - ? this.stroke.toLive(ctx, this) - : this.stroke; - } - }, - - _setFillStyles: function(ctx) { - if (this.fill) { - ctx.fillStyle = this.fill.toLive - ? this.fill.toLive(ctx, this) - : this.fill; - } - }, - - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - if (!this.active || noTransform) { - return; - } - var vpt = this.getViewportTransform(); - ctx.save(); - var center; - if (this.group) { - center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.group.angle)); - } - center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group); - if (this.group) { - center.x *= this.group.scaleX; - center.y *= this.group.scaleY; - } - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setShadow: function(ctx) { - if (!this.shadow) { - return; - } - - var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1, - multY = (this.canvas && this.canvas.viewportTransform[3]) || 1; - - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur * (multX + multY) * (this.scaleX + this.scaleY) / 4; - ctx.shadowOffsetX = this.shadow.offsetX * multX * this.scaleX; - ctx.shadowOffsetY = this.shadow.offsetY * multY * this.scaleY; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _removeShadow: function(ctx) { - if (!this.shadow) { - return; - } - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderFill: function(ctx) { - if (!this.fill) { - return; - } - - ctx.save(); - if (this.fill.gradientTransform) { - var g = this.fill.gradientTransform; - ctx.transform.apply(ctx, g); - } - if (this.fill.toLive) { - ctx.translate( - -this.width / 2 + this.fill.offsetX || 0, - -this.height / 2 + this.fill.offsetY || 0); - } - if (this.fillRule === 'evenodd') { - ctx.fill('evenodd'); - } - else { - ctx.fill(); - } - ctx.restore(); - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderStroke: function(ctx) { - if (!this.stroke || this.strokeWidth === 0) { - return; - } - - ctx.save(); - if (this.strokeDashArray) { - // Spec requires the concatenation of two copies the dash list when the number of elements is odd - if (1 & this.strokeDashArray.length) { - this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + if (this.strokeDashArray && fabric.StaticCanvas.supports("setLineDash")) { + ctx.setLineDash(this.strokeDashArray); } - if (supportsLineDash) { - ctx.setLineDash(this.strokeDashArray); - this._stroke && this._stroke(ctx); + }, + _setShadow: function() { + if (!this.shadow) { + return; } - else { - this._renderDashedStroke && this._renderDashedStroke(ctx); - } - ctx.stroke(); - } - else { - if (this.stroke.gradientTransform) { - var g = this.stroke.gradientTransform; - ctx.transform.apply(ctx, g); - } - this._stroke ? this._stroke(ctx) : ctx.stroke(); - } - this._removeShadow(ctx); - ctx.restore(); + var ctx = this.canvas.contextTop; + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; }, - - /** - * Clones an instance - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {fabric.Object} clone of an instance - */ - clone: function(callback, propertiesToInclude) { - if (this.constructor.fromObject) { - return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); - } - return new fabric.Object(this.toObject(propertiesToInclude)); - }, - - /** - * Creates an instance of fabric.Image out of an object - * @param {Function} callback callback, invoked with an instance as a first argument - * @return {fabric.Object} thisArg - */ - cloneAsImage: function(callback) { - var dataUrl = this.toDataURL(); - fabric.util.loadImage(dataUrl, function(img) { - if (callback) { - callback(new fabric.Image(img)); - } - }); - return this; - }, - - /** - * Converts an object into a data-url-like string - * @param {Object} options Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - */ - toDataURL: function(options) { - options || (options = { }); - - var el = fabric.util.createCanvasElement(), - boundingRect = this.getBoundingRect(); - - el.width = boundingRect.width; - el.height = boundingRect.height; - - fabric.util.wrapElement(el, 'div'); - var canvas = new fabric.StaticCanvas(el); - - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (options.format === 'jpg') { - options.format = 'jpeg'; - } - - if (options.format === 'jpeg') { - canvas.backgroundColor = '#fff'; - } - - var origParams = { - active: this.get('active'), - left: this.getLeft(), - top: this.getTop() - }; - - this.set('active', false); - this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); - - var originalCanvas = this.canvas; - canvas.add(this); - var data = canvas.toDataURL(options); - - this.set(origParams).setCoords(); - this.canvas = originalCanvas; - - canvas.dispose(); - canvas = null; - - return data; - }, - - /** - * Returns true if specified type is identical to the type of an instance - * @param {String} type Type to check against - * @return {Boolean} - */ - isType: function(type) { - return this.type === type; - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 0; - }, - - /** - * Returns a JSON representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} JSON - */ - toJSON: function(propertiesToInclude) { - // delegate, not alias - return this.toObject(propertiesToInclude); - }, - - /** - * Sets gradient (fill or stroke) of an object - * Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0 - * @param {String} property Property name 'stroke' or 'fill' - * @param {Object} [options] Options object - * @param {String} [options.type] Type of gradient 'radial' or 'linear' - * @param {Number} [options.x1=0] x-coordinate of start point - * @param {Number} [options.y1=0] y-coordinate of start point - * @param {Number} [options.x2=0] x-coordinate of end point - * @param {Number} [options.y2=0] y-coordinate of end point - * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) - * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) - * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} - * @example Set linear gradient - * object.setGradient('fill', { - * type: 'linear', - * x1: -object.width / 2, - * y1: 0, - * x2: object.width / 2, - * y2: 0, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - * @example Set radial gradient - * object.setGradient('fill', { - * type: 'radial', - * x1: 0, - * y1: 0, - * x2: 0, - * y2: 0, - * r1: object.width / 2, - * r2: 10, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - */ - setGradient: function(property, options) { - options || (options = { }); - - var gradient = { colorStops: [] }; - - gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); - gradient.coords = { - x1: options.x1, - y1: options.y1, - x2: options.x2, - y2: options.y2 - }; - - if (options.r1 || options.r2) { - gradient.coords.r1 = options.r1; - gradient.coords.r2 = options.r2; - } - - for (var position in options.colorStops) { - var color = new fabric.Color(options.colorStops[position]); - gradient.colorStops.push({ - offset: position, - color: color.toRgb(), - opacity: color.getAlpha() - }); - } - - return this.set(property, fabric.Gradient.forObject(this, gradient)); - }, - - /** - * Sets pattern fill of an object - * @param {Object} options Options object - * @param {(String|HTMLImageElement)} options.source Pattern source - * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner - * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} - * @example Set pattern - * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { - * object.setPatternFill({ - * source: img, - * repeat: 'repeat' - * }); - * canvas.renderAll(); - * }); - */ - setPatternFill: function(options) { - return this.set('fill', new fabric.Pattern(options)); - }, - - /** - * Sets {@link fabric.Object#shadow|shadow} of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @param {String} [options.color=rgb(0,0,0)] Shadow color - * @param {Number} [options.blur=0] Shadow blur - * @param {Number} [options.offsetX=0] Shadow horizontal offset - * @param {Number} [options.offsetY=0] Shadow vertical offset - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} - * @example Set shadow with string notation - * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); - * canvas.renderAll(); - * @example Set shadow with object notation - * object.setShadow({ - * color: 'red', - * blur: 10, - * offsetX: 20, - * offsetY: 20 - * }); - * canvas.renderAll(); - */ - setShadow: function(options) { - return this.set('shadow', options ? new fabric.Shadow(options) : null); - }, - - /** - * Sets "color" of an instance (alias of `set('fill', …)`) - * @param {String} color Color value - * @return {fabric.Object} thisArg - * @chainable - */ - setColor: function(color) { - this.set('fill', color); - return this; - }, - - /** - * Sets "angle" of an instance - * @param {Number} angle Angle value (in degrees) - * @return {fabric.Object} thisArg - * @chainable - */ - setAngle: function(angle) { - var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; - - if (shouldCenterOrigin) { - this._setOriginToCenter(); - } - - this.set('angle', angle); - - if (shouldCenterOrigin) { - this._resetOrigin(); - } - - return this; - }, - - /** - * Centers object horizontally on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerH: function () { - this.canvas.centerObjectH(this); - return this; - }, - - /** - * Centers object vertically on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerV: function () { - this.canvas.centerObjectV(this); - return this; - }, - - /** - * Centers object vertically and horizontally on canvas to which is was added last - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - center: function () { - this.canvas.centerObject(this); - return this; - }, - - /** - * Removes object from canvas to which it was added last - * @return {fabric.Object} thisArg - * @chainable - */ - remove: function() { - this.canvas.remove(this); - return this; - }, - - /** - * Returns coordinates of a pointer relative to an object - * @param {Event} e Event to operate upon - * @param {Object} [pointer] Pointer to operate upon (instead of event) - * @return {Object} Coordinates of a pointer (x, y) - */ - getLocalPointer: function(e, pointer) { - pointer = pointer || this.canvas.getPointer(e); - var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); - return { - x: pointer.x - objectLeftTop.x, - y: pointer.y - objectLeftTop.y - }; - }, - - /** - * Sets canvas globalCompositeOperation for specific object - * custom composition operation for the particular object can be specifed using globalCompositeOperation property - * @param {CanvasRenderingContext2D} ctx Rendering canvas context - */ - _setupCompositeOperation: function (ctx) { - if (this.globalCompositeOperation) { - this._prevGlobalCompositeOperation = ctx.globalCompositeOperation; - ctx.globalCompositeOperation = this.globalCompositeOperation; - } - }, - - /** - * Restores previously saved canvas globalCompositeOperation after obeject rendering - * @param {CanvasRenderingContext2D} ctx Rendering canvas context - */ - _restoreCompositeOperation: function (ctx) { - if (this.globalCompositeOperation && this._prevGlobalCompositeOperation) { - ctx.globalCompositeOperation = this._prevGlobalCompositeOperation; - } + _resetShadow: function() { + var ctx = this.canvas.contextTop; + ctx.shadowColor = ""; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; } - }); - - fabric.util.createAccessors(fabric.Object); - - /** - * Alias for {@link fabric.Object.prototype.setAngle} - * @alias rotate -> setAngle - * @memberof fabric.Object - */ - fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; - - extend(fabric.Object.prototype, fabric.Observable); - - /** - * Defines the number of fraction digits to use when serializing object values. - * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. - * @static - * @memberof fabric.Object - * @constant - * @type Number - */ - fabric.Object.NUM_FRACTION_DIGITS = 2; - - /** - * Unique id used internally when creating SVG elements - * @static - * @memberof fabric.Object - * @type Number - */ - fabric.Object.__uid = 0; - -})(typeof exports !== 'undefined' ? exports : this); - +}); (function() { - - var degreesToRadians = fabric.util.degreesToRadians; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Translates the coordinates from origin to center coordinates (based on the object's dimensions) - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToCenterPoint: function(point, originX, originY) { - var cx = point.x, - cy = point.y, - strokeWidth = this.stroke ? this.strokeWidth : 0; - - if (originX === 'left') { - cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - - if (originY === 'top') { - cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - - // Apply the reverse rotation to the point (it's already scaled properly) - return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); - }, - - /** - * Translates the coordinates from center to origin coordinates (based on the object's dimensions) - * @param {fabric.Point} center The point which corresponds to center of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToOriginPoint: function(center, originX, originY) { - var x = center.x, - y = center.y, - strokeWidth = this.stroke ? this.strokeWidth : 0; - - // Get the point coordinates - if (originX === 'left') { - x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - if (originY === 'top') { - y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - - // Apply the rotation to the point (it's already scaled properly) - return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); - }, - - /** - * Returns the real center coordinates of the object - * @return {fabric.Point} - */ - getCenterPoint: function() { - var leftTop = new fabric.Point(this.left, this.top); - return this.translateToCenterPoint(leftTop, this.originX, this.originY); - }, - - /** - * Returns the coordinates of the object based on center coordinates - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @return {fabric.Point} - */ - // getOriginPoint: function(center) { - // return this.translateToOriginPoint(center, this.originX, this.originY); - // }, - - /** - * Returns the coordinates of the object as if it has a different origin - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - getPointByOrigin: function(originX, originY) { - var center = this.getCenterPoint(); - return this.translateToOriginPoint(center, originX, originY); - }, - - /** - * Returns the point in local coordinates - * @param {fabric.Point} point The point relative to the global coordinate system - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - toLocalPoint: function(point, originX, originY) { - var center = this.getCenterPoint(), - strokeWidth = this.stroke ? this.strokeWidth : 0, - x, y; - - if (originX && originY) { - if (originX === 'left') { - x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, { + initialize: function(canvas) { + this.canvas = canvas; + this._points = []; + }, + onMouseDown: function(pointer) { + this._prepareForDrawing(pointer); + this._captureDrawingPath(pointer); + this._render(); + }, + onMouseMove: function(pointer) { + this._captureDrawingPath(pointer); + this.canvas.clearContext(this.canvas.contextTop); + this._render(); + }, + onMouseUp: function() { + this._finalizeAndAddPath(); + }, + _prepareForDrawing: function(pointer) { + var p = new fabric.Point(pointer.x, pointer.y); + this._reset(); + this._addPoint(p); + this.canvas.contextTop.moveTo(p.x, p.y); + }, + _addPoint: function(point) { + this._points.push(point); + }, + _reset: function() { + this._points.length = 0; + this._setBrushStyles(); + this._setShadow(); + }, + _captureDrawingPath: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y); + this._addPoint(pointerPoint); + }, + _render: function() { + var ctx = this.canvas.contextTop, v = this.canvas.viewportTransform, p1 = this._points[0], p2 = this._points[1]; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.beginPath(); + if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { + p1.x -= .5; + p2.x += .5; + } + ctx.moveTo(p1.x, p1.y); + for (var i = 1, len = this._points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + p1 = this._points[i]; + p2 = this._points[i + 1]; + } + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + ctx.restore(); + }, + convertPointsToSVGPath: function(points) { + var path = [], p1 = new fabric.Point(points[0].x, points[0].y), p2 = new fabric.Point(points[1].x, points[1].y); + path.push("M ", points[0].x, " ", points[0].y, " "); + for (var i = 1, len = points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + path.push("Q ", p1.x, " ", p1.y, " ", midPoint.x, " ", midPoint.y, " "); + p1 = new fabric.Point(points[i].x, points[i].y); + if (i + 1 < points.length) { + p2 = new fabric.Point(points[i + 1].x, points[i + 1].y); + } + } + path.push("L ", p1.x, " ", p1.y, " "); + return path; + }, + createPath: function(pathData) { + var path = new fabric.Path(pathData, { + fill: null, + stroke: this.color, + strokeWidth: this.width, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeDashArray: this.strokeDashArray, + originX: "center", + originY: "center" + }); + if (this.shadow) { + this.shadow.affectStroke = true; + path.setShadow(this.shadow); + } + return path; + }, + _finalizeAndAddPath: function() { + var ctx = this.canvas.contextTop; + ctx.closePath(); + var pathData = this.convertPointsToSVGPath(this._points).join(""); + if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") { + this.canvas.renderAll(); + return; + } + var path = this.createPath(pathData); + this.canvas.add(path); + path.setCoords(); + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderAll(); + this.canvas.fire("path:created", { + path: path + }); } - else if (originX === 'right') { - x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else { - x = center.x; - } - - if (originY === 'top') { - y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else { - y = center.y; - } - } - else { - x = this.left; - y = this.top; - } - - return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)) - .subtractEquals(new fabric.Point(x, y)); - }, - - /** - * Returns the point in global coordinates - * @param {fabric.Point} The point relative to the local coordinate system - * @return {fabric.Point} - */ - // toGlobalPoint: function(point) { - // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); - // }, - - /** - * Sets the position of the object taking into consideration the object's origin - * @param {fabric.Point} pos The new position of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {void} - */ - setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY), - position = this.translateToOriginPoint(center, this.originX, this.originY); - - this.set('left', position.x); - this.set('top', position.y); - }, - - /** - * @param {String} to One of 'left', 'center', 'right' - */ - adjustPosition: function(to) { - var angle = degreesToRadians(this.angle), - hypotHalf = this.getWidth() / 2, - xHalf = Math.cos(angle) * hypotHalf, - yHalf = Math.sin(angle) * hypotHalf, - hypotFull = this.getWidth(), - xFull = Math.cos(angle) * hypotFull, - yFull = Math.sin(angle) * hypotFull; - - if (this.originX === 'center' && to === 'left' || - this.originX === 'right' && to === 'center') { - // move half left - this.left -= xHalf; - this.top -= yHalf; - } - else if (this.originX === 'left' && to === 'center' || - this.originX === 'center' && to === 'right') { - // move half right - this.left += xHalf; - this.top += yHalf; - } - else if (this.originX === 'left' && to === 'right') { - // move full right - this.left += xFull; - this.top += yFull; - } - else if (this.originX === 'right' && to === 'left') { - // move full left - this.left -= xFull; - this.top -= yFull; - } - - this.setCoords(); - this.originX = to; - }, - - /** - * Sets the origin/position of the object to it's center point - * @private - * @return {void} - */ - _setOriginToCenter: function() { - this._originalOriginX = this.originX; - this._originalOriginY = this.originY; - - var center = this.getCenterPoint(); - - this.originX = 'center'; - this.originY = 'center'; - - this.left = center.x; - this.top = center.y; - }, - - /** - * Resets the origin/position of the object to it's original origin - * @private - * @return {void} - */ - _resetOrigin: function() { - var originPoint = this.translateToOriginPoint( - this.getCenterPoint(), - this._originalOriginX, - this._originalOriginY); - - this.originX = this._originalOriginX; - this.originY = this._originalOriginY; - - this.left = originPoint.x; - this.top = originPoint.y; - - this._originalOriginX = null; - this._originalOriginY = null; - }, - - /** - * @private - */ - _getLeftTopCoords: function() { - return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center'); - } - }); - + }); })(); - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Object containing coordinates of object's controls - * @type Object - * @default - */ - oCoords: null, - - /** - * Checks if object intersects with an area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object intersects with an area formed by 2 points - */ - intersectsWithRect: function(pointTL, pointBR) { - var oCoords = this.oCoords, - tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), - tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), - bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br = new fabric.Point(oCoords.br.x, oCoords.br.y), - intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - pointTL, - pointBR - ); - return intersection.status === 'Intersection'; +fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, { + width: 10, + initialize: function(canvas) { + this.canvas = canvas; + this.points = []; }, - - /** - * Checks if object intersects with another object - * @param {Object} other Object to test - * @return {Boolean} true if object intersects with another object - */ - intersectsWithObject: function(other) { - // extracts coords - function getCoords(oCoords) { - return { - tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), - tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), - bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br: new fabric.Point(oCoords.br.x, oCoords.br.y) - }; - } - var thisCoords = getCoords(this.oCoords), - otherCoords = getCoords(other.oCoords), - intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); - - return intersection.status === 'Intersection'; - }, - - /** - * Checks if object is fully contained within area of another object - * @param {Object} other Object to test - * @return {Boolean} true if object is fully contained within area of another object - */ - isContainedWithinObject: function(other) { - var boundingRect = other.getBoundingRect(), - point1 = new fabric.Point(boundingRect.left, boundingRect.top), - point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); - - return this.isContainedWithinRect(point1, point2); - }, - - /** - * Checks if object is fully contained within area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object is fully contained within area formed by 2 points - */ - isContainedWithinRect: function(pointTL, pointBR) { - var boundingRect = this.getBoundingRect(); - - return ( - boundingRect.left >= pointTL.x && - boundingRect.left + boundingRect.width <= pointBR.x && - boundingRect.top >= pointTL.y && - boundingRect.top + boundingRect.height <= pointBR.y - ); - }, - - /** - * Checks if point is inside the object - * @param {fabric.Point} point Point to check against - * @return {Boolean} true if point is inside the object - */ - containsPoint: function(point) { - var lines = this._getImageLines(this.oCoords), - xPoints = this._findCrossPoints(point, lines); - - // if xPoints is odd then point is inside the object - return (xPoints !== 0 && xPoints % 2 === 1); - }, - - /** - * Method that returns an object with the object edges in it, given the coordinates of the corners - * @private - * @param {Object} oCoords Coordinates of the object corners - */ - _getImageLines: function(oCoords) { - return { - topline: { - o: oCoords.tl, - d: oCoords.tr - }, - rightline: { - o: oCoords.tr, - d: oCoords.br - }, - bottomline: { - o: oCoords.br, - d: oCoords.bl - }, - leftline: { - o: oCoords.bl, - d: oCoords.tl - } - }; - }, - - /** - * Helper method to determine how many cross points are between the 4 object edges - * and the horizontal line determined by a point on canvas - * @private - * @param {fabric.Point} point Point to check - * @param {Object} oCoords Coordinates of the object being evaluated - */ - _findCrossPoints: function(point, oCoords) { - var b1, b2, a1, a2, xi, yi, - xcount = 0, - iLine; - - for (var lineKey in oCoords) { - iLine = oCoords[lineKey]; - // optimisation 1: line below point. no cross - if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { - continue; - } - // optimisation 2: line above point. no cross - if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { - continue; - } - // optimisation 3: vertical line case - if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { - xi = iLine.o.x; - yi = point.y; - } - // calculate the intersection point - else { - b1 = 0; - b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); - a1 = point.y - b1 * point.x; - a2 = iLine.o.y - b2 * iLine.o.x; - - xi = - (a1 - a2) / (b1 - b2); - yi = a1 + b1 * xi; - } - // dont count xi < point.x cases - if (xi >= point.x) { - xcount += 1; - } - // optimisation 4: specific for square images - if (xcount === 2) { - break; - } - } - return xcount; - }, - - /** - * Returns width of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} width value - */ - getBoundingRectWidth: function() { - return this.getBoundingRect().width; - }, - - /** - * Returns height of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} height value - */ - getBoundingRectHeight: function() { - return this.getBoundingRect().height; - }, - - /** - * Returns coordinates of object's bounding rectangle (left, top, width, height) - * @return {Object} Object with left, top, width, height properties - */ - getBoundingRect: function() { - this.oCoords || this.setCoords(); - - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], - minX = fabric.util.array.min(xCoords), - maxX = fabric.util.array.max(xCoords), - width = Math.abs(minX - maxX), - - yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], - minY = fabric.util.array.min(yCoords), - maxY = fabric.util.array.max(yCoords), - height = Math.abs(minY - maxY); - - return { - left: minX, - top: minY, - width: width, - height: height - }; - }, - - /** - * Returns width of an object - * @return {Number} width value - */ - getWidth: function() { - return this.width * this.scaleX; - }, - - /** - * Returns height of an object - * @return {Number} height value - */ - getHeight: function() { - return this.height * this.scaleY; - }, - - /** - * Makes sure the scale is valid and modifies it if necessary - * @private - * @param {Number} value - * @return {Number} - */ - _constrainScale: function(value) { - if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) { - return -this.minScaleLimit; - } - else { - return this.minScaleLimit; - } - } - return value; - }, - - /** - * Scales an object (equally by x and y) - * @param {Number} value Scale factor - * @return {fabric.Object} thisArg - * @chainable - */ - scale: function(value) { - value = this._constrainScale(value); - - if (value < 0) { - this.flipX = !this.flipX; - this.flipY = !this.flipY; - value *= -1; - } - - this.scaleX = value; - this.scaleY = value; - this.setCoords(); - return this; - }, - - /** - * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) - * @param {Number} value New width value - * @return {fabric.Object} thisArg - * @chainable - */ - scaleToWidth: function(value) { - // adjust to bounding rect factor so that rotated shapes would fit as well - var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); - return this.scale(value / this.width / boundingRectFactor); - }, - - /** - * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) - * @param {Number} value New height value - * @return {fabric.Object} thisArg - * @chainable - */ - scaleToHeight: function(value) { - // adjust to bounding rect factor so that rotated shapes would fit as well - var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); - return this.scale(value / this.height / boundingRectFactor); - }, - - /** - * Sets corner position coordinates based on current angle, width and height - * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords - * @return {fabric.Object} thisArg - * @chainable - */ - setCoords: function() { - var theta = degreesToRadians(this.angle), - vpt = this.getViewportTransform(), - f = function (p) { - return fabric.util.transformPoint(p, vpt); - }, - p = this._calculateCurrentDimensions(false), - currentWidth = p.x, currentHeight = p.y; - - // If width is negative, make postive. Fixes path selection issue - if (currentWidth < 0) { - currentWidth = Math.abs(currentWidth); - } - - var _hypotenuse = Math.sqrt( - Math.pow(currentWidth / 2, 2) + - Math.pow(currentHeight / 2, 2)), - - _angle = Math.atan( - isFinite(currentHeight / currentWidth) - ? currentHeight / currentWidth - : 0), - - // offset added for rotate and scale actions - offsetX = Math.cos(_angle + theta) * _hypotenuse, - offsetY = Math.sin(_angle + theta) * _hypotenuse, - sinTh = Math.sin(theta), - cosTh = Math.cos(theta), - coords = this.getCenterPoint(), - wh = new fabric.Point(currentWidth, currentHeight), - _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), - _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), - bl = f(new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh))), - br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), - tl = f(_tl), - tr = f(_tr), - ml = new fabric.Point((tl.x + bl.x)/2, (tl.y + bl.y)/2), - mt = new fabric.Point((tr.x + tl.x)/2, (tr.y + tl.y)/2), - mr = new fabric.Point((br.x + tr.x)/2, (br.y + tr.y)/2), - mb = new fabric.Point((br.x + bl.x)/2, (br.y + bl.y)/2), - mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset); - // debugging - - /* setTimeout(function() { - canvas.contextTop.fillStyle = 'green'; - canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); - canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); - canvas.contextTop.fillRect(br.x, br.y, 3, 3); - canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); - canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); - canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); - canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); - canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); - canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3); - }, 50); */ - - this.oCoords = { - // corners - tl: tl, tr: tr, br: br, bl: bl, - // middle - ml: ml, mt: mt, mr: mr, mb: mb, - // rotating point - mtr: mtr - }; - - // set coordinates of the draggable boxes in the corners used to scale/rotate the image - this._setCornerCoords && this._setCornerCoords(); - - return this; - } - }); -})(); - - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Moves an object to the bottom of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - sendToBack: function() { - if (this.group) { - fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); - } - else { - this.canvas.sendToBack(this); - } - return this; - }, - - /** - * Moves an object to the top of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - bringToFront: function() { - if (this.group) { - fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); - } - else { - this.canvas.bringToFront(this); - } - return this; - }, - - /** - * Moves an object down in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - sendBackwards: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); - } - else { - this.canvas.sendBackwards(this, intersecting); - } - return this; - }, - - /** - * Moves an object up in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - bringForward: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); - } - else { - this.canvas.bringForward(this, intersecting); - } - return this; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {Number} index New position of object - * @return {fabric.Object} thisArg - * @chainable - */ - moveTo: function(index) { - if (this.group) { - fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); - } - else { - this.canvas.moveTo(this, index); - } - return this; - } -}); - - -/* _TO_SVG_START_ */ -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Returns styles-string for svg-export - * @return {String} - */ - getSvgStyles: function() { - - var fill = this.fill - ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) - : 'none', - fillRule = this.fillRule, - stroke = this.stroke - ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) - : 'none', - - strokeWidth = this.strokeWidth ? this.strokeWidth : '0', - strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', - strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', - strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', - strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', - opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', - - visibility = this.visible ? '' : ' visibility: hidden;', - filter = this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; - - return [ - 'stroke: ', stroke, '; ', - 'stroke-width: ', strokeWidth, '; ', - 'stroke-dasharray: ', strokeDashArray, '; ', - 'stroke-linecap: ', strokeLineCap, '; ', - 'stroke-linejoin: ', strokeLineJoin, '; ', - 'stroke-miterlimit: ', strokeMiterLimit, '; ', - 'fill: ', fill, '; ', - 'fill-rule: ', fillRule, '; ', - 'opacity: ', opacity, ';', - filter, - visibility - ].join(''); - }, - - /** - * Returns transform-string for svg-export - * @return {String} - */ - getSvgTransform: function() { - if (this.group && this.group.type === 'path-group') { - return ''; - } - var toFixed = fabric.util.toFixed, - angle = this.getAngle(), - vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0], - center = fabric.util.transformPoint(this.getCenterPoint(), vpt), - - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - translatePart = this.type === 'path-group' ? '' : 'translate(' + - toFixed(center.x, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(center.y, NUM_FRACTION_DIGITS) + - ')', - - anglePart = angle !== 0 - ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') - : '', - - scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1) - ? '' : - (' scale(' + - toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + - ' ' + - toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + - ')'), - - addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0, - - flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', - - addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0, - - flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; - - return [ - translatePart, anglePart, scalePart, flipXPart, flipYPart - ].join(''); - }, - - /** - * Returns transform-string for svg-export from the transform matrix of single elements - * @return {String} - */ - getSvgTransformMatrix: function() { - return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : ''; - }, - - /** - * @private - */ - _createBaseSVGMarkup: function() { - var markup = [ ]; - - if (this.fill && this.fill.toLive) { - markup.push(this.fill.toSVG(this, false)); - } - if (this.stroke && this.stroke.toLive) { - markup.push(this.stroke.toSVG(this, false)); - } - if (this.shadow) { - markup.push(this.shadow.toSVG(this)); - } - return markup; - } -}); -/* _TO_SVG_END_ */ - - -/* - Depends on `stateProperties` -*/ -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Returns true if object state (one of its state properties) was changed - * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called - */ - hasStateChanged: function() { - return this.stateProperties.some(function(prop) { - return this.get(prop) !== this.originalState[prop]; - }, this); - }, - - /** - * Saves state of an object - * @param {Object} [options] Object with additional `stateProperties` array to include when saving state - * @return {fabric.Object} thisArg - */ - saveState: function(options) { - this.stateProperties.forEach(function(prop) { - this.originalState[prop] = this.get(prop); - }, this); - - if (options && options.stateProperties) { - options.stateProperties.forEach(function(prop) { - this.originalState[prop] = this.get(prop); - }, this); - } - - return this; - }, - - /** - * Setups state of an object - * @return {fabric.Object} thisArg - */ - setupState: function() { - this.originalState = { }; - this.saveState(); - - return this; - } -}); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians, - //jscs:disable 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, - newTheta = degreesToRadians(45 - this.angle), - cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, - cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), - sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), - x, y; - - for (var point in coords) { - x = coords[point].x; - y = coords[point].y; - coords[point].corner = { - tl: { - x: x - sinHalfOffset, - y: y - cosHalfOffset - }, - tr: { - x: x + cosHalfOffset, - y: y - sinHalfOffset - }, - bl: { - x: x - cosHalfOffset, - y: y + sinHalfOffset - }, - br: { - x: x + sinHalfOffset, - y: y + cosHalfOffset - } - }; - } - }, - - _calculateCurrentDimensions: function(shouldTransform) { - var vpt = this.getViewportTransform(), - strokeWidth = this.strokeWidth, - w = this.width, - h = this.height, - capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', - vLine = this.type === 'line' && this.width === 0, - hLine = this.type === 'line' && this.height === 0, - sLine = vLine || hLine, - strokeW = (capped && hLine) || !sLine, - strokeH = (capped && vLine) || !sLine; - - if (vLine) { - w = strokeWidth; - } - else if (hLine) { - h = strokeWidth; - } - if (strokeW) { - w += (w < 0 ? -strokeWidth : strokeWidth); - } - if (strokeH) { - h += (h < 0 ? -strokeWidth : strokeWidth); - } - - w = w * this.scaleX + 2 * this.padding; - h = h * this.scaleY + 2 * this.padding; - - if (shouldTransform) { - return fabric.util.transformPoint(new fabric.Point(w, h), vpt, true); - } - return { x: w, y: h }; - }, - - /** - * Draws borders of an object's bounding box. - * Requires public properties: width, height - * Requires public options: padding, borderColor - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawBorders: function(ctx) { - if (!this.hasBorders) { - return this; - } - - ctx.save(); - - ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; - ctx.strokeStyle = this.borderColor; - ctx.lineWidth = 1 / this.borderScaleFactor; - - var wh = this._calculateCurrentDimensions(true), - width = wh.x, - height = wh.y; - if (this.group) { - width = width * this.group.scaleX; - height = height * this.group.scaleY; - } - - ctx.strokeRect( - ~~(-(width / 2)) - 0.5, // offset needed to make lines look sharper - ~~(-(height / 2)) - 0.5, - ~~(width) + 1, // double offset needed to make lines look sharper - ~~(height) + 1 - ); - - if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) { - - var rotateHeight = -height / 2; - + drawDot: function(pointer) { + var point = this.addPoint(pointer), ctx = this.canvas.contextTop, v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.fillStyle = point.fill; ctx.beginPath(); - ctx.moveTo(0, rotateHeight); - ctx.lineTo(0, rotateHeight - this.rotatingPointOffset); + ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); - ctx.stroke(); - } - - ctx.restore(); - return this; + ctx.fill(); + ctx.restore(); }, - - /** - * Draws corners of an object's bounding box. - * Requires public properties: width, height - * Requires public options: cornerSize, padding - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawControls: function(ctx) { - if (!this.hasControls) { - return this; - } - - var wh = this._calculateCurrentDimensions(true), - width = wh.x, - height = wh.y, - left = -(width / 2), - top = -(height / 2), - scaleOffset = this.cornerSize / 2, - methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; - - ctx.save(); - - ctx.lineWidth = 1; - - ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; - ctx.strokeStyle = ctx.fillStyle = this.cornerColor; - - // top-left - this._drawControl('tl', ctx, methodName, - left - scaleOffset, - top - scaleOffset); - - // top-right - this._drawControl('tr', ctx, methodName, - left + width - scaleOffset, - top - scaleOffset); - - // bottom-left - this._drawControl('bl', ctx, methodName, - left - scaleOffset, - top + height - scaleOffset); - - // bottom-right - this._drawControl('br', ctx, methodName, - left + width - scaleOffset, - top + height - scaleOffset); - - if (!this.get('lockUniScaling')) { - - // middle-top - this._drawControl('mt', ctx, methodName, - left + width/2 - scaleOffset, - top - scaleOffset); - - // middle-bottom - this._drawControl('mb', ctx, methodName, - left + width/2 - scaleOffset, - top + height - scaleOffset); - - // middle-right - this._drawControl('mr', ctx, methodName, - left + width - scaleOffset, - top + height/2 - scaleOffset); - - // middle-left - this._drawControl('ml', ctx, methodName, - left - scaleOffset, - top + height/2 - scaleOffset); - } - - // middle-top-rotate - if (this.hasRotatingPoint) { - this._drawControl('mtr', ctx, methodName, - left + width/2 - scaleOffset, - top - this.rotatingPointOffset - scaleOffset); - } - - ctx.restore(); - - return this; + onMouseDown: function(pointer) { + this.points.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.drawDot(pointer); }, - - /** - * @private - */ - _drawControl: function(control, ctx, methodName, left, top) { - if (!this.isControlVisible(control)) { - return; - } - var size = this.cornerSize; - isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size); - ctx[methodName](left, top, size, size); + onMouseMove: function(pointer) { + this.drawDot(pointer); }, - - /** - * 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]; + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + var circles = []; + for (var i = 0, len = this.points.length; i < len; i++) { + var point = this.points[i], circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: "center", + originY: "center", + fill: point.fill + }); + this.shadow && circle.setShadow(this.shadow); + circles.push(circle); + } + var group = new fabric.Group(circles, { + originX: "center", + originY: "center" + }); + group.canvas = this.canvas; + this.canvas.add(group); + this.canvas.fire("path:created", { + path: group + }); + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); }, - - /** - * 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; + addPoint: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y), circleRadius = fabric.util.getRandomInt(Math.max(0, this.width - 20), this.width + 20) / 2, circleColor = new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0, 100) / 100).toRgba(); + pointerPoint.radius = circleRadius; + pointerPoint.fill = circleColor; + this.points.push(pointerPoint); + return pointerPoint; } - }); +}); + +fabric.SprayBrush = fabric.util.createClass(fabric.BaseBrush, { + width: 10, + density: 20, + dotWidth: 1, + dotWidthVariance: 1, + randomOpacity: false, + optimizeOverlapping: true, + initialize: function(canvas) { + this.canvas = canvas; + this.sprayChunks = []; + }, + onMouseDown: function(pointer) { + this.sprayChunks.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.addSprayChunk(pointer); + this.render(); + }, + onMouseMove: function(pointer) { + this.addSprayChunk(pointer); + this.render(); + }, + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + var rects = []; + for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + var sprayChunk = this.sprayChunks[i]; + for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { + var rect = new fabric.Rect({ + width: sprayChunk[j].width, + height: sprayChunk[j].width, + left: sprayChunk[j].x + 1, + top: sprayChunk[j].y + 1, + originX: "center", + originY: "center", + fill: this.color + }); + this.shadow && rect.setShadow(this.shadow); + rects.push(rect); + } + } + if (this.optimizeOverlapping) { + rects = this._getOptimizedRects(rects); + } + var group = new fabric.Group(rects, { + originX: "center", + originY: "center" + }); + group.canvas = this.canvas; + this.canvas.add(group); + this.canvas.fire("path:created", { + path: group + }); + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); + }, + _getOptimizedRects: function(rects) { + var uniqueRects = {}, key; + for (var i = 0, len = rects.length; i < len; i++) { + key = rects[i].left + "" + rects[i].top; + if (!uniqueRects[key]) { + uniqueRects[key] = rects[i]; + } + } + var uniqueRectsArray = []; + for (key in uniqueRects) { + uniqueRectsArray.push(uniqueRects[key]); + } + return uniqueRectsArray; + }, + render: function() { + var ctx = this.canvas.contextTop; + ctx.fillStyle = this.color; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { + var point = this.sprayChunkPoints[i]; + if (typeof point.opacity !== "undefined") { + ctx.globalAlpha = point.opacity; + } + ctx.fillRect(point.x, point.y, point.width, point.width); + } + ctx.restore(); + }, + addSprayChunk: function(pointer) { + this.sprayChunkPoints = []; + var x, y, width, radius = this.width / 2; + for (var i = 0; i < this.density; i++) { + x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); + y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); + if (this.dotWidthVariance) { + width = fabric.util.getRandomInt(Math.max(1, this.dotWidth - this.dotWidthVariance), this.dotWidth + this.dotWidthVariance); + } else { + width = this.dotWidth; + } + var point = new fabric.Point(x, y); + point.width = width; + if (this.randomOpacity) { + point.opacity = fabric.util.getRandomInt(0, 100) / 100; + } + this.sprayChunkPoints.push(point); + } + this.sprayChunks.push(this.sprayChunkPoints); + } +}); + +fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, { + getPatternSrc: function() { + var dotWidth = 20, dotDistance = 5, patternCanvas = fabric.document.createElement("canvas"), patternCtx = patternCanvas.getContext("2d"); + patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; + patternCtx.fillStyle = this.color; + patternCtx.beginPath(); + patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); + patternCtx.closePath(); + patternCtx.fill(); + return patternCanvas; + }, + getPatternSrcFunction: function() { + return String(this.getPatternSrc).replace("this.color", '"' + this.color + '"'); + }, + getPattern: function() { + return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), "repeat"); + }, + _setBrushStyles: function() { + this.callSuper("_setBrushStyles"); + this.canvas.contextTop.strokeStyle = this.getPattern(); + }, + createPath: function(pathData) { + var path = this.callSuper("createPath", pathData); + path.stroke = new fabric.Pattern({ + source: this.source || this.getPatternSrcFunction() + }); + return path; + } +}); + +(function() { + var getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, atan2 = Math.atan2, abs = Math.abs, STROKE_OFFSET = .5; + fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, { + initialize: function(el, options) { + options || (options = {}); + this._initStatic(el, options); + this._initInteractive(); + this._createCacheCanvas(); + fabric.Canvas.activeInstance = this; + }, + uniScaleTransform: false, + centeredScaling: false, + centeredRotation: false, + interactive: true, + selection: true, + selectionColor: "rgba(100, 100, 255, 0.3)", + selectionDashArray: [], + selectionBorderColor: "rgba(255, 255, 255, 0.3)", + selectionLineWidth: 1, + hoverCursor: "move", + moveCursor: "move", + defaultCursor: "default", + freeDrawingCursor: "crosshair", + rotationCursor: "crosshair", + containerClass: "canvas-container", + perPixelTargetFind: false, + targetFindTolerance: 0, + skipTargetFind: false, + _initInteractive: function() { + this._currentTransform = null; + this._groupSelector = null; + this._initWrapperElement(); + this._createUpperCanvas(); + this._initEventListeners(); + this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); + this.calcOffset(); + }, + _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; + } + }, + containsPoint: function(e, target) { + var pointer = this.getPointer(e, true), xy = this._normalizePointer(target, pointer); + return target.containsPoint(xy) || target._findTargetCorner(pointer); + }, + _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 + }; + }, + 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; + }, + _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; + }, + _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; + }, + _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; + }, + _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; + }, + _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); + }, + _translateObject: function(x, y) { + var target = this._currentTransform.target; + if (!target.get("lockMovementX")) { + target.set("left", x - this._currentTransform.offsetX); + } + if (!target.get("lockMovementY")) { + target.set("top", y - this._currentTransform.offsetY); + } + }, + _scaleObject: function(x, y, by) { + var t = this._currentTransform, target = t.target, lockScalingX = target.get("lockScalingX"), lockScalingY = target.get("lockScalingY"), lockScalingFlip = target.get("lockScalingFlip"); + if (lockScalingX && lockScalingY) { + return; + } + 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); + this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip); + target.setPositionByOrigin(constraintPosition, t.originX, t.originY); + }, + _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) { + var target = transform.target, forbidScalingX = false, forbidScalingY = false, strokeWidth = target.stroke ? target.strokeWidth : 0; + transform.newScaleX = localMouse.x / (target.width + strokeWidth / 2); + transform.newScaleY = localMouse.y / (target.height + strokeWidth / 2); + if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) { + forbidScalingX = true; + } + if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) { + forbidScalingY = true; + } + if (by === "equally" && !lockScalingX && !lockScalingY) { + forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform); + } else if (!by) { + forbidScalingX || lockScalingX || target.set("scaleX", transform.newScaleX); + forbidScalingY || lockScalingY || target.set("scaleY", transform.newScaleY); + } else if (by === "x" && !target.get("lockUniScaling")) { + forbidScalingX || lockScalingX || target.set("scaleX", transform.newScaleX); + } else if (by === "y" && !target.get("lockUniScaling")) { + forbidScalingY || lockScalingY || target.set("scaleY", transform.newScaleY); + } + forbidScalingX || forbidScalingY || this._flipObject(transform, by); + }, + _scaleObjectEqually: function(localMouse, target, transform) { + var dist = localMouse.y + localMouse.x, strokeWidth = target.stroke ? target.strokeWidth : 0, lastDist = (target.height + strokeWidth / 2) * transform.original.scaleY + (target.width + strokeWidth / 2) * transform.original.scaleX; + transform.newScaleX = transform.original.scaleX * dist / lastDist; + transform.newScaleY = transform.original.scaleY * dist / lastDist; + target.set("scaleX", transform.newScaleX); + target.set("scaleY", transform.newScaleY); + }, + _flipObject: function(transform, by) { + if (transform.newScaleX < 0 && by !== "y") { + if (transform.originX === "left") { + transform.originX = "right"; + } else if (transform.originX === "right") { + transform.originX = "left"; + } + } + if (transform.newScaleY < 0 && by !== "x") { + if (transform.originY === "top") { + transform.originY = "bottom"; + } else if (transform.originY === "bottom") { + transform.originY = "top"; + } + } + }, + _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; + } + } + if (abs(localMouse.x) > target.padding) { + if (localMouse.x < 0) { + localMouse.x += target.padding; + } else { + localMouse.x -= target.padding; + } + } else { + localMouse.x = 0; + } + if (abs(localMouse.y) > target.padding) { + if (localMouse.y < 0) { + localMouse.y += target.padding; + } else { + localMouse.y -= target.padding; + } + } else { + localMouse.y = 0; + } + }, + _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); + if (angle < 0) { + angle = 360 + angle; + } + t.target.angle = angle % 360; + }, + setCursor: function(value) { + this.upperCanvasEl.style.cursor = value; + }, + _resetObjectTransform: function(target) { + target.scaleX = 1; + target.scaleY = 1; + target.setAngle(0); + }, + _drawSelection: function() { + var ctx = this.contextTop, groupSelector = this._groupSelector, left = groupSelector.left, top = groupSelector.top, aleft = abs(left), atop = abs(top); + ctx.fillStyle = this.selectionColor; + ctx.fillRect(groupSelector.ex - (left > 0 ? 0 : -left), groupSelector.ey - (top > 0 ? 0 : -top), aleft, atop); + ctx.lineWidth = this.selectionLineWidth; + ctx.strokeStyle = this.selectionBorderColor; + if (this.selectionDashArray.length > 1) { + var px = groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft), 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); + } + }, + _isLastRenderedObject: function(e) { + return this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true)); + }, + findTarget: function(e, skipGroup) { + if (this.skipTargetFind) { + return; + } + if (this._isLastRenderedObject(e)) { + return this.lastRenderedObjectWithControlsAboveOverlay; + } + var activeGroup = this.getActiveGroup(); + if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { + return activeGroup; + } + var target = this._searchPossibleTargets(e); + this._fireOverOutEvents(target); + return target; + }, + _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; + } + }, + _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; + } + } + }, + _searchPossibleTargets: function(e) { + var target, pointer = this.getPointer(e, true), i = this._objects.length; + while (i--) { + if (!this._objects[i].group && this._checkTarget(e, this._objects[i], pointer)) { + this.relatedTarget = this._objects[i]; + target = this._objects[i]; + break; + } + } + return target; + }, + getPointer: function(e, ignoreZoom, upperCanvasEl) { + if (!upperCanvasEl) { + upperCanvasEl = this.upperCanvasEl; + } + var pointer = getPointer(e, upperCanvasEl), bounds = upperCanvasEl.getBoundingClientRect(), boundsWidth = bounds.width || 0, boundsHeight = bounds.height || 0, cssScale; + if (!boundsWidth || !boundsHeight) { + if ("top" in bounds && "bottom" in bounds) { + boundsHeight = Math.abs(bounds.top - bounds.bottom); + } + if ("right" in bounds && "left" in bounds) { + boundsWidth = Math.abs(bounds.right - bounds.left); + } + } + this.calcOffset(); + pointer.x = pointer.x - this._offset.left; + pointer.y = pointer.y - this._offset.top; + if (!ignoreZoom) { + pointer = fabric.util.transformPoint(pointer, fabric.util.invertTransform(this.viewportTransform)); + } + if (boundsWidth === 0 || boundsHeight === 0) { + cssScale = { + width: 1, + height: 1 + }; + } else { + cssScale = { + width: upperCanvasEl.width / boundsWidth, + height: upperCanvasEl.height / boundsHeight + }; + } + return { + x: pointer.x * cssScale.width, + y: pointer.y * cssScale.height + }; + }, + _createUpperCanvas: function() { + var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ""); + this.upperCanvasEl = this._createCanvasElement(); + fabric.util.addClass(this.upperCanvasEl, "upper-canvas " + lowerCanvasClass); + this.wrapperEl.appendChild(this.upperCanvasEl); + this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); + this._applyCanvasStyle(this.upperCanvasEl); + this.contextTop = this.upperCanvasEl.getContext("2d"); + }, + _createCacheCanvas: function() { + this.cacheCanvasEl = this._createCanvasElement(); + this.cacheCanvasEl.setAttribute("width", this.width); + this.cacheCanvasEl.setAttribute("height", this.height); + this.contextCache = this.cacheCanvasEl.getContext("2d"); + }, + _initWrapperElement: function() { + this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, "div", { + "class": this.containerClass + }); + fabric.util.setStyle(this.wrapperEl, { + width: this.getWidth() + "px", + height: this.getHeight() + "px", + position: "relative" + }); + fabric.util.makeElementUnselectable(this.wrapperEl); + }, + _applyCanvasStyle: function(element) { + var width = this.getWidth() || element.width, height = this.getHeight() || element.height; + fabric.util.setStyle(element, { + position: "absolute", + width: width + "px", + height: height + "px", + left: 0, + top: 0 + }); + element.width = width; + element.height = height; + fabric.util.makeElementUnselectable(element); + }, + _copyCanvasStyle: function(fromEl, toEl) { + toEl.style.cssText = fromEl.style.cssText; + }, + getSelectionContext: function() { + return this.contextTop; + }, + getSelectionElement: function() { + return this.upperCanvasEl; + }, + _setActiveObject: function(object) { + if (this._activeObject) { + this._activeObject.set("active", false); + } + this._activeObject = object; + object.set("active", true); + }, + setActiveObject: function(object, e) { + this._setActiveObject(object); + this.renderAll(); + this.fire("object:selected", { + target: object, + e: e + }); + object.fire("selected", { + e: e + }); + return this; + }, + getActiveObject: function() { + return this._activeObject; + }, + _discardActiveObject: function() { + if (this._activeObject) { + this._activeObject.set("active", false); + } + this._activeObject = null; + }, + discardActiveObject: function(e) { + this._discardActiveObject(); + this.renderAll(); + this.fire("selection:cleared", { + e: e + }); + return this; + }, + _setActiveGroup: function(group) { + this._activeGroup = group; + if (group) { + group.set("active", true); + } + }, + setActiveGroup: function(group, e) { + this._setActiveGroup(group); + if (group) { + this.fire("object:selected", { + target: group, + e: e + }); + group.fire("selected", { + e: e + }); + } + return this; + }, + getActiveGroup: function() { + return this._activeGroup; + }, + _discardActiveGroup: function() { + var g = this.getActiveGroup(); + if (g) { + g.destroy(); + } + this.setActiveGroup(null); + }, + discardActiveGroup: function(e) { + this._discardActiveGroup(); + this.fire("selection:cleared", { + e: e + }); + return this; + }, + deactivateAll: function() { + var allObjects = this.getObjects(), i = 0, len = allObjects.length; + for (;i < len; i++) { + allObjects[i].set("active", false); + } + this._discardActiveGroup(); + this._discardActiveObject(); + return this; + }, + deactivateAllWithDispatch: function(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; + }, + drawControls: function(ctx) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + this._drawGroupControls(ctx, activeGroup); + } else { + this._drawObjectsControls(ctx); + } + }, + _drawGroupControls: function(ctx, activeGroup) { + activeGroup._renderControls(ctx); + }, + _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]; + } + } + }); + for (var prop in fabric.StaticCanvas) { + if (prop !== "prototype") { + fabric.Canvas[prop] = fabric.StaticCanvas[prop]; + } + } + if (fabric.isTouchSupported) { + fabric.Canvas.prototype._setCursorFromEvent = function() {}; + } + fabric.Element = fabric.Canvas; })(); - -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(); - } +(function() { + var cursorOffset = { + mt: 0, + tr: 1, + mr: 2, + br: 3, + mb: 4, + bl: 5, + ml: 6, + tl: 7 + }, addListener = fabric.util.addListener, removeListener = fabric.util.removeListener; + fabric.util.object.extend(fabric.Canvas.prototype, { + cursorMap: [ "n-resize", "ne-resize", "e-resize", "se-resize", "s-resize", "sw-resize", "w-resize", "nw-resize" ], + _initEventListeners: function() { + this._bindEvents(); + addListener(fabric.window, "resize", this._onResize); + addListener(this.upperCanvasEl, "mousedown", this._onMouseDown); + addListener(this.upperCanvasEl, "mousemove", this._onMouseMove); + addListener(this.upperCanvasEl, "mousewheel", this._onMouseWheel); + addListener(this.upperCanvasEl, "touchstart", this._onMouseDown); + addListener(this.upperCanvasEl, "touchmove", this._onMouseMove); + if (typeof eventjs !== "undefined" && "add" in eventjs) { + eventjs.add(this.upperCanvasEl, "gesture", this._onGesture); + eventjs.add(this.upperCanvasEl, "drag", this._onDrag); + eventjs.add(this.upperCanvasEl, "orientation", this._onOrientationChange); + eventjs.add(this.upperCanvasEl, "shake", this._onShake); + eventjs.add(this.upperCanvasEl, "longpress", this._onLongPress); + } + }, + _bindEvents: function() { + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onResize = this._onResize.bind(this); + this._onGesture = this._onGesture.bind(this); + this._onDrag = this._onDrag.bind(this); + this._onShake = this._onShake.bind(this); + this._onLongPress = this._onLongPress.bind(this); + this._onOrientationChange = this._onOrientationChange.bind(this); + this._onMouseWheel = this._onMouseWheel.bind(this); + }, + removeListeners: function() { + removeListener(fabric.window, "resize", this._onResize); + removeListener(this.upperCanvasEl, "mousedown", this._onMouseDown); + removeListener(this.upperCanvasEl, "mousemove", this._onMouseMove); + removeListener(this.upperCanvasEl, "mousewheel", this._onMouseWheel); + removeListener(this.upperCanvasEl, "touchstart", this._onMouseDown); + removeListener(this.upperCanvasEl, "touchmove", this._onMouseMove); + if (typeof eventjs !== "undefined" && "remove" in eventjs) { + eventjs.remove(this.upperCanvasEl, "gesture", this._onGesture); + eventjs.remove(this.upperCanvasEl, "drag", this._onDrag); + eventjs.remove(this.upperCanvasEl, "orientation", this._onOrientationChange); + eventjs.remove(this.upperCanvasEl, "shake", this._onShake); + eventjs.remove(this.upperCanvasEl, "longpress", this._onLongPress); + } + }, + _onGesture: function(e, self) { + this.__onTransformGesture && this.__onTransformGesture(e, self); + }, + _onDrag: function(e, self) { + this.__onDrag && this.__onDrag(e, self); + }, + _onMouseWheel: function(e, self) { + this.__onMouseWheel && this.__onMouseWheel(e, self); + }, + _onOrientationChange: function(e, self) { + this.__onOrientationChange && this.__onOrientationChange(e, self); + }, + _onShake: function(e, self) { + this.__onShake && this.__onShake(e, self); + }, + _onLongPress: function(e, self) { + this.__onLongPress && this.__onLongPress(e, self); + }, + _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") { + removeListener(this.upperCanvasEl, "mousedown", this._onMouseDown); + } else { + addListener(fabric.document, "mouseup", this._onMouseUp); + addListener(fabric.document, "mousemove", this._onMouseMove); + } + }, + _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") { + var _this = this; + setTimeout(function() { + addListener(_this.upperCanvasEl, "mousedown", _this._onMouseDown); + }, 400); + } + }, + _onMouseMove: function(e) { + !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); + this.__onMouseMove(e); + }, + _onResize: function() { + this.calcOffset(); + }, + _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)); + }, + __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); + var _this = this; + setTimeout(function() { + _this._setCursorFromEvent(e, target); + }, 50); + this.fire("mouse:up", { + target: target, + e: e + }); + target && target.fire("mouseup", { + e: e + }); + }, + _finalizeCurrentTransform: function() { + var transform = this._currentTransform, target = transform.target; + if (target._scaling) { + target._scaling = false; + } + target.setCoords(); + if (this.stateful && target.hasStateChanged()) { + this.fire("object:modified", { + target: target + }); + target.fire("modified"); + } + this._restoreOriginXY(target); + }, + _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; + } + }, + _onMouseDownInDrawingMode: function(e) { + this._isCurrentlyDrawing = true; + this.discardActiveObject(e).renderAll(); + if (this.clipTo) { + fabric.util.clipContext(this, this.contextTop); + } + var ivt = fabric.util.invertTransform(this.viewportTransform), pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseDown(pointer); + this.fire("mouse:down", { + e: e + }); + var target = this.findTarget(e); + if (typeof target !== "undefined") { + target.fire("mousedown", { + e: e, + target: target + }); + } + }, + _onMouseMoveInDrawingMode: function(e) { + if (this._isCurrentlyDrawing) { + var ivt = fabric.util.invertTransform(this.viewportTransform), pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseMove(pointer); + } + this.setCursor(this.freeDrawingCursor); + this.fire("mouse:move", { + e: e + }); + var target = this.findTarget(e); + if (typeof target !== "undefined") { + target.fire("mousemove", { + e: e, + target: target + }); + } + }, + _onMouseUpInDrawingMode: function(e) { + this._isCurrentlyDrawing = false; + if (this.clipTo) { + this.contextTop.restore(); + } + this.freeDrawingBrush.onMouseUp(); + this.fire("mouse:up", { + e: e + }); + var target = this.findTarget(e); + if (typeof target !== "undefined") { + target.fire("mouseup", { + e: e, + target: target + }); + } + }, + __onMouseDown: function(e) { + var isLeftClick = "which" in e ? e.which === 1 : e.button === 1; + if (!isLeftClick && !fabric.isTouchSupported) { + return; + } + if (this.isDrawingMode) { + this._onMouseDownInDrawingMode(e); + return; + } + if (this._currentTransform) { + return; + } + var target = this.findTarget(e), pointer = this.getPointer(e, true); + 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); + } + shouldRender && this.renderAll(); + this.fire("mouse:down", { + target: target, + e: e + }); + target && target.fire("mousedown", { + e: e + }); + }, + _beforeTransform: function(e, target) { + this.stateful && target.saveState(); + if (target._findTargetCorner(this.getPointer(e))) { + this.onBeforeScaleRotate(target); + } + if (target !== this.getActiveGroup() && target !== this.getActiveObject()) { + this.deactivateAll(); + this.setActiveObject(target, e); + } + }, + _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 + }; + } + }, + _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; + }, + _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; + }, + __onMouseMove: function(e) { + var target, pointer; + if (this.isDrawingMode) { + this._onMouseMoveInDrawingMode(e); + return; + } + if (typeof e.touches !== "undefined" && e.touches.length > 1) { + return; + } + var groupSelector = this._groupSelector; + 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 + }); + }, + _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(); + }, + _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); + } + }, + _fire: function(eventName, target, e) { + this.fire("object:" + eventName, { + target: target, + e: e + }); + target.fire(eventName, { + e: e + }); + }, + _beforeScaleTransform: function(e, transform) { + if (transform.action === "scale" || transform.action === "scaleX" || transform.action === "scaleY") { + var centerTransform = this._shouldCenterTransform(e, transform.target); + if (centerTransform && (transform.originX !== "center" || transform.originY !== "center") || !centerTransform && transform.originX === "center" && transform.originY === "center") { + this._resetCurrentTransform(e); + transform.reset = true; + } + } + }, + _onScale: function(e, transform, x, y) { + if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get("lockUniScaling")) { + transform.currentAction = "scale"; + this._scaleObject(x, y); + } else { + if (!transform.reset && transform.currentAction === "scale") { + this._resetCurrentTransform(e, transform.target); + } + transform.currentAction = "scaleEqually"; + this._scaleObject(x, y, "equally"); + } + }, + _setCursorFromEvent: function(e, target) { + if (!target || !target.selectable) { + this.setCursor(this.defaultCursor); + return false; + } else { + var activeGroup = this.getActiveGroup(), 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; + }, + _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; + } + }, + _getRotatedCornerCursor: function(corner, target) { + var n = Math.round(target.getAngle() % 360 / 45); + if (n < 0) { + n += 8; + } + n += cursorOffset[corner]; + n %= 8; + return this.cursorMap[n]; + } }); +})(); - 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(); - } +(function() { + var min = Math.min, max = Math.max; + fabric.util.object.extend(fabric.Canvas.prototype, { + _shouldGroup: function(e, target) { + var activeObject = this.getActiveObject(); + return e.shiftKey && (this.getActiveGroup() || activeObject && activeObject !== target) && this.selection; + }, + _handleGrouping: function(e, target) { + if (target === this.getActiveGroup()) { + target = this.findTarget(e, true); + if (!target || target.isType("group")) { + return; + } + } + if (this.getActiveGroup()) { + this._updateActiveGroup(target, e); + } else { + this._createActiveGroup(target, e); + } + if (this._activeGroup) { + this._activeGroup.saveCoords(); + } + }, + _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) { + this.discardActiveGroup(e); + 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); + }, + _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); + }, + _createGroup: function(target) { + var objects = this.getObjects(), isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), groupObjects = isActiveLower ? [ this._activeObject, target ] : [ target, this._activeObject ]; + return new fabric.Group(groupObjects, { + canvas: this + }); + }, + _groupSelectedObjects: function(e) { + var group = this._collectObjects(); + if (group.length === 1) { + this.setActiveObject(group[0], e); + } else if (group.length > 1) { + group = new fabric.Group(group.reverse(), { + canvas: this + }); + group.addWithUpdate(); + this.setActiveGroup(group, e); + group.saveCoords(); + this.fire("selection:created", { + target: group + }); + this.renderAll(); + } + }, + _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); + if (isClick) { + break; + } + } + } + return group; + }, + _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); + } + this._groupSelector = null; + this._currentTransform = null; + } }); +})(); - 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.StaticCanvas.prototype, { + toDataURL: function(options) { + options || (options = {}); + var format = options.format || "png", quality = options.quality || 1, multiplier = options.multiplier || 1, cropping = { + left: options.left, + top: options.top, + width: options.width, + height: options.height + }; + if (multiplier !== 1) { + return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); + } else { + return this.__toDataURL(format, quality, cropping); + } + }, + __toDataURL: function(format, quality, cropping) { + this.renderAll(true); + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); + if (format === "jpg") { + format = "jpeg"; + } + var data = fabric.StaticCanvas.supports("toDataURLWithQuality") ? (croppedCanvasEl || canvasEl).toDataURL("image/" + format, quality) : (croppedCanvasEl || canvasEl).toDataURL("image/" + format); + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + if (croppedCanvasEl) { + croppedCanvasEl = null; + } + return data; + }, + __getCroppedCanvas: function(canvasEl, cropping) { + var croppedCanvasEl, croppedCtx, shouldCrop = "left" in cropping || "top" in cropping || "width" in cropping || "height" in cropping; + if (shouldCrop) { + croppedCanvasEl = fabric.util.createCanvasElement(); + croppedCtx = croppedCanvasEl.getContext("2d"); + croppedCanvasEl.width = cropping.width || this.width; + croppedCanvasEl.height = cropping.height || this.height; + croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); + } + return croppedCanvasEl; + }, + __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { + var origWidth = this.getWidth(), origHeight = this.getHeight(), scaledWidth = origWidth * multiplier, scaledHeight = origHeight * multiplier, activeObject = this.getActiveObject(), activeGroup = this.getActiveGroup(), ctx = this.contextTop || this.contextContainer; + if (multiplier > 1) { + this.setWidth(scaledWidth).setHeight(scaledHeight); + } + ctx.scale(multiplier, multiplier); + if (cropping.left) { + cropping.left *= multiplier; + } + if (cropping.top) { + cropping.top *= multiplier; + } + if (cropping.width) { + cropping.width *= multiplier; + } else if (multiplier < 1) { + cropping.width = scaledWidth; + } + if (cropping.height) { + cropping.height *= multiplier; + } else if (multiplier < 1) { + cropping.height = scaledHeight; + } + if (activeGroup) { + this._tempRemoveBordersControlsFromGroup(activeGroup); + } else if (activeObject && this.deactivateAll) { + this.deactivateAll(); + } + this.renderAll(true); + var data = this.__toDataURL(format, quality, cropping); + this.width = origWidth; + this.height = origHeight; + ctx.scale(1 / multiplier, 1 / multiplier); + this.setWidth(origWidth).setHeight(origHeight); + if (activeGroup) { + this._restoreBordersControlsOnGroup(activeGroup); + } else if (activeObject && this.setActiveObject) { + this.setActiveObject(activeObject); + } + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + return data; + }, + toDataURLWithMultiplier: function(format, multiplier, quality) { + return this.toDataURL({ + format: format, + multiplier: multiplier, + quality: quality + }); + }, + _tempRemoveBordersControlsFromGroup: function(group) { + group.origHasControls = group.hasControls; + group.origBorderColor = group.borderColor; + group.hasControls = true; + group.borderColor = "rgba(0,0,0,0)"; + group.forEachObject(function(o) { + o.origBorderColor = o.borderColor; + o.borderColor = "rgba(0,0,0,0)"; + }); + }, + _restoreBordersControlsOnGroup: function(group) { + group.hideControls = group.origHideControls; + group.borderColor = group.origBorderColor; + group.forEachObject(function(o) { + o.borderColor = o.origBorderColor; + delete o.origBorderColor; + }); + } }); -fabric.util.object.extend(fabric.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; +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + loadFromDatalessJSON: function(json, callback, reviver) { + return this.loadFromJSON(json, callback, reviver); + }, + loadFromJSON: function(json, callback, reviver) { + if (!json) { + return; } - else { - _this.set(property, value); + var serialized = typeof json === "string" ? JSON.parse(json) : json; + this.clear(); + var _this = this; + this._enlivenObjects(serialized.objects, function() { + _this._setBgOverlay(serialized, callback); + }, reviver); + return this; + }, + _setBgOverlay: function(serialized, callback) { + var _this = this, loaded = { + backgroundColor: false, + overlayColor: false, + backgroundImage: false, + overlayImage: false + }; + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { + callback && callback(); + return; } - if (skipCallbacks) { - return; + var cbIfLoaded = function() { + if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { + _this.renderAll(); + callback && callback(); + } + }; + this.__setBgOverlay("backgroundImage", serialized.backgroundImage, loaded, cbIfLoaded); + this.__setBgOverlay("overlayImage", serialized.overlayImage, loaded, cbIfLoaded); + this.__setBgOverlay("backgroundColor", serialized.background, loaded, cbIfLoaded); + this.__setBgOverlay("overlayColor", serialized.overlay, loaded, cbIfLoaded); + cbIfLoaded(); + }, + __setBgOverlay: function(property, value, loaded, callback) { + var _this = this; + if (!value) { + loaded[property] = true; + return; } - options.onChange && options.onChange(); - }, - onComplete: function() { - if (skipCallbacks) { - return; + if (property === "backgroundImage" || property === "overlayImage") { + fabric.Image.fromObject(value, function(img) { + _this[property] = img; + loaded[property] = true; + callback && callback(); + }); + } else { + this["set" + fabric.util.string.capitalize(property, true)](value, function() { + loaded[property] = true; + callback && callback(); + }); } - - _this.setCoords(); - options.onComplete && options.onComplete(); - } - }); - } + }, + _enlivenObjects: function(objects, callback, reviver) { + var _this = this; + if (!objects || objects.length === 0) { + callback && callback(); + return; + } + var renderOnAddRemove = this.renderOnAddRemove; + this.renderOnAddRemove = false; + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + enlivenedObjects.forEach(function(obj, index) { + _this.insertAt(obj, index, true); + }); + _this.renderOnAddRemove = renderOnAddRemove; + callback && callback(); + }, null, reviver); + }, + _toDataURL: function(format, callback) { + this.clone(function(clone) { + callback(clone.toDataURL(format)); + }); + }, + _toDataURLWithMultiplier: function(format, multiplier, callback) { + this.clone(function(clone) { + callback(clone.toDataURLWithMultiplier(format, multiplier)); + }); + }, + clone: function(callback, properties) { + var data = JSON.stringify(this.toJSON(properties)); + this.cloneWithoutData(function(clone) { + clone.loadFromJSON(data, function() { + callback && callback(clone); + }); + }); + }, + cloneWithoutData: function(callback) { + var el = fabric.document.createElement("canvas"); + el.width = this.getWidth(); + el.height = this.getHeight(); + var clone = new fabric.Canvas(el); + clone.clipTo = this.clipTo; + if (this.backgroundImage) { + clone.setBackgroundImage(this.backgroundImage.src, function() { + clone.renderAll(); + callback && callback(clone); + }); + clone.backgroundImageOpacity = this.backgroundImageOpacity; + clone.backgroundImageStretch = this.backgroundImageStretch; + } else { + callback && callback(clone); + } + } }); - (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Line) { - fabric.warn('fabric.Line is already defined'); - return; - } - - /** - * Line class - * @class fabric.Line - * @extends fabric.Object - * @see {@link fabric.Line#initialize} for constructor definition - */ - fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'line', - - /** - * x value or first line edge - * @type Number - * @default - */ - x1: 0, - - /** - * y value or first line edge - * @type Number - * @default - */ - y1: 0, - - /** - * x value or second line edge - * @type Number - * @default - */ - x2: 0, - - /** - * y value or second line edge - * @type Number - * @default - */ - y2: 0, - - /** - * Constructor - * @param {Array} [points] Array of points - * @param {Object} [options] Options object - * @return {fabric.Line} thisArg - */ - initialize: function(points, options) { - options = options || { }; - - if (!points) { - points = [0, 0, 0, 0]; - } - - this.callSuper('initialize', options); - - this.set('x1', points[0]); - this.set('y1', points[1]); - this.set('x2', points[2]); - this.set('y2', points[3]); - - this._setWidthHeight(options); - }, - - /** - * @private - * @param {Object} [options] Options - */ - _setWidthHeight: function(options) { - options || (options = { }); - - this.width = Math.abs(this.x2 - this.x1); - this.height = Math.abs(this.y2 - this.y1); - - this.left = 'left' in options - ? options.left - : this._getLeftToOriginX(); - - this.top = 'top' in options - ? options.top - : this._getTopToOriginY(); - }, - - /** - * @private - * @param {String} key - * @param {Any} value - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - if (typeof coordProps[key] !== 'undefined') { - this._setWidthHeight(); - } - return this; - }, - - /** - * @private - * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. - */ - _getLeftToOriginX: makeEdgeToOriginGetter( - { // property names - origin: 'originX', - axis1: 'x1', - axis2: 'x2', - dimension: 'width' - }, - { // possible values of origin - nearest: 'left', - center: 'center', - farthest: 'right' - } - ), - - /** - * @private - * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. - */ - _getTopToOriginY: makeEdgeToOriginGetter( - { // property names - origin: 'originY', - axis1: 'y1', - axis2: 'y2', - dimension: 'height' - }, - { // possible values of origin - nearest: 'top', - center: 'center', - farthest: 'bottom' - } - ), - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - - if (noTransform) { - // Line coords are distances from left-top of canvas to origin of line. - // To render line in a path-group, we need to translate them to - // distances from center of path-group to center of line. - var cp = this.getCenterPoint(); - ctx.translate( - cp.x - this.strokeWidth / 2, - cp.y - this.strokeWidth / 2 - ); - } - - if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { - // move from center (of virtual box) to its left/top corner - // we can't assume x1, y1 is top left and x2, y2 is bottom right - var p = this.calcLinePoints(); - ctx.moveTo(p.x1, p.y1); - ctx.lineTo(p.x2, p.y2); - } - - ctx.lineWidth = this.strokeWidth; - - // TODO: test this - // make sure setting "fill" changes color of a line - // (by copying fillStyle to strokeStyle, since line is stroked, not filled) - var origStrokeStyle = ctx.strokeStyle; - ctx.strokeStyle = this.stroke || ctx.fillStyle; - this.stroke && this._renderStroke(ctx); - ctx.strokeStyle = origStrokeStyle; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var p = this.calcLinePoints(); - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray); - ctx.closePath(); - }, - - /** - * Returns object representation of an instance - * @methd toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); - }, - - /** - * Recalculates line points given width and height - * @private - */ - calcLinePoints: function() { - var xMult = this.x1 <= this.x2 ? -1 : 1, - yMult = this.y1 <= this.y2 ? -1 : 1, - x1 = (xMult * this.width * 0.5), - y1 = (yMult * this.height * 0.5), - x2 = (xMult * this.width * -0.5), - y2 = (yMult * this.height * -0.5); - - return { - x1: x1, - x2: x2, - y1: y1, - y2: y2 - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 }; - - if (!(this.group && this.group.type === 'path-group')) { - p = this.calcLinePoints(); - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, toFixed = fabric.util.toFixed, capitalize = fabric.util.string.capitalize, degreesToRadians = fabric.util.degreesToRadians, supportsLineDash = fabric.StaticCanvas.supports("setLineDash"); + if (fabric.Object) { + return; } - }); + fabric.Object = fabric.util.createClass({ + type: "object", + originX: "left", + originY: "top", + top: 0, + left: 0, + width: 0, + height: 0, + scaleX: 1, + scaleY: 1, + flipX: false, + flipY: false, + opacity: 1, + angle: 0, + cornerSize: 12, + transparentCorners: true, + hoverCursor: null, + padding: 0, + borderColor: "rgba(102,153,255,0.75)", + cornerColor: "rgba(102,153,255,0.5)", + centeredScaling: false, + centeredRotation: true, + fill: "rgb(0,0,0)", + fillRule: "nonzero", + globalCompositeOperation: "source-over", + backgroundColor: "", + stroke: null, + strokeWidth: 1, + strokeDashArray: null, + strokeLineCap: "butt", + strokeLineJoin: "miter", + strokeMiterLimit: 10, + shadow: null, + borderOpacityWhenMoving: .4, + borderScaleFactor: 1, + transformMatrix: null, + minScaleLimit: .01, + selectable: true, + evented: true, + visible: true, + hasControls: true, + hasBorders: true, + hasRotatingPoint: true, + rotatingPointOffset: 40, + perPixelTargetFind: false, + includeDefaultValues: true, + clipTo: null, + lockMovementX: false, + lockMovementY: false, + lockRotation: false, + lockScalingX: false, + lockScalingY: false, + lockUniScaling: false, + lockScalingFlip: false, + stateProperties: ("top left width height scaleX scaleY flipX flipY originX originY transformMatrix " + "stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit " + "angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor").split(" "), + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + _initGradient: function(options) { + if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { + this.set("fill", new fabric.Gradient(options.fill)); + } + }, + _initPattern: function(options) { + if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { + this.set("fill", new fabric.Pattern(options.fill)); + } + if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { + this.set("stroke", new fabric.Pattern(options.stroke)); + } + }, + _initClipping: function(options) { + if (!options.clipTo || typeof options.clipTo !== "string") { + return; + } + var functionBody = fabric.util.getFunctionBody(options.clipTo); + if (typeof functionBody !== "undefined") { + this.clipTo = new Function("ctx", functionBody); + } + }, + setOptions: function(options) { + for (var prop in options) { + this.set(prop, options[prop]); + } + this._initGradient(options); + this._initPattern(options); + this._initClipping(options); + }, + transform: function(ctx, fromLeft) { + var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + ctx.scale(this.scaleX * (this.flipX ? -1 : 1), this.scaleY * (this.flipY ? -1 : 1)); + }, + toObject: function(propertiesToInclude) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, object = { + type: this.type, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: this.fill && this.fill.toObject ? this.fill.toObject() : this.fill, + stroke: this.stroke && this.stroke.toObject ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: this.shadow && this.shadow.toObject ? this.shadow.toObject() : this.shadow, + visible: this.visible, + clipTo: this.clipTo && String(this.clipTo), + backgroundColor: this.backgroundColor, + fillRule: this.fillRule, + globalCompositeOperation: this.globalCompositeOperation + }; + if (!this.includeDefaultValues) { + object = this._removeDefaultValues(object); + } + fabric.util.populateWithProperties(this, object, propertiesToInclude); + return object; + }, + toDatalessObject: function(propertiesToInclude) { + return this.toObject(propertiesToInclude); + }, + _removeDefaultValues: function(object) { + var prototype = fabric.util.getKlass(object.type).prototype, stateProperties = prototype.stateProperties; + stateProperties.forEach(function(prop) { + if (object[prop] === prototype[prop]) { + delete object[prop]; + } + }); + return object; + }, + toString: function() { + return "#"; + }, + get: function(property) { + return this[property]; + }, + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + set: function(key, value) { + if (typeof key === "object") { + this._setObject(key); + } else { + if (typeof value === "function" && key !== "clipTo") { + this._set(key, value(this.get(key))); + } else { + this._set(key, value); + } + } + return this; + }, + _set: function(key, value) { + var shouldConstrainValue = key === "scaleX" || key === "scaleY"; + if (shouldConstrainValue) { + value = this._constrainScale(value); + } + if (key === "scaleX" && value < 0) { + this.flipX = !this.flipX; + value *= -1; + } else if (key === "scaleY" && value < 0) { + this.flipY = !this.flipY; + value *= -1; + } else if (key === "width" || key === "height") { + this.minScaleLimit = toFixed(Math.min(.1, 1 / Math.max(this.width, this.height)), 2); + } else if (key === "shadow" && value && !(value instanceof fabric.Shadow)) { + value = new fabric.Shadow(value); + } + this[key] = value; + return this; + }, + toggle: function(property) { + var value = this.get(property); + if (typeof value === "boolean") { + this.set(property, !value); + } + return this; + }, + setSourcePath: function(value) { + this.sourcePath = value; + return this; + }, + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) { + return this.canvas.viewportTransform; + } + return [ 1, 0, 0, 1, 0, 0 ]; + }, + render: function(ctx, noTransform) { + if (this.width === 0 && this.height === 0 || !this.visible) { + return; + } + ctx.save(); + this._setupCompositeOperation(ctx); + if (!noTransform) { + this.transform(ctx); + } + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + if (this.transformMatrix) { + ctx.transform.apply(ctx, this.transformMatrix); + } + this._setOpacity(ctx); + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + this._render(ctx, noTransform); + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + this._restoreCompositeOperation(ctx); + ctx.restore(); + }, + _setOpacity: function(ctx) { + if (this.group) { + this.group._setOpacity(ctx); + } + ctx.globalAlpha *= this.opacity; + }, + _setStrokeStyles: function(ctx) { + if (this.stroke) { + ctx.lineWidth = this.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx, this) : this.stroke; + } + }, + _setFillStyles: function(ctx) { + if (this.fill) { + ctx.fillStyle = this.fill.toLive ? this.fill.toLive(ctx, this) : this.fill; + } + }, + _renderControls: function(ctx, noTransform) { + if (!this.active || noTransform) { + return; + } + var vpt = this.getViewportTransform(); + ctx.save(); + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + this.drawBorders(ctx); + this.drawControls(ctx); + ctx.restore(); + }, + _setShadow: function(ctx) { + if (!this.shadow) { + return; + } + var multX = this.canvas && this.canvas.viewportTransform[0] || 1, multY = this.canvas && this.canvas.viewportTransform[3] || 1; + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur * (multX + multY) * (this.scaleX + this.scaleY) / 4; + ctx.shadowOffsetX = this.shadow.offsetX * multX * this.scaleX; + ctx.shadowOffsetY = this.shadow.offsetY * multY * this.scaleY; + }, + _removeShadow: function(ctx) { + if (!this.shadow) { + return; + } + ctx.shadowColor = ""; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + _renderFill: function(ctx) { + if (!this.fill) { + return; + } + ctx.save(); + if (this.fill.gradientTransform) { + var g = this.fill.gradientTransform; + ctx.transform.apply(ctx, g); + } + if (this.fill.toLive) { + ctx.translate(-this.width / 2 + this.fill.offsetX || 0, -this.height / 2 + this.fill.offsetY || 0); + } + if (this.fillRule === "evenodd") { + ctx.fill("evenodd"); + } else { + ctx.fill(); + } + ctx.restore(); + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + }, + _renderStroke: function(ctx) { + if (!this.stroke || this.strokeWidth === 0) { + return; + } + ctx.save(); + if (this.strokeDashArray) { + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + if (supportsLineDash) { + ctx.setLineDash(this.strokeDashArray); + this._stroke && this._stroke(ctx); + } else { + this._renderDashedStroke && this._renderDashedStroke(ctx); + } + ctx.stroke(); + } else { + if (this.stroke.gradientTransform) { + var g = this.stroke.gradientTransform; + ctx.transform.apply(ctx, g); + } + this._stroke ? this._stroke(ctx) : ctx.stroke(); + } + this._removeShadow(ctx); + ctx.restore(); + }, + clone: function(callback, propertiesToInclude) { + if (this.constructor.fromObject) { + return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); + } + return new fabric.Object(this.toObject(propertiesToInclude)); + }, + cloneAsImage: function(callback) { + var dataUrl = this.toDataURL(); + fabric.util.loadImage(dataUrl, function(img) { + if (callback) { + callback(new fabric.Image(img)); + } + }); + return this; + }, + toDataURL: function(options) { + options || (options = {}); + var el = fabric.util.createCanvasElement(), boundingRect = this.getBoundingRect(); + el.width = boundingRect.width; + el.height = boundingRect.height; + fabric.util.wrapElement(el, "div"); + var canvas = new fabric.StaticCanvas(el); + if (options.format === "jpg") { + options.format = "jpeg"; + } + if (options.format === "jpeg") { + canvas.backgroundColor = "#fff"; + } + var origParams = { + active: this.get("active"), + left: this.getLeft(), + top: this.getTop() + }; + this.set("active", false); + this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), "center", "center"); + var originalCanvas = this.canvas; + canvas.add(this); + var data = canvas.toDataURL(options); + this.set(origParams).setCoords(); + this.canvas = originalCanvas; + canvas.dispose(); + canvas = null; + return data; + }, + isType: function(type) { + return this.type === type; + }, + complexity: function() { + return 0; + }, + toJSON: function(propertiesToInclude) { + return this.toObject(propertiesToInclude); + }, + setGradient: function(property, options) { + options || (options = {}); + var gradient = { + colorStops: [] + }; + gradient.type = options.type || (options.r1 || options.r2 ? "radial" : "linear"); + gradient.coords = { + x1: options.x1, + y1: options.y1, + x2: options.x2, + y2: options.y2 + }; + if (options.r1 || options.r2) { + gradient.coords.r1 = options.r1; + gradient.coords.r2 = options.r2; + } + for (var position in options.colorStops) { + var color = new fabric.Color(options.colorStops[position]); + gradient.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); + } + return this.set(property, fabric.Gradient.forObject(this, gradient)); + }, + setPatternFill: function(options) { + return this.set("fill", new fabric.Pattern(options)); + }, + setShadow: function(options) { + return this.set("shadow", options ? new fabric.Shadow(options) : null); + }, + setColor: function(color) { + this.set("fill", color); + return this; + }, + setAngle: function(angle) { + var shouldCenterOrigin = (this.originX !== "center" || this.originY !== "center") && this.centeredRotation; + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + this.set("angle", angle); + if (shouldCenterOrigin) { + this._resetOrigin(); + } + return this; + }, + centerH: function() { + this.canvas.centerObjectH(this); + return this; + }, + centerV: function() { + this.canvas.centerObjectV(this); + return this; + }, + center: function() { + this.canvas.centerObject(this); + return this; + }, + remove: function() { + this.canvas.remove(this); + return this; + }, + getLocalPointer: function(e, pointer) { + pointer = pointer || this.canvas.getPointer(e); + var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), "left", "top"); + return { + x: pointer.x - objectLeftTop.x, + y: pointer.y - objectLeftTop.y + }; + }, + _setupCompositeOperation: function(ctx) { + if (this.globalCompositeOperation) { + this._prevGlobalCompositeOperation = ctx.globalCompositeOperation; + ctx.globalCompositeOperation = this.globalCompositeOperation; + } + }, + _restoreCompositeOperation: function(ctx) { + if (this.globalCompositeOperation && this._prevGlobalCompositeOperation) { + ctx.globalCompositeOperation = this._prevGlobalCompositeOperation; + } + } + }); + fabric.util.createAccessors(fabric.Object); + fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; + extend(fabric.Object.prototype, fabric.Observable); + fabric.Object.NUM_FRACTION_DIGITS = 2; + fabric.Object.__uid = 0; +})(typeof exports !== "undefined" ? exports : this); - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) - * @static - * @memberOf fabric.Line - * @see http://www.w3.org/TR/SVG/shapes.html#LineElement - */ - fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); +(function() { + var degreesToRadians = fabric.util.degreesToRadians; + fabric.util.object.extend(fabric.Object.prototype, { + translateToCenterPoint: function(point, originX, originY) { + var cx = point.x, cy = point.y, strokeWidth = this.stroke ? this.strokeWidth : 0; + if (originX === "left") { + cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else if (originX === "right") { + cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + if (originY === "top") { + cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else if (originY === "bottom") { + cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); + }, + translateToOriginPoint: function(center, originX, originY) { + var x = center.x, y = center.y, strokeWidth = this.stroke ? this.strokeWidth : 0; + if (originX === "left") { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else if (originX === "right") { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + if (originY === "top") { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else if (originY === "bottom") { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); + }, + getCenterPoint: function() { + var leftTop = new fabric.Point(this.left, this.top); + return this.translateToCenterPoint(leftTop, this.originX, this.originY); + }, + getPointByOrigin: function(originX, originY) { + var center = this.getCenterPoint(); + return this.translateToOriginPoint(center, originX, originY); + }, + toLocalPoint: function(point, originX, originY) { + var center = this.getCenterPoint(), strokeWidth = this.stroke ? this.strokeWidth : 0, x, y; + if (originX && originY) { + if (originX === "left") { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else if (originX === "right") { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else { + x = center.x; + } + if (originY === "top") { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else if (originY === "bottom") { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else { + y = center.y; + } + } else { + x = this.left; + y = this.top; + } + return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x, y)); + }, + setPositionByOrigin: function(pos, originX, originY) { + var center = this.translateToCenterPoint(pos, originX, originY), position = this.translateToOriginPoint(center, this.originX, this.originY); + this.set("left", position.x); + this.set("top", position.y); + }, + adjustPosition: function(to) { + var angle = degreesToRadians(this.angle), hypotHalf = this.getWidth() / 2, xHalf = Math.cos(angle) * hypotHalf, yHalf = Math.sin(angle) * hypotHalf, hypotFull = this.getWidth(), xFull = Math.cos(angle) * hypotFull, yFull = Math.sin(angle) * hypotFull; + if (this.originX === "center" && to === "left" || this.originX === "right" && to === "center") { + this.left -= xHalf; + this.top -= yHalf; + } else if (this.originX === "left" && to === "center" || this.originX === "center" && to === "right") { + this.left += xHalf; + this.top += yHalf; + } else if (this.originX === "left" && to === "right") { + this.left += xFull; + this.top += yFull; + } else if (this.originX === "right" && to === "left") { + this.left -= xFull; + this.top -= yFull; + } + this.setCoords(); + this.originX = to; + }, + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + var center = this.getCenterPoint(); + this.originX = "center"; + this.originY = "center"; + this.left = center.x; + this.top = center.y; + }, + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint(this.getCenterPoint(), this._originalOriginX, this._originalOriginY); + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + this.left = originPoint.x; + this.top = originPoint.y; + this._originalOriginX = null; + this._originalOriginY = null; + }, + _getLeftTopCoords: function() { + return this.translateToOriginPoint(this.getCenterPoint(), "left", "center"); + } + }); +})(); - /** - * Returns fabric.Line instance from an SVG element - * @static - * @memberOf fabric.Line - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromElement = function(element, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), - points = [ - parsedAttributes.x1 || 0, - parsedAttributes.y1 || 0, - parsedAttributes.x2 || 0, - parsedAttributes.y2 || 0 - ]; - return new fabric.Line(points, extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ +(function() { + var degreesToRadians = fabric.util.degreesToRadians; + fabric.util.object.extend(fabric.Object.prototype, { + oCoords: null, + intersectsWithRect: function(pointTL, pointBR) { + var oCoords = this.oCoords, tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), br = new fabric.Point(oCoords.br.x, oCoords.br.y), intersection = fabric.Intersection.intersectPolygonRectangle([ tl, tr, br, bl ], pointTL, pointBR); + return intersection.status === "Intersection"; + }, + intersectsWithObject: function(other) { + function getCoords(oCoords) { + return { + tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), + tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), + bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), + br: new fabric.Point(oCoords.br.x, oCoords.br.y) + }; + } + var thisCoords = getCoords(this.oCoords), otherCoords = getCoords(other.oCoords), intersection = fabric.Intersection.intersectPolygonPolygon([ thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl ], [ otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl ]); + return intersection.status === "Intersection"; + }, + isContainedWithinObject: function(other) { + var boundingRect = other.getBoundingRect(), point1 = new fabric.Point(boundingRect.left, boundingRect.top), point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); + return this.isContainedWithinRect(point1, point2); + }, + isContainedWithinRect: function(pointTL, pointBR) { + var boundingRect = this.getBoundingRect(); + return boundingRect.left >= pointTL.x && boundingRect.left + boundingRect.width <= pointBR.x && boundingRect.top >= pointTL.y && boundingRect.top + boundingRect.height <= pointBR.y; + }, + containsPoint: function(point) { + var lines = this._getImageLines(this.oCoords), xPoints = this._findCrossPoints(point, lines); + return xPoints !== 0 && xPoints % 2 === 1; + }, + _getImageLines: function(oCoords) { + return { + topline: { + o: oCoords.tl, + d: oCoords.tr + }, + rightline: { + o: oCoords.tr, + d: oCoords.br + }, + bottomline: { + o: oCoords.br, + d: oCoords.bl + }, + leftline: { + o: oCoords.bl, + d: oCoords.tl + } + }; + }, + _findCrossPoints: function(point, oCoords) { + var b1, b2, a1, a2, xi, yi, xcount = 0, iLine; + for (var lineKey in oCoords) { + iLine = oCoords[lineKey]; + if (iLine.o.y < point.y && iLine.d.y < point.y) { + continue; + } + if (iLine.o.y >= point.y && iLine.d.y >= point.y) { + continue; + } + if (iLine.o.x === iLine.d.x && iLine.o.x >= point.x) { + xi = iLine.o.x; + yi = point.y; + } else { + b1 = 0; + b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); + a1 = point.y - b1 * point.x; + a2 = iLine.o.y - b2 * iLine.o.x; + xi = -(a1 - a2) / (b1 - b2); + yi = a1 + b1 * xi; + } + if (xi >= point.x) { + xcount += 1; + } + if (xcount === 2) { + break; + } + } + return xcount; + }, + getBoundingRectWidth: function() { + return this.getBoundingRect().width; + }, + getBoundingRectHeight: function() { + return this.getBoundingRect().height; + }, + getBoundingRect: function() { + this.oCoords || this.setCoords(); + var xCoords = [ this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x ], minX = fabric.util.array.min(xCoords), maxX = fabric.util.array.max(xCoords), width = Math.abs(minX - maxX), yCoords = [ this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y ], minY = fabric.util.array.min(yCoords), maxY = fabric.util.array.max(yCoords), height = Math.abs(minY - maxY); + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + getWidth: function() { + return this.width * this.scaleX; + }, + getHeight: function() { + return this.height * this.scaleY; + }, + _constrainScale: function(value) { + if (Math.abs(value) < this.minScaleLimit) { + if (value < 0) { + return -this.minScaleLimit; + } else { + return this.minScaleLimit; + } + } + return value; + }, + scale: function(value) { + value = this._constrainScale(value); + if (value < 0) { + this.flipX = !this.flipX; + this.flipY = !this.flipY; + value *= -1; + } + this.scaleX = value; + this.scaleY = value; + this.setCoords(); + return this; + }, + scaleToWidth: function(value) { + var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); + return this.scale(value / this.width / boundingRectFactor); + }, + scaleToHeight: function(value) { + var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); + return this.scale(value / this.height / boundingRectFactor); + }, + setCoords: function() { + var theta = degreesToRadians(this.angle), vpt = this.getViewportTransform(), f = function(p) { + return fabric.util.transformPoint(p, vpt); + }, p = this._calculateCurrentDimensions(false), currentWidth = p.x, currentHeight = p.y; + if (currentWidth < 0) { + currentWidth = Math.abs(currentWidth); + } + var _hypotenuse = Math.sqrt(Math.pow(currentWidth / 2, 2) + Math.pow(currentHeight / 2, 2)), _angle = Math.atan(isFinite(currentHeight / currentWidth) ? currentHeight / currentWidth : 0), offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta), coords = this.getCenterPoint(), wh = new fabric.Point(currentWidth, currentHeight), _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), _tr = new fabric.Point(_tl.x + wh.x * cosTh, _tl.y + wh.x * sinTh), bl = f(new fabric.Point(_tl.x - wh.y * sinTh, _tl.y + wh.y * cosTh)), br = f(new fabric.Point(_tr.x - wh.y * sinTh, _tr.y + wh.y * cosTh)), tl = f(_tl), tr = f(_tr), ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2), mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2), mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2), mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2), mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset); + this.oCoords = { + tl: tl, + tr: tr, + br: br, + bl: bl, + ml: ml, + mt: mt, + mr: mr, + mb: mb, + mtr: mtr + }; + this._setCornerCoords && this._setCornerCoords(); + return this; + } + }); +})(); - /** - * Returns fabric.Line instance from an object representation - * @static - * @memberOf fabric.Line - * @param {Object} object Object to create an instance from - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromObject = function(object) { - var points = [object.x1, object.y1, object.x2, object.y2]; - return new fabric.Line(points, object); - }; +fabric.util.object.extend(fabric.Object.prototype, { + sendToBack: function() { + if (this.group) { + fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); + } else { + this.canvas.sendToBack(this); + } + return this; + }, + bringToFront: function() { + if (this.group) { + fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); + } else { + this.canvas.bringToFront(this); + } + return this; + }, + sendBackwards: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); + } else { + this.canvas.sendBackwards(this, intersecting); + } + return this; + }, + bringForward: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); + } else { + this.canvas.bringForward(this, intersecting); + } + return this; + }, + moveTo: function(index) { + if (this.group) { + fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); + } else { + this.canvas.moveTo(this, index); + } + return this; + } +}); - /** - * Produces a function that calculates distance from canvas edge to Line origin. - */ - function makeEdgeToOriginGetter(propertyNames, originValues) { - var origin = propertyNames.origin, - axis1 = propertyNames.axis1, - axis2 = propertyNames.axis2, - dimension = propertyNames.dimension, - nearest = originValues.nearest, - center = originValues.center, - farthest = originValues.farthest; +fabric.util.object.extend(fabric.Object.prototype, { + getSvgStyles: function() { + var fill = this.fill ? this.fill.toLive ? "url(#SVGID_" + this.fill.id + ")" : this.fill : "none", fillRule = this.fillRule, stroke = this.stroke ? this.stroke.toLive ? "url(#SVGID_" + this.stroke.id + ")" : this.stroke : "none", strokeWidth = this.strokeWidth ? this.strokeWidth : "0", strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(" ") : "", strokeLineCap = this.strokeLineCap ? this.strokeLineCap : "butt", strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : "miter", strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : "4", opacity = typeof this.opacity !== "undefined" ? this.opacity : "1", visibility = this.visible ? "" : " visibility: hidden;", filter = this.shadow ? "filter: url(#SVGID_" + this.shadow.id + ");" : ""; + return [ "stroke: ", stroke, "; ", "stroke-width: ", strokeWidth, "; ", "stroke-dasharray: ", strokeDashArray, "; ", "stroke-linecap: ", strokeLineCap, "; ", "stroke-linejoin: ", strokeLineJoin, "; ", "stroke-miterlimit: ", strokeMiterLimit, "; ", "fill: ", fill, "; ", "fill-rule: ", fillRule, "; ", "opacity: ", opacity, ";", filter, visibility ].join(""); + }, + getSvgTransform: function() { + if (this.group && this.group.type === "path-group") { + return ""; + } + var toFixed = fabric.util.toFixed, angle = this.getAngle(), vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [ 1, 0, 0, 1, 0, 0 ], center = fabric.util.transformPoint(this.getCenterPoint(), vpt), NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, translatePart = this.type === "path-group" ? "" : "translate(" + toFixed(center.x, NUM_FRACTION_DIGITS) + " " + toFixed(center.y, NUM_FRACTION_DIGITS) + ")", anglePart = angle !== 0 ? " rotate(" + toFixed(angle, NUM_FRACTION_DIGITS) + ")" : "", scalePart = this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1 ? "" : " scale(" + toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + " " + toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + ")", addTranslateX = this.type === "path-group" ? this.width * vpt[0] : 0, flipXPart = this.flipX ? " matrix(-1 0 0 1 " + addTranslateX + " 0) " : "", addTranslateY = this.type === "path-group" ? this.height * vpt[3] : 0, flipYPart = this.flipY ? " matrix(1 0 0 -1 0 " + addTranslateY + ")" : ""; + return [ translatePart, anglePart, scalePart, flipXPart, flipYPart ].join(""); + }, + getSvgTransformMatrix: function() { + return this.transformMatrix ? " matrix(" + this.transformMatrix.join(" ") + ") " : ""; + }, + _createBaseSVGMarkup: function() { + var markup = []; + if (this.fill && this.fill.toLive) { + markup.push(this.fill.toSVG(this, false)); + } + if (this.stroke && this.stroke.toLive) { + markup.push(this.stroke.toSVG(this, false)); + } + if (this.shadow) { + markup.push(this.shadow.toSVG(this)); + } + return markup; + } +}); - return function() { - switch (this.get(origin)) { - case nearest: - return Math.min(this.get(axis1), this.get(axis2)); - case center: - return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); - case farthest: - return Math.max(this.get(axis1), this.get(axis2)); - } +fabric.util.object.extend(fabric.Object.prototype, { + hasStateChanged: function() { + return this.stateProperties.some(function(prop) { + return this.get(prop) !== this.originalState[prop]; + }, this); + }, + saveState: function(options) { + this.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + if (options && options.stateProperties) { + options.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + } + return this; + }, + setupState: function() { + this.originalState = {}; + this.saveState(); + return this; + } +}); + +(function() { + var degreesToRadians = fabric.util.degreesToRadians, isVML = function() { + return typeof G_vmlCanvasManager !== "undefined"; }; - - } - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - pi = Math.PI, - extend = fabric.util.object.extend; - - if (fabric.Circle) { - fabric.warn('fabric.Circle is already defined.'); - return; - } - - /** - * Circle class - * @class fabric.Circle - * @extends fabric.Object - * @see {@link fabric.Circle#initialize} for constructor definition - */ - fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'circle', - - /** - * Radius of this circle - * @type Number - * @default - */ - radius: 0, - - /** - * Start angle of the circle, moving clockwise - * @type Number - * @default 0 - */ - startAngle: 0, - - /** - * End angle of the circle - * @type Number - * @default 2Pi - */ - endAngle: pi * 2, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Circle} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - this.set('radius', options.radius || 0); - this.startAngle = options.startAngle || this.startAngle; - this.endAngle = options.endAngle || this.endAngle; - }, - - /** - * @private - * @param {String} key - * @param {Any} value - * @return {fabric.Circle} thisArg - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - - if (key === 'radius') { - this.setRadius(value); - } - - return this; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - radius: this.get('radius'), - startAngle: this.startAngle, - endAngle: this.endAngle - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = 0, y = 0, - angle = (this.endAngle - this.startAngle) % ( 2 * pi); - - if (angle === 0) { - if (this.group && this.group.type === 'path-group') { - x = this.left + this.radius; - y = this.top + this.radius; + fabric.util.object.extend(fabric.Object.prototype, { + _controlsVisibility: null, + _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); + xPoints = this._findCrossPoints({ + x: ex, + y: ey + }, lines); + if (xPoints !== 0 && xPoints % 2 === 1) { + this.__corner = i; + return i; + } + } + return false; + }, + _setCornerCoords: function() { + var coords = this.oCoords, newTheta = degreesToRadians(45 - this.angle), cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), x, y; + for (var point in coords) { + x = coords[point].x; + y = coords[point].y; + coords[point].corner = { + tl: { + x: x - sinHalfOffset, + y: y - cosHalfOffset + }, + tr: { + x: x + cosHalfOffset, + y: y - sinHalfOffset + }, + bl: { + x: x - cosHalfOffset, + y: y + sinHalfOffset + }, + br: { + x: x + sinHalfOffset, + y: y + cosHalfOffset + } + }; + } + }, + _calculateCurrentDimensions: function(shouldTransform) { + var vpt = this.getViewportTransform(), strokeWidth = this.strokeWidth, w = this.width, h = this.height, capped = this.strokeLineCap === "round" || this.strokeLineCap === "square", vLine = this.type === "line" && this.width === 0, hLine = this.type === "line" && this.height === 0, sLine = vLine || hLine, strokeW = capped && hLine || !sLine, strokeH = capped && vLine || !sLine; + if (vLine) { + w = strokeWidth; + } else if (hLine) { + h = strokeWidth; + } + if (strokeW) { + w += w < 0 ? -strokeWidth : strokeWidth; + } + if (strokeH) { + h += h < 0 ? -strokeWidth : strokeWidth; + } + w = w * this.scaleX + 2 * this.padding; + h = h * this.scaleY + 2 * this.padding; + if (shouldTransform) { + return fabric.util.transformPoint(new fabric.Point(w, h), vpt, true); + } + return { + x: w, + y: h + }; + }, + drawBorders: function(ctx) { + if (!this.hasBorders) { + return this; + } + ctx.save(); + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + ctx.strokeStyle = this.borderColor; + ctx.lineWidth = 1 / this.borderScaleFactor; + var wh = this._calculateCurrentDimensions(true), width = wh.x, height = wh.y; + if (this.group) { + width = width * this.group.scaleX; + height = height * this.group.scaleY; + } + ctx.strokeRect(~~-(width / 2) - .5, ~~-(height / 2) - .5, ~~width + 1, ~~height + 1); + if (this.hasRotatingPoint && this.isControlVisible("mtr") && !this.get("lockRotation") && this.hasControls) { + var rotateHeight = -height / 2; + ctx.beginPath(); + ctx.moveTo(0, rotateHeight); + ctx.lineTo(0, rotateHeight - this.rotatingPointOffset); + ctx.closePath(); + ctx.stroke(); + } + ctx.restore(); + return this; + }, + drawControls: function(ctx) { + if (!this.hasControls) { + return this; + } + var wh = this._calculateCurrentDimensions(true), width = wh.x, height = wh.y, left = -(width / 2), top = -(height / 2), scaleOffset = this.cornerSize / 2, methodName = this.transparentCorners ? "strokeRect" : "fillRect"; + ctx.save(); + ctx.lineWidth = 1; + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + ctx.strokeStyle = ctx.fillStyle = this.cornerColor; + this._drawControl("tl", ctx, methodName, left - scaleOffset, top - scaleOffset); + this._drawControl("tr", ctx, methodName, left + width - scaleOffset, top - scaleOffset); + this._drawControl("bl", ctx, methodName, left - scaleOffset, top + height - scaleOffset); + this._drawControl("br", ctx, methodName, left + width - scaleOffset, top + height - scaleOffset); + if (!this.get("lockUniScaling")) { + this._drawControl("mt", ctx, methodName, left + width / 2 - scaleOffset, top - scaleOffset); + this._drawControl("mb", ctx, methodName, left + width / 2 - scaleOffset, top + height - scaleOffset); + this._drawControl("mr", ctx, methodName, left + width - scaleOffset, top + height / 2 - scaleOffset); + this._drawControl("ml", ctx, methodName, left - scaleOffset, top + height / 2 - scaleOffset); + } + if (this.hasRotatingPoint) { + this._drawControl("mtr", ctx, methodName, left + width / 2 - scaleOffset, top - this.rotatingPointOffset - scaleOffset); + } + ctx.restore(); + return this; + }, + _drawControl: function(control, ctx, methodName, left, top) { + if (!this.isControlVisible(control)) { + return; + } + var size = this.cornerSize; + isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size); + ctx[methodName](left, top, size, size); + }, + isControlVisible: function(controlName) { + return this._getControlsVisibility()[controlName]; + }, + setControlVisible: function(controlName, visible) { + this._getControlsVisibility()[controlName] = visible; + return this; + }, + setControlsVisibility: function(options) { + options || (options = {}); + for (var p in options) { + this.setControlVisible(p, options[p]); + } + return this; + }, + _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; } - markup.push( - '\n' - ); - } - else { - var startX = Math.cos(this.startAngle) * this.radius, - startY = Math.sin(this.startAngle) * this.radius, - endX = Math.cos(this.endAngle) * this.radius, - endY = Math.sin(this.endAngle) * this.radius, - largeFlag = angle > pi ? '1' : '0'; + }); +})(); - markup.push( - '\n' - ); - } - - return reviver ? reviver(markup.join('')) : markup.join(''); +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + FX_DURATION: 500, + fxCenterObjectH: function(object, callbacks) { + callbacks = callbacks || {}; + var empty = function() {}, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; + fabric.util.animate({ + startValue: object.get("left"), + endValue: this.getCenter().left, + duration: this.FX_DURATION, + onChange: function(value) { + object.set("left", value); + _this.renderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + return this; }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - ctx.arc(noTransform ? this.left + this.radius : 0, - noTransform ? this.top + this.radius : 0, - this.radius, - this.startAngle, - this.endAngle, false); - this._renderFill(ctx); - this._renderStroke(ctx); + 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; }, - - /** - * Returns horizontal radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusX: function() { - return this.get('radius') * this.get('scaleX'); - }, - - /** - * Returns vertical radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusY: function() { - return this.get('radius') * this.get('scaleY'); - }, - - /** - * Sets radius of an object (and updates width accordingly) - * @return {Number} - */ - setRadius: function(value) { - this.radius = value; - this.set('width', value * 2).set('height', value * 2); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; + 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; } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) - * @static - * @memberOf fabric.Circle - * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement - */ - fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); - - /** - * Returns {@link fabric.Circle} instance from an SVG element - * @static - * @memberOf fabric.Circle - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @throws {Error} If value of `r` attribute is missing or invalid - * @return {fabric.Circle} Instance of fabric.Circle - */ - fabric.Circle.fromElement = function(element, options) { - options || (options = { }); - - var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); - - if (!isValidRadius(parsedAttributes)) { - throw new Error('value of `r` attribute is required and can not be negative'); - } - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - - var obj = new fabric.Circle(extend(parsedAttributes, options)); - - obj.left -= obj.radius; - obj.top -= obj.radius; - return obj; - }; - - /** - * @private - */ - function isValidRadius(attributes) { - return (('radius' in attributes) && (attributes.radius >= 0)); - } - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Circle} instance from an object representation - * @static - * @memberOf fabric.Circle - * @param {Object} object Object to create an instance from - * @return {Object} Instance of fabric.Circle - */ - fabric.Circle.fromObject = function(object) { - return new fabric.Circle(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Triangle) { - fabric.warn('fabric.Triangle is already defined'); - return; - } - - /** - * Triangle class - * @class fabric.Triangle - * @extends fabric.Object - * @return {fabric.Triangle} thisArg - * @see {@link fabric.Triangle#initialize} for constructor definition - */ - fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'triangle', - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('width', options.width || 100) - .set('height', options.height || 100); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - var widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - ctx.beginPath(); - ctx.moveTo(-widthBy2, heightBy2); - ctx.lineTo(0, -heightBy2); - ctx.lineTo(widthBy2, heightBy2); - ctx.closePath(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); - ctx.closePath(); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - widthBy2 = this.width / 2, - heightBy2 = this.height / 2, - points = [ - -widthBy2 + ' ' + heightBy2, - '0 ' + -heightBy2, - widthBy2 + ' ' + heightBy2 - ] - .join(','); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /** - * Returns fabric.Triangle instance from an object representation - * @static - * @memberOf fabric.Triangle - * @param {Object} object Object to create an instance from - * @return {Object} instance of Canvas.Triangle - */ - fabric.Triangle.fromObject = function(object) { - return new fabric.Triangle(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - piBy2 = Math.PI * 2, - extend = fabric.util.object.extend; - - if (fabric.Ellipse) { - fabric.warn('fabric.Ellipse is already defined.'); - return; - } - - /** - * Ellipse class - * @class fabric.Ellipse - * @extends fabric.Object - * @return {fabric.Ellipse} thisArg - * @see {@link fabric.Ellipse#initialize} for constructor definition - */ - fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'ellipse', - - /** - * Horizontal radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical radius - * @type Number - * @default - */ - ry: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Ellipse} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('rx', options.rx || 0); - this.set('ry', options.ry || 0); - }, - - /** - * @private - * @param {String} key - * @param {Any} value - * @return {fabric.Ellipse} thisArg - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - switch (key) { - - case 'rx': - this.rx = value; - this.set('width', value * 2); - break; - - case 'ry': - this.ry = value; - this.set('height', value * 2); - break; - - } - return this; - }, - - /** - * Returns horizontal radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRx: function() { - return this.get('rx') * this.get('scaleX'); - }, - - /** - * Returns Vertical radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRy: function() { - return this.get('ry') * this.get('scaleY'); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - rx: this.get('rx'), - ry: this.get('ry') - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = 0, y = 0; - if (this.group && this.group.type === 'path-group') { - x = this.left + this.rx; - y = this.top + this.ry; - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - ctx.save(); - ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); - ctx.arc( - noTransform ? this.left + this.rx : 0, - noTransform ? (this.top + this.ry) * this.rx/this.ry : 0, - this.rx, - 0, - piBy2, - false); - ctx.restore(); - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) - * @static - * @memberOf fabric.Ellipse - * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement - */ - fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); - - /** - * Returns {@link fabric.Ellipse} instance from an SVG element - * @static - * @memberOf fabric.Ellipse - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromElement = function(element, options) { - options || (options = { }); - - var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - - var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); - - ellipse.top -= ellipse.ry; - ellipse.left -= ellipse.rx; - return ellipse; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Ellipse} instance from an object representation - * @static - * @memberOf fabric.Ellipse - * @param {Object} object Object to create an instance from - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromObject = function(object) { - return new fabric.Ellipse(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - if (fabric.Rect) { - console.warn('fabric.Rect is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push('rx', 'ry', 'x', 'y'); - - /** - * Rectangle class - * @class fabric.Rect - * @extends fabric.Object - * @return {fabric.Rect} thisArg - * @see {@link fabric.Rect#initialize} for constructor definition - */ - fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { - - /** - * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * Type of an object - * @type String - * @default - */ - type: 'rect', - - /** - * Horizontal border radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical border radius - * @type Number - * @default - */ - ry: 0, - - /** - * Used to specify dash pattern for stroke on this object - * @type Array - */ - strokeDashArray: null, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - this._initRxRy(); - - }, - - /** - * Initializes rx/ry attributes - * @private - */ - _initRxRy: function() { - if (this.rx && !this.ry) { - this.ry = this.rx; - } - else if (this.ry && !this.rx) { - this.rx = this.ry; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx, noTransform) { - - // optimize 1x1 case (used in spray brush) - if (this.width === 1 && this.height === 1) { - ctx.fillRect(0, 0, 1, 1); - return; - } - - var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, - ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, - w = this.width, - h = this.height, - x = noTransform ? this.left : -this.width / 2, - y = noTransform ? this.top : -this.height / 2, - isRounded = rx !== 0 || ry !== 0, - k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; - - ctx.beginPath(); - - ctx.moveTo(x + rx, y); - - ctx.lineTo(x + w - rx, y); - isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); - - ctx.lineTo(x + w, y + h - ry); - isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); - - ctx.lineTo(x + rx, y + h); - isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); - - ctx.lineTo(x, y + ry); - isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); - - ctx.closePath(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var x = -this.width / 2, - y = -this.height / 2, - w = this.width, - h = this.height; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); - ctx.closePath(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var object = extend(this.callSuper('toObject', propertiesToInclude), { - rx: this.get('rx') || 0, - ry: this.get('ry') || 0 - }); - if (!this.includeDefaultValues) { - this._removeDefaultValues(object); - } - return object; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; - if (!(this.group && this.group.type === 'path-group')) { - x = -this.width / 2; - y = -this.height / 2; - } - markup.push( - '\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) - * @static - * @memberOf fabric.Rect - * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement - */ - fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); - - /** - * Returns {@link fabric.Rect} instance from an SVG element - * @static - * @memberOf fabric.Rect - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Rect} Instance of fabric.Rect - */ - fabric.Rect.fromElement = function(element, options) { - if (!element) { - return null; - } - options = options || { }; - - var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - rect.visible = rect.width > 0 && rect.height > 0; - return rect; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Rect} instance from an object representation - * @static - * @memberOf fabric.Rect - * @param {Object} object Object to create an instance from - * @return {Object} instance of fabric.Rect - */ - fabric.Rect.fromObject = function(object) { - return new fabric.Rect(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Polyline) { - fabric.warn('fabric.Polyline is already defined'); - return; - } - - /** - * Polyline class - * @class fabric.Polyline - * @extends fabric.Object - * @see {@link fabric.Polyline#initialize} for constructor definition - */ - fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polyline', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Minimum X from points values, necessary to offset points - * @type Number - * @default - */ - minX: 0, - - /** - * Minimum Y from points values, necessary to offset points - * @type Number - * @default - */ - minY: 0, - - /** - * Constructor - * @param {Array} points Array of points (where each point is an object with x and y) - * @param {Object} [options] Options object - * @param {Boolean} [skipOffset] Whether points offsetting should be skipped - * @return {fabric.Polyline} thisArg - * @example - * var poly = new fabric.Polyline([ - * { x: 10, y: 10 }, - * { x: 50, y: 30 }, - * { x: 40, y: 70 }, - * { x: 60, y: 50 }, - * { x: 100, y: 150 }, - * { x: 40, y: 100 } - * ], { - * stroke: 'red', - * left: 100, - * top: 100 - * }); - */ - initialize: function(points, options) { - return fabric.Polygon.prototype.initialize.call(this, points, options); - }, - - /** - * @private - */ - _calcDimensions: function() { - return fabric.Polygon.prototype._calcDimensions.call(this); - }, - - /** - * @private - */ - _applyPointOffset: function() { - return fabric.Polygon.prototype._applyPointOffset.call(this); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - return fabric.Polygon.prototype.toSVG.call(this, reviver); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - if (!fabric.Polygon.prototype.commonRender.call(this, ctx)) { - return; - } - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var p1, p2; - - ctx.beginPath(); - for (var i = 0, len = this.points.length; i < len; i++) { - p1 = this.points[i]; - p2 = this.points[i + 1] || p1; - fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.get('points').length; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) - * @static - * @memberOf fabric.Polyline - * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement - */ - fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); - - /** - * Returns fabric.Polyline instance from an SVG element - * @static - * @memberOf fabric.Polyline - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromElement = function(element, options) { - if (!element) { - return null; - } - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); - - return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polyline instance from an object representation - * @static - * @memberOf fabric.Polyline - * @param {Object} object Object to create an instance from - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromObject = function(object) { - var points = object.points; - return new fabric.Polyline(points, object, true); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - toFixed = fabric.util.toFixed; - - if (fabric.Polygon) { - fabric.warn('fabric.Polygon is already defined'); - return; - } - - /** - * Polygon class - * @class fabric.Polygon - * @extends fabric.Object - * @see {@link fabric.Polygon#initialize} for constructor definition - */ - fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polygon', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Minimum X from points values, necessary to offset points - * @type Number - * @default - */ - minX: 0, - - /** - * Minimum Y from points values, necessary to offset points - * @type Number - * @default - */ - minY: 0, - - /** - * Constructor - * @param {Array} points Array of points - * @param {Object} [options] Options object - * @return {fabric.Polygon} thisArg - */ - initialize: function(points, options) { - options = options || { }; - this.points = points || [ ]; - this.callSuper('initialize', options); - this._calcDimensions(); - if (!('top' in options)) { - this.top = this.minY; - } - if (!('left' in options)) { - this.left = this.minX; - } - }, - - /** - * @private - */ - _calcDimensions: function() { - - var points = this.points, - minX = min(points, 'x'), - minY = min(points, 'y'), - maxX = max(points, 'x'), - maxY = max(points, 'y'); - - this.width = (maxX - minX) || 0; - this.height = (maxY - minY) || 0; - - this.minX = minX || 0, - this.minY = minY || 0; - }, - - /** - * @private - */ - _applyPointOffset: function() { - // change points to offset polygon into a bounding box - // executed one time - this.points.forEach(function(p) { - p.x -= (this.minX + this.width / 2); - p.y -= (this.minY + this.height / 2); - }, this); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - points: this.points.concat() - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var points = [], - markup = this._createBaseSVGMarkup(); - - for (var i = 0, len = this.points.length; i < len; i++) { - points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); - } - - markup.push( - '<', this.type, ' ', - 'points="', points.join(''), - '" style="', this.getSvgStyles(), - '" transform="', this.getSvgTransform(), - ' ', this.getSvgTransformMatrix(), - '"/>\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - if (!this.commonRender(ctx)) { - return; - } - this._renderFill(ctx); - if (this.stroke || this.strokeDashArray) { - ctx.closePath(); - this._renderStroke(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - commonRender: function(ctx) { - var point, len = this.points.length; - - if (!len || isNaN(this.points[len - 1].y)) { - // do not draw if no points or odd points - // NaN comes from parseFloat of a empty string in parser - return false; - } - - ctx.beginPath(); - - if (this._applyPointOffset) { - if (!(this.group && this.group.type === 'path-group')) { - this._applyPointOffset(); +}); + +fabric.util.object.extend(fabric.Object.prototype, { + animate: function() { + if (arguments[0] && typeof arguments[0] === "object") { + var propsToAnimate = [], prop, skipCallbacks; + for (prop in arguments[0]) { + propsToAnimate.push(prop); + } + for (var i = 0, len = propsToAnimate.length; i < len; i++) { + prop = propsToAnimate[i]; + skipCallbacks = i !== len - 1; + this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); + } + } else { + this._animate.apply(this, arguments); } - this._applyPointOffset = null; - } - - ctx.moveTo(this.points[0].x, this.points[0].y); - for (var i = 0; i < len; i++) { - point = this.points[i]; - ctx.lineTo(point.x, point.y); - } - return true; + return this; }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - fabric.Polyline.prototype._renderDashedStroke.call(this, ctx); - ctx.closePath(); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.points.length; + _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(); + } + }); } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) - * @static - * @memberOf fabric.Polygon - * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement - */ - fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); - - /** - * Returns {@link fabric.Polygon} instance from an SVG element - * @static - * @memberOf fabric.Polygon - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromElement = function(element, options) { - if (!element) { - return null; - } - - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); - - return new fabric.Polygon(points, extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polygon instance from an object representation - * @static - * @memberOf fabric.Polygon - * @param {Object} object Object to create an instance from - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromObject = function(object) { - return new fabric.Polygon(object.points, object, true); - }; - -})(typeof exports !== 'undefined' ? exports : this); - +}); (function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, coordProps = { + x1: 1, + x2: 1, + y1: 1, + y2: 1 + }, supportsLineDash = fabric.StaticCanvas.supports("setLineDash"); + if (fabric.Line) { + fabric.warn("fabric.Line is already defined"); + return; + } + fabric.Line = fabric.util.createClass(fabric.Object, { + type: "line", + x1: 0, + y1: 0, + x2: 0, + y2: 0, + initialize: function(points, options) { + options = options || {}; + if (!points) { + points = [ 0, 0, 0, 0 ]; + } + this.callSuper("initialize", options); + this.set("x1", points[0]); + this.set("y1", points[1]); + this.set("x2", points[2]); + this.set("y2", points[3]); + this._setWidthHeight(options); + }, + _setWidthHeight: function(options) { + options || (options = {}); + this.width = Math.abs(this.x2 - this.x1); + this.height = Math.abs(this.y2 - this.y1); + this.left = "left" in options ? options.left : this._getLeftToOriginX(); + this.top = "top" in options ? options.top : this._getTopToOriginY(); + }, + _set: function(key, value) { + this.callSuper("_set", key, value); + if (typeof coordProps[key] !== "undefined") { + this._setWidthHeight(); + } + return this; + }, + _getLeftToOriginX: makeEdgeToOriginGetter({ + origin: "originX", + axis1: "x1", + axis2: "x2", + dimension: "width" + }, { + nearest: "left", + center: "center", + farthest: "right" + }), + _getTopToOriginY: makeEdgeToOriginGetter({ + origin: "originY", + axis1: "y1", + axis2: "y2", + dimension: "height" + }, { + nearest: "top", + center: "center", + farthest: "bottom" + }), + _render: function(ctx, noTransform) { + ctx.beginPath(); + if (noTransform) { + var cp = this.getCenterPoint(); + ctx.translate(cp.x - this.strokeWidth / 2, cp.y - this.strokeWidth / 2); + } + if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { + var p = this.calcLinePoints(); + ctx.moveTo(p.x1, p.y1); + ctx.lineTo(p.x2, p.y2); + } + ctx.lineWidth = this.strokeWidth; + var origStrokeStyle = ctx.strokeStyle; + ctx.strokeStyle = this.stroke || ctx.fillStyle; + this.stroke && this._renderStroke(ctx); + ctx.strokeStyle = origStrokeStyle; + }, + _renderDashedStroke: function(ctx) { + var p = this.calcLinePoints(); + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray); + ctx.closePath(); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), this.calcLinePoints()); + }, + calcLinePoints: function() { + var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1, x1 = xMult * this.width * .5, y1 = yMult * this.height * .5, x2 = xMult * this.width * -.5, y2 = yMult * this.height * -.5; + return { + x1: x1, + x2: x2, + y1: y1, + y2: y2 + }; + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), p = { + x1: this.x1, + x2: this.x2, + y1: this.y1, + y2: this.y2 + }; + if (!(this.group && this.group.type === "path-group")) { + p = this.calcLinePoints(); + } + markup.push("\n'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return 1; + } + }); + fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")); + fabric.Line.fromElement = function(element, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), points = [ parsedAttributes.x1 || 0, parsedAttributes.y1 || 0, parsedAttributes.x2 || 0, parsedAttributes.y2 || 0 ]; + return new fabric.Line(points, extend(parsedAttributes, options)); + }; + fabric.Line.fromObject = function(object) { + var points = [ object.x1, object.y1, object.x2, object.y2 ]; + return new fabric.Line(points, object); + }; + function makeEdgeToOriginGetter(propertyNames, originValues) { + var origin = propertyNames.origin, axis1 = propertyNames.axis1, axis2 = propertyNames.axis2, dimension = propertyNames.dimension, nearest = originValues.nearest, center = originValues.center, farthest = originValues.farthest; + return function() { + switch (this.get(origin)) { + case nearest: + return Math.min(this.get(axis1), this.get(axis2)); - 'use strict'; + case center: + return Math.min(this.get(axis1), this.get(axis2)) + .5 * this.get(dimension); - var fabric = global.fabric || (global.fabric = { }), - min = fabric.util.array.min, - max = fabric.util.array.max, - extend = fabric.util.object.extend, - _toString = Object.prototype.toString, - drawArc = fabric.util.drawArc, - commandLengths = { + case farthest: + return Math.max(this.get(axis1), this.get(axis2)); + } + }; + } +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), pi = Math.PI, extend = fabric.util.object.extend; + if (fabric.Circle) { + fabric.warn("fabric.Circle is already defined."); + return; + } + fabric.Circle = fabric.util.createClass(fabric.Object, { + type: "circle", + radius: 0, + startAngle: 0, + endAngle: pi * 2, + initialize: function(options) { + options = options || {}; + this.callSuper("initialize", options); + this.set("radius", options.radius || 0); + this.startAngle = options.startAngle || this.startAngle; + this.endAngle = options.endAngle || this.endAngle; + }, + _set: function(key, value) { + this.callSuper("_set", key, value); + if (key === "radius") { + this.setRadius(value); + } + return this; + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + radius: this.get("radius"), + startAngle: this.startAngle, + endAngle: this.endAngle + }); + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = 0, y = 0, angle = (this.endAngle - this.startAngle) % (2 * pi); + if (angle === 0) { + if (this.group && this.group.type === "path-group") { + x = this.left + this.radius; + y = this.top + this.radius; + } + markup.push("\n'); + } else { + var startX = Math.cos(this.startAngle) * this.radius, startY = Math.sin(this.startAngle) * this.radius, endX = Math.cos(this.endAngle) * this.radius, endY = Math.sin(this.endAngle) * this.radius, largeFlag = angle > pi ? "1" : "0"; + markup.push('\n'); + } + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.arc(noTransform ? this.left + this.radius : 0, noTransform ? this.top + this.radius : 0, this.radius, this.startAngle, this.endAngle, false); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + getRadiusX: function() { + return this.get("radius") * this.get("scaleX"); + }, + getRadiusY: function() { + return this.get("radius") * this.get("scaleY"); + }, + setRadius: function(value) { + this.radius = value; + this.set("width", value * 2).set("height", value * 2); + }, + complexity: function() { + return 1; + } + }); + fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")); + fabric.Circle.fromElement = function(element, options) { + options || (options = {}); + var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); + if (!isValidRadius(parsedAttributes)) { + throw new Error("value of `r` attribute is required and can not be negative"); + } + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + var obj = new fabric.Circle(extend(parsedAttributes, options)); + obj.left -= obj.radius; + obj.top -= obj.radius; + return obj; + }; + function isValidRadius(attributes) { + return "radius" in attributes && attributes.radius >= 0; + } + fabric.Circle.fromObject = function(object) { + return new fabric.Circle(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Triangle) { + fabric.warn("fabric.Triangle is already defined"); + return; + } + fabric.Triangle = fabric.util.createClass(fabric.Object, { + type: "triangle", + initialize: function(options) { + options = options || {}; + this.callSuper("initialize", options); + this.set("width", options.width || 100).set("height", options.height || 100); + }, + _render: function(ctx) { + var widthBy2 = this.width / 2, heightBy2 = this.height / 2; + ctx.beginPath(); + ctx.moveTo(-widthBy2, heightBy2); + ctx.lineTo(0, -heightBy2); + ctx.lineTo(widthBy2, heightBy2); + ctx.closePath(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + _renderDashedStroke: function(ctx) { + var widthBy2 = this.width / 2, heightBy2 = this.height / 2; + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); + ctx.closePath(); + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), widthBy2 = this.width / 2, heightBy2 = this.height / 2, points = [ -widthBy2 + " " + heightBy2, "0 " + -heightBy2, widthBy2 + " " + heightBy2 ].join(","); + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return 1; + } + }); + fabric.Triangle.fromObject = function(object) { + return new fabric.Triangle(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), piBy2 = Math.PI * 2, extend = fabric.util.object.extend; + if (fabric.Ellipse) { + fabric.warn("fabric.Ellipse is already defined."); + return; + } + fabric.Ellipse = fabric.util.createClass(fabric.Object, { + type: "ellipse", + rx: 0, + ry: 0, + initialize: function(options) { + options = options || {}; + this.callSuper("initialize", options); + this.set("rx", options.rx || 0); + this.set("ry", options.ry || 0); + }, + _set: function(key, value) { + this.callSuper("_set", key, value); + switch (key) { + case "rx": + this.rx = value; + this.set("width", value * 2); + break; + + case "ry": + this.ry = value; + this.set("height", value * 2); + break; + } + return this; + }, + getRx: function() { + return this.get("rx") * this.get("scaleX"); + }, + getRy: function() { + return this.get("ry") * this.get("scaleY"); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + rx: this.get("rx"), + ry: this.get("ry") + }); + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = 0, y = 0; + if (this.group && this.group.type === "path-group") { + x = this.left + this.rx; + y = this.top + this.ry; + } + markup.push("\n'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.save(); + ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0); + ctx.arc(noTransform ? this.left + this.rx : 0, noTransform ? (this.top + this.ry) * this.rx / this.ry : 0, this.rx, 0, piBy2, false); + ctx.restore(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + complexity: function() { + return 1; + } + }); + fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")); + fabric.Ellipse.fromElement = function(element, options) { + options || (options = {}); + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); + ellipse.top -= ellipse.ry; + ellipse.left -= ellipse.rx; + return ellipse; + }; + fabric.Ellipse.fromObject = function(object) { + return new fabric.Ellipse(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + if (fabric.Rect) { + console.warn("fabric.Rect is already defined"); + return; + } + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push("rx", "ry", "x", "y"); + fabric.Rect = fabric.util.createClass(fabric.Object, { + stateProperties: stateProperties, + type: "rect", + rx: 0, + ry: 0, + strokeDashArray: null, + initialize: function(options) { + options = options || {}; + this.callSuper("initialize", options); + this._initRxRy(); + }, + _initRxRy: function() { + if (this.rx && !this.ry) { + this.ry = this.rx; + } else if (this.ry && !this.rx) { + this.rx = this.ry; + } + }, + _render: function(ctx, noTransform) { + if (this.width === 1 && this.height === 1) { + ctx.fillRect(0, 0, 1, 1); + return; + } + var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, w = this.width, h = this.height, x = noTransform ? this.left : -this.width / 2, y = noTransform ? this.top : -this.height / 2, isRounded = rx !== 0 || ry !== 0, k = 1 - .5522847498; + ctx.beginPath(); + ctx.moveTo(x + rx, y); + ctx.lineTo(x + w - rx, y); + isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); + ctx.lineTo(x + w, y + h - ry); + isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); + ctx.lineTo(x + rx, y + h); + isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); + ctx.lineTo(x, y + ry); + isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); + ctx.closePath(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); + ctx.closePath(); + }, + toObject: function(propertiesToInclude) { + var object = extend(this.callSuper("toObject", propertiesToInclude), { + rx: this.get("rx") || 0, + ry: this.get("ry") || 0 + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; + if (!(this.group && this.group.type === "path-group")) { + x = -this.width / 2; + y = -this.height / 2; + } + markup.push("\n'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return 1; + } + }); + fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")); + fabric.Rect.fromElement = function(element, options) { + if (!element) { + return null; + } + options = options || {}; + var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + var rect = new fabric.Rect(extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes)); + rect.visible = rect.width > 0 && rect.height > 0; + return rect; + }; + fabric.Rect.fromObject = function(object) { + return new fabric.Rect(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Polyline) { + fabric.warn("fabric.Polyline is already defined"); + return; + } + fabric.Polyline = fabric.util.createClass(fabric.Object, { + type: "polyline", + points: null, + minX: 0, + minY: 0, + initialize: function(points, options) { + return fabric.Polygon.prototype.initialize.call(this, points, options); + }, + _calcDimensions: function() { + return fabric.Polygon.prototype._calcDimensions.call(this); + }, + _applyPointOffset: function() { + return fabric.Polygon.prototype._applyPointOffset.call(this); + }, + toObject: function(propertiesToInclude) { + return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); + }, + toSVG: function(reviver) { + return fabric.Polygon.prototype.toSVG.call(this, reviver); + }, + _render: function(ctx) { + if (!fabric.Polygon.prototype.commonRender.call(this, ctx)) { + return; + } + this._renderFill(ctx); + this._renderStroke(ctx); + }, + _renderDashedStroke: function(ctx) { + var p1, p2; + ctx.beginPath(); + for (var i = 0, len = this.points.length; i < len; i++) { + p1 = this.points[i]; + p2 = this.points[i + 1] || p1; + fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); + } + }, + complexity: function() { + return this.get("points").length; + } + }); + fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + fabric.Polyline.fromElement = function(element, options) { + if (!element) { + return null; + } + options || (options = {}); + var points = fabric.parsePointsAttribute(element.getAttribute("points")), parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); + return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); + }; + fabric.Polyline.fromObject = function(object) { + var points = object.points; + return new fabric.Polyline(points, object, true); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, toFixed = fabric.util.toFixed; + if (fabric.Polygon) { + fabric.warn("fabric.Polygon is already defined"); + return; + } + fabric.Polygon = fabric.util.createClass(fabric.Object, { + type: "polygon", + points: null, + minX: 0, + minY: 0, + initialize: function(points, options) { + options = options || {}; + this.points = points || []; + this.callSuper("initialize", options); + this._calcDimensions(); + if (!("top" in options)) { + this.top = this.minY; + } + if (!("left" in options)) { + this.left = this.minX; + } + }, + _calcDimensions: function() { + var points = this.points, minX = min(points, "x"), minY = min(points, "y"), maxX = max(points, "x"), maxY = max(points, "y"); + this.width = maxX - minX || 0; + this.height = maxY - minY || 0; + this.minX = minX || 0, this.minY = minY || 0; + }, + _applyPointOffset: function() { + this.points.forEach(function(p) { + p.x -= this.minX + this.width / 2; + p.y -= this.minY + this.height / 2; + }, this); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + points: this.points.concat() + }); + }, + toSVG: function(reviver) { + var points = [], markup = this._createBaseSVGMarkup(); + for (var i = 0, len = this.points.length; i < len; i++) { + points.push(toFixed(this.points[i].x, 2), ",", toFixed(this.points[i].y, 2), " "); + } + markup.push("<", this.type, " ", 'points="', points.join(""), '" style="', this.getSvgStyles(), '" transform="', this.getSvgTransform(), " ", this.getSvgTransformMatrix(), '"/>\n'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _render: function(ctx) { + if (!this.commonRender(ctx)) { + return; + } + this._renderFill(ctx); + if (this.stroke || this.strokeDashArray) { + ctx.closePath(); + this._renderStroke(ctx); + } + }, + commonRender: function(ctx) { + var point, len = this.points.length; + if (!len || isNaN(this.points[len - 1].y)) { + return false; + } + ctx.beginPath(); + if (this._applyPointOffset) { + if (!(this.group && this.group.type === "path-group")) { + this._applyPointOffset(); + } + this._applyPointOffset = null; + } + ctx.moveTo(this.points[0].x, this.points[0].y); + for (var i = 0; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x, point.y); + } + return true; + }, + _renderDashedStroke: function(ctx) { + fabric.Polyline.prototype._renderDashedStroke.call(this, ctx); + ctx.closePath(); + }, + complexity: function() { + return this.points.length; + } + }); + fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + fabric.Polygon.fromElement = function(element, options) { + if (!element) { + return null; + } + options || (options = {}); + var points = fabric.parsePointsAttribute(element.getAttribute("points")), parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); + return new fabric.Polygon(points, extend(parsedAttributes, options)); + }; + fabric.Polygon.fromObject = function(object) { + return new fabric.Polygon(object.points, object, true); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), min = fabric.util.array.min, max = fabric.util.array.max, extend = fabric.util.object.extend, _toString = Object.prototype.toString, drawArc = fabric.util.drawArc, commandLengths = { m: 2, l: 2, h: 1, @@ -15087,8376 +7105,4061 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot q: 4, t: 2, a: 7 - }, - repeatedCommands = { - m: 'l', - M: 'L' - }; - - if (fabric.Path) { - fabric.warn('fabric.Path is already defined'); - return; - } - - /** - * Path class - * @class fabric.Path - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} - * @see {@link fabric.Path#initialize} for constructor definition - */ - fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path', - - /** - * Array of path points - * @type Array - * @default - */ - path: null, - - /** - * Minimum X from points values, necessary to offset points - * @type Number - * @default - */ - minX: 0, - - /** - * Minimum Y from points values, necessary to offset points - * @type Number - * @default - */ - minY: 0, - - /** - * Constructor - * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) - * @param {Object} [options] Options object - * @return {fabric.Path} thisArg - */ - initialize: function(path, options) { - options = options || { }; - - this.setOptions(options); - - if (!path) { - throw new Error('`path` argument is required'); - } - - var fromArray = _toString.call(path) === '[object Array]'; - - this.path = fromArray - ? path - // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) - : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); - - if (!this.path) { - return; - } - - if (!fromArray) { - this.path = this._parsePath(); - } - - this._setPositionDimensions(); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * @private - */ - _setPositionDimensions: function() { - var calcDim = this._parseDimensions(); - - this.minX = calcDim.left; - this.minY = calcDim.top; - this.width = calcDim.width; - this.height = calcDim.height; - - calcDim.left += this.originX === 'center' - ? this.width / 2 - : this.originX === 'right' - ? this.width - : 0; - - calcDim.top += this.originY === 'center' - ? this.height / 2 - : this.originY === 'bottom' - ? this.height - : 0; - - this.top = this.top || calcDim.top; - this.left = this.left || calcDim.left; - - this.pathOffset = this.pathOffset || { - x: this.minX + this.width / 2, - y: this.minY + this.height / 2 - }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render path on - */ - _render: function(ctx) { - var current, // current instruction - previous = null, - subpathStartX = 0, - subpathStartY = 0, - x = 0, // current x - y = 0, // current y - controlX = 0, // current control point x - controlY = 0, // current control point y - tempX, - tempY, - l = -this.pathOffset.x, - t = -this.pathOffset.y; - - if (this.group && this.group.type === 'path-group') { - l = 0; - t = 0; - } - - ctx.beginPath(); - - for (var i = 0, len = this.path.length; i < len; ++i) { - - current = this.path[i]; - - switch (current[0]) { // first letter - - case 'l': // lineto, relative - x += current[1]; - y += current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'L': // lineto, absolute - x = current[1]; - y = current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'h': // horizontal lineto, relative - x += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'H': // horizontal lineto, absolute - x = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'v': // vertical lineto, relative - y += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'V': // verical lineto, absolute - y = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'm': // moveTo, relative - x += current[1]; - y += current[2]; - subpathStartX = x; - subpathStartY = y; - ctx.moveTo(x + l, y + t); - break; - - case 'M': // moveTo, absolute - x = current[1]; - y = current[2]; - subpathStartX = x; - subpathStartY = y; - ctx.moveTo(x + l, y + t); - break; - - case 'c': // bezierCurveTo, relative - tempX = x + current[5]; - tempY = y + current[6]; - controlX = x + current[3]; - controlY = y + current[4]; - ctx.bezierCurveTo( - x + current[1] + l, // x1 - y + current[2] + t, // y1 - controlX + l, // x2 - controlY + t, // y2 - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'C': // bezierCurveTo, absolute - x = current[5]; - y = current[6]; - controlX = current[3]; - controlY = current[4]; - ctx.bezierCurveTo( - current[1] + l, - current[2] + t, - controlX + l, - controlY + t, - x + l, - y + t - ); - break; - - case 's': // shorthand cubic bezierCurveTo, relative - - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - ctx.bezierCurveTo( - controlX + l, - controlY + t, - x + current[1] + l, - y + current[2] + t, - tempX + l, - tempY + t - ); - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = x + current[1]; - controlY = y + current[2]; - - x = tempX; - y = tempY; - break; - - case 'S': // shorthand cubic bezierCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - ctx.bezierCurveTo( - controlX + l, - controlY + t, - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = current[1]; - controlY = current[2]; - - break; - - case 'q': // quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - controlX = x + current[1]; - controlY = y + current[2]; - - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'Q': // quadraticCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - - ctx.quadraticCurveTo( - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - controlX = current[1]; - controlY = current[2]; - break; - - case 't': // shorthand quadraticCurveTo, relative - - // transform to absolute x,y - tempX = x + current[1]; - tempY = y + current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - - break; - - case 'T': - tempX = current[1]; - tempY = current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'a': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + x + l, - current[7] + y + t - ]); - x += current[6]; - y += current[7]; - break; - - case 'A': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + l, - current[7] + t - ]); - x = current[6]; - y = current[7]; - break; - - case 'z': - case 'Z': - x = subpathStartX; - y = subpathStartY; - ctx.closePath(); - break; - } - previous = current; - } - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns string representation of an instance - * @return {String} string representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(this.callSuper('toObject', propertiesToInclude), { - path: this.path.map(function(item) { return item.slice() }), - pathOffset: this.pathOffset - }); - if (this.sourcePath) { - o.sourcePath = this.sourcePath; - } - if (this.transformMatrix) { - o.transformMatrix = this.transformMatrix; - } - return o; - }, - - /** - * Returns dataless object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.path = this.sourcePath; - } - delete o.sourcePath; - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var chunks = [], - markup = this._createBaseSVGMarkup(), addTransform = ''; - - for (var i = 0, len = this.path.length; i < len; i++) { - chunks.push(this.path[i].join(' ')); - } - var path = chunks.join(' '); - if (!(this.group && this.group.type === 'path-group')) { - addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; - } - markup.push( - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns number representation of an instance complexity - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.path.length; - }, - - /** - * @private - */ - _parsePath: function() { - var result = [ ], - coords = [ ], - currentPath, - parsed, - re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, - match, - coordsStr; - - for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { - currentPath = this.path[i]; - - coordsStr = currentPath.slice(1).trim(); - coords.length = 0; - - while ((match = re.exec(coordsStr))) { - coords.push(match[0]); - } - - coordsParsed = [ currentPath.charAt(0) ]; - - for (var j = 0, jlen = coords.length; j < jlen; j++) { - parsed = parseFloat(coords[j]); - if (!isNaN(parsed)) { - coordsParsed.push(parsed); - } - } - - var command = coordsParsed[0], - commandLength = commandLengths[command.toLowerCase()], - repeatedCommand = repeatedCommands[command] || command; - - if (coordsParsed.length - 1 > commandLength) { - for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { - result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); - command = repeatedCommand; - } - } - else { - result.push(coordsParsed); - } - } - - return result; - }, - - /** - * @private - */ - _parseDimensions: function() { - - var aX = [], - aY = [], - current, // current instruction - previous = null, - subpathStartX = 0, - subpathStartY = 0, - x = 0, // current x - y = 0, // current y - controlX = 0, // current control point x - controlY = 0, // current control point y - tempX, - tempY, - bounds; - - for (var i = 0, len = this.path.length; i < len; ++i) { - - current = this.path[i]; - - switch (current[0]) { // first letter - - case 'l': // lineto, relative - x += current[1]; - y += current[2]; - bounds = [ ]; - break; - - case 'L': // lineto, absolute - x = current[1]; - y = current[2]; - bounds = [ ]; - break; - - case 'h': // horizontal lineto, relative - x += current[1]; - bounds = [ ]; - break; - - case 'H': // horizontal lineto, absolute - x = current[1]; - bounds = [ ]; - break; - - case 'v': // vertical lineto, relative - y += current[1]; - bounds = [ ]; - break; - - case 'V': // verical lineto, absolute - y = current[1]; - bounds = [ ]; - break; - - case 'm': // moveTo, relative - x += current[1]; - y += current[2]; - subpathStartX = x; - subpathStartY = y; - bounds = [ ]; - break; - - case 'M': // moveTo, absolute - x = current[1]; - y = current[2]; - subpathStartX = x; - subpathStartY = y; - bounds = [ ]; - break; - - case 'c': // bezierCurveTo, relative - tempX = x + current[5]; - tempY = y + current[6]; - controlX = x + current[3]; - controlY = y + current[4]; - bounds = fabric.util.getBoundsOfCurve(x, y, - x + current[1], // x1 - y + current[2], // y1 - controlX, // x2 - controlY, // y2 - tempX, - tempY - ); - x = tempX; - y = tempY; - break; - - case 'C': // bezierCurveTo, absolute - x = current[5]; - y = current[6]; - controlX = current[3]; - controlY = current[4]; - bounds = fabric.util.getBoundsOfCurve(x, y, - current[1], - current[2], - controlX, - controlY, - x, - y - ); - break; - - case 's': // shorthand cubic bezierCurveTo, relative - - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - x + current[1], - y + current[2], - tempX, - tempY - ); - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = x + current[1]; - controlY = y + current[2]; - x = tempX; - y = tempY; - break; - - case 'S': // shorthand cubic bezierCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - current[1], - current[2], - tempX, - tempY - ); - x = tempX; - y = tempY; - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = current[1]; - controlY = current[2]; - break; - - case 'q': // quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - controlX = x + current[1]; - controlY = y + current[2]; - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - tempX, - tempY - ); - x = tempX; - y = tempY; - break; - - case 'Q': // quadraticCurveTo, absolute - controlX = current[1]; - controlY = current[2]; - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - current[3], - current[4] - ); - x = current[3]; - y = current[4]; - break; - - case 't': // shorthand quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[1]; - tempY = y + current[2]; - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - tempX, - tempY - ); - x = tempX; - y = tempY; - - break; - - case 'T': - tempX = current[1]; - tempY = current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - tempX, - tempY - ); - x = tempX; - y = tempY; - break; - - case 'a': - // TODO: optimize this - bounds = fabric.util.getBoundsOfArc(x, y, - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + x, - current[7] + y - ); - x += current[6]; - y += current[7]; - break; - - case 'A': - // TODO: optimize this - bounds = fabric.util.getBoundsOfArc(x, y, - current[1], - current[2], - current[3], - current[4], - current[5], - current[6], - current[7] - ); - x = current[6]; - y = current[7]; - break; - - case 'z': - case 'Z': - x = subpathStartX; - y = subpathStartY; - break; - } - previous = current; - bounds.forEach(function (point) { - aX.push(point.x); - aY.push(point.y); - }); - aX.push(x); - aY.push(y); - } - - var minX = min(aX), - minY = min(aY), - maxX = max(aX), - maxY = max(aY), - deltaX = maxX - minX, - deltaY = maxY - minY, - - o = { - left: minX, - top: minY, - width: deltaX, - height: deltaY - }; - - return o; - } - }); - - /** - * Creates an instance of fabric.Path from an object - * @static - * @memberOf fabric.Path - * @param {Object} object - * @param {Function} callback Callback to invoke when an fabric.Path instance is created - */ - fabric.Path.fromObject = function(object, callback) { - if (typeof object.path === 'string') { - fabric.loadSVGFromURL(object.path, function (elements) { - var path = elements[0], - pathUrl = object.path; - - delete object.path; - - fabric.util.object.extend(path, object); - path.setSourcePath(pathUrl); - - callback(path); - }); - } - else { - callback(new fabric.Path(object.path, object)); - } - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) - * @static - * @memberOf fabric.Path - * @see http://www.w3.org/TR/SVG/paths.html#PathElement - */ - fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); - - /** - * Creates an instance of fabric.Path from an SVG element - * @static - * @memberOf fabric.Path - * @param {SVGElement} element to parse - * @param {Function} callback Callback to invoke when an fabric.Path instance is created - * @param {Object} [options] Options object - */ - fabric.Path.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); - callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Path - * @type Boolean - * @default - */ - fabric.Path.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - invoke = fabric.util.array.invoke, - parentToObject = fabric.Object.prototype.toObject; - - if (fabric.PathGroup) { - fabric.warn('fabric.PathGroup is already defined'); - return; - } - - /** - * Path group class - * @class fabric.PathGroup - * @extends fabric.Path - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} - * @see {@link fabric.PathGroup#initialize} for constructor definition - */ - fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path-group', - - /** - * Fill value - * @type String - * @default - */ - fill: '', - - /** - * Constructor - * @param {Array} paths - * @param {Object} [options] Options object - * @return {fabric.PathGroup} thisArg - */ - initialize: function(paths, options) { - - options = options || { }; - this.paths = paths || [ ]; - - for (var i = this.paths.length; i--;) { - this.paths[i].group = this; - } - - if (options.toBeParsed) { - this.parseDimensionsFromPaths(options); - delete options.toBeParsed; - } - this.setOptions(options); - this.setCoords(); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * Calculate width and height based on paths contained - */ - parseDimensionsFromPaths: function(options) { - var points, p, xC = [ ], yC = [ ], path, height, width, - m = this.transformMatrix; - for (var j = this.paths.length; j--;) { - path = this.paths[j]; - height = path.height + path.strokeWidth; - width = path.width + path.strokeWidth; - points = [ - { x: path.left, y: path.top }, - { x: path.left + width, y: path.top }, - { x: path.left, y: path.top + height }, - { x: path.left + width, y: path.top + height } - ]; - for (var i = 0; i < points.length; i++) { - p = points[i]; - if (m) { - p = fabric.util.transformPoint(p, m, false); - } - xC.push(p.x); - yC.push(p.y); - } - } - options.width = Math.max.apply(null, xC); - options.height = Math.max.apply(null, yC); - }, - - /** - * Renders this group on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render this instance on - */ - render: function(ctx) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - - if (this.transformMatrix) { - ctx.transform.apply(ctx, this.transformMatrix); - } - this.transform(ctx); - - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - ctx.translate(-this.width/2, -this.height/2); - for (var i = 0, l = this.paths.length; i < l; ++i) { - this.paths[i].render(ctx, true); - } - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * Sets certain property to a certain value - * @param {String} prop - * @param {Any} value - * @return {fabric.PathGroup} thisArg - */ - _set: function(prop, value) { - - if (prop === 'fill' && value && this.isSameColor()) { - var i = this.paths.length; - while (i--) { - this.paths[i]._set(prop, value); - } - } - - return this.callSuper('_set', prop, value); - }, - - /** - * Returns object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(parentToObject.call(this, propertiesToInclude), { - paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) - }); - if (this.sourcePath) { - o.sourcePath = this.sourcePath; - } - return o; - }, - - /** - * Returns dataless object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} dataless object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.paths = this.sourcePath; - } - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var objects = this.getObjects(), - p = this.getPointByOrigin('left', 'top'), - translatePart = 'translate(' + p.x + ' ' + p.y + ')', - markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - - for (var i = 0, len = objects.length; i < len; i++) { - markup.push(objects[i].toSVG(reviver)); - } - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns a string representation of this path group - * @return {String} string representation of an object - */ - toString: function() { - return '#'; - }, - - /** - * Returns true if all paths in this group are of same color - * @return {Boolean} true if all paths are of the same color (`fill`) - */ - isSameColor: function() { - var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); - return this.getObjects().every(function(path) { - return (path.get('fill') || '').toLowerCase() === firstPathFill; - }); - }, - - /** - * Returns number representation of object's complexity - * @return {Number} complexity - */ - complexity: function() { - return this.paths.reduce(function(total, path) { - return total + ((path && path.complexity) ? path.complexity() : 0); - }, 0); - }, - - /** - * Returns all paths in this path group - * @return {Array} array of path objects included in this path group - */ - getObjects: function() { - return this.paths; - } - }); - - /** - * Creates fabric.PathGroup instance from an object representation - * @static - * @memberOf fabric.PathGroup - * @param {Object} object Object to create an instance from - * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created - */ - fabric.PathGroup.fromObject = function(object, callback) { - if (typeof object.paths === 'string') { - fabric.loadSVGFromURL(object.paths, function (elements) { - - var pathUrl = object.paths; - delete object.paths; - - var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); - - callback(pathGroup); - }); - } - else { - fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { - delete object.paths; - callback(new fabric.PathGroup(enlivenedObjects, object)); - }); - } - }; - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.PathGroup - * @type Boolean - * @default - */ - fabric.PathGroup.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - invoke = fabric.util.array.invoke; - - if (fabric.Group) { - return; - } - - // lock-related properties, for use in fabric.Group#get - // to enable locking behavior on group - // when one of its objects has lock-related properties set - var _lockProperties = { - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - lockUniScaling: true - }; - - /** - * Group class - * @class fabric.Group - * @extends fabric.Object - * @mixes fabric.Collection - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} - * @see {@link fabric.Group#initialize} for constructor definition - */ - fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'group', - - /** - * Constructor - * @param {Object} objects Group objects - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(objects, options) { - options = options || { }; - - this._objects = objects || []; - for (var i = this._objects.length; i--; ) { - this._objects[i].group = this; - } - - this.originalState = { }; - this.callSuper('initialize'); - - if (options.originX) { - this.originX = options.originX; - } - - if (options.originY) { - this.originY = options.originY; - } - - this._calcBounds(); - this._updateObjectsCoords(); - - this.callSuper('initialize', options); - - this.setCoords(); - this.saveCoords(); - }, - - /** - * @private - */ - _updateObjectsCoords: function() { - this.forEachObject(this._updateObjectCoords, this); - }, - - /** - * @private - */ - _updateObjectCoords: function(object) { - var objectLeft = object.getLeft(), - objectTop = object.getTop(), - center = this.getCenterPoint(); - - object.set({ - originalLeft: objectLeft, - originalTop: objectTop, - left: objectLeft - center.x, - top: objectTop - center.y - }); - - object.setCoords(); - - // do not display corners of objects enclosed in a group - object.__origHasControls = object.hasControls; - object.hasControls = false; - }, - - /** - * Returns string represenation of a group - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Adds an object to a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - addWithUpdate: function(object) { - this._restoreObjectsState(); - if (object) { - this._objects.push(object); - object.group = this; - } - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - this._calcBounds(); - this._updateObjectsCoords(); - return this; - }, - - /** - * @private - */ - _setObjectActive: function(object) { - object.set('active', true); - object.group = this; - }, - - /** - * Removes an object from a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - removeWithUpdate: function(object) { - this._moveFlippedObject(object); - this._restoreObjectsState(); - - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - - this.remove(object); - this._calcBounds(); - this._updateObjectsCoords(); - - return this; - }, - - /** - * @private - */ - _onObjectAdded: function(object) { - object.group = this; - }, - - /** - * @private - */ - _onObjectRemoved: function(object) { - delete object.group; - object.set('active', false); - }, - - /** - * Properties that are delegated to group objects when reading/writing - * @param {Object} delegatedProperties - */ - delegatedProperties: { - fill: true, - opacity: true, - fontFamily: true, - fontWeight: true, - fontSize: true, - fontStyle: true, - lineHeight: true, - textDecoration: true, - textAlign: true, - backgroundColor: true - }, - - /** - * @private - */ - _set: function(key, value) { - if (key in this.delegatedProperties) { - var i = this._objects.length; - while (i--) { - this._objects[i].set(key, value); - } - } - this.callSuper('_set', key, value); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - objects: invoke(this._objects, 'toObject', propertiesToInclude) - }); - }, - - /** - * Renders instance on a given context - * @param {CanvasRenderingContext2D} ctx context to render instance on - */ - render: function(ctx) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - this.clipTo && fabric.util.clipContext(this, ctx); - this.transform(ctx); - // the array is now sorted in order of highest first, so start from end - for (var i = 0, len = this._objects.length; i < len; i++) { - this._renderObject(this._objects[i], ctx); - } - - this.clipTo && ctx.restore(); - - ctx.restore(); - }, - - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - this.callSuper('_renderControls', ctx, noTransform); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i]._renderControls(ctx); - } - }, - - /** - * @private - */ - _renderObject: function(object, ctx) { - var originalHasRotatingPoint = object.hasRotatingPoint; - - // do not render if object is not visible - if (!object.visible) { - return; - } - - object.hasRotatingPoint = false; - - object.render(ctx); - - object.hasRotatingPoint = originalHasRotatingPoint; - }, - - /** - * Retores original state of each of group objects (original state is that which was before group was created). - * @private - * @return {fabric.Group} thisArg - * @chainable - */ - _restoreObjectsState: function() { - this._objects.forEach(this._restoreObjectState, this); - return this; - }, - - /** - * Realises the transform from this group onto the supplied object - * i.e. it tells you what would happen if the supplied object was in - * the group, and then the group was destroyed. It mutates the supplied - * object. - * @param {fabric.Object} object - * @return {fabric.Object} transformedObject - */ - realizeTransform: function(object) { - this._moveFlippedObject(object); - this._setObjectPosition(object); - return object; - }, - /** - * Moves a flipped object to the position where it's displayed - * @private - * @param {fabric.Object} object - * @return {fabric.Group} thisArg - */ - _moveFlippedObject: function(object) { - var oldOriginX = object.get('originX'), - oldOriginY = object.get('originY'), - center = object.getCenterPoint(); - - object.set({ - originX: 'center', - originY: 'center', - left: center.x, - top: center.y - }); - - this._toggleFlipping(object); - - var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); - - object.set({ - originX: oldOriginX, - originY: oldOriginY, - left: newOrigin.x, - top: newOrigin.y - }); - - return this; - }, - - /** - * @private - */ - _toggleFlipping: function(object) { - if (this.flipX) { - object.toggle('flipX'); - object.set('left', -object.get('left')); - object.setAngle(-object.getAngle()); - } - if (this.flipY) { - object.toggle('flipY'); - object.set('top', -object.get('top')); - object.setAngle(-object.getAngle()); - } - }, - - /** - * Restores original state of a specified object in group - * @private - * @param {fabric.Object} object - * @return {fabric.Group} thisArg - */ - _restoreObjectState: function(object) { - this._setObjectPosition(object); - - object.setCoords(); - object.hasControls = object.__origHasControls; - delete object.__origHasControls; - object.set('active', false); - object.setCoords(); - delete object.group; - - return this; - }, - - /** - * @private - */ - _setObjectPosition: function(object) { - var center = this.getCenterPoint(), - rotated = this._getRotatedLeftTop(object); - - object.set({ - angle: object.getAngle() + this.getAngle(), - left: center.x + rotated.left, - top: center.y + rotated.top, - scaleX: object.get('scaleX') * this.get('scaleX'), - scaleY: object.get('scaleY') * this.get('scaleY') - }); - }, - - /** - * @private - */ - _getRotatedLeftTop: function(object) { - var groupAngle = this.getAngle() * (Math.PI / 180); - return { - left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') + - Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')), - - top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') + - Math.sin(groupAngle) * object.getLeft() * this.get('scaleX')) - }; - }, - - /** - * Destroys a group (restoring state of its objects) - * @return {fabric.Group} thisArg - * @chainable - */ - destroy: function() { - this._objects.forEach(this._moveFlippedObject, this); - return this._restoreObjectsState(); - }, - - /** - * Saves coordinates of this instance (to be used together with `hasMoved`) - * @saveCoords - * @return {fabric.Group} thisArg - * @chainable - */ - saveCoords: function() { - this._originalLeft = this.get('left'); - this._originalTop = this.get('top'); - return this; - }, - - /** - * Checks whether this group was moved (since `saveCoords` was called last) - * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) - */ - hasMoved: function() { - return this._originalLeft !== this.get('left') || - this._originalTop !== this.get('top'); - }, - - /** - * Sets coordinates of all group objects - * @return {fabric.Group} thisArg - * @chainable - */ - setObjectsCoords: function() { - this.forEachObject(function(object) { - object.setCoords(); - }); - return this; - }, - - /** - * @private - */ - _calcBounds: function(onlyWidthHeight) { - var aX = [], - aY = [], - o, prop, - props = ['tr', 'br', 'bl', 'tl']; - - for (var i = 0, len = this._objects.length; i < len; ++i) { - o = this._objects[i]; - o.setCoords(); - for (var j = 0; j < props.length; j++) { - prop = props[j]; - aX.push(o.oCoords[prop].x); - aY.push(o.oCoords[prop].y); - } - } - - this.set(this._getBounds(aX, aY, onlyWidthHeight)); - }, - - /** - * @private - */ - _getBounds: function(aX, aY, onlyWidthHeight) { - var ivt = fabric.util.invertTransform(this.getViewportTransform()), - minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), - maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), - obj = { - width: (maxXY.x - minXY.x) || 0, - height: (maxXY.y - minXY.y) || 0 - }; - - if (!onlyWidthHeight) { - obj.left = minXY.x || 0; - obj.top = minXY.y || 0; - if (this.originX === 'center') { - obj.left += obj.width / 2; - } - if (this.originX === 'right') { - obj.left += obj.width; - } - if (this.originY === 'center') { - obj.top += obj.height / 2; - } - if (this.originY === 'bottom') { - obj.top += obj.height; - } - } - return obj; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = [ - //jscs:disable validateIndentation - '\n' - //jscs:enable validateIndentation - ]; - - for (var i = 0, len = this._objects.length; i < len; i++) { - markup.push(this._objects[i].toSVG(reviver)); - } - - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns requested property - * @param {String} prop Property to get - * @return {Any} - */ - get: function(prop) { - if (prop in _lockProperties) { - if (this[prop]) { - return this[prop]; - } - else { - for (var i = 0, len = this._objects.length; i < len; i++) { - if (this._objects[i][prop]) { - return true; - } - } - return false; - } - } - else { - if (prop in this.delegatedProperties) { - return this._objects[0] && this._objects[0].get(prop); - } - return this[prop]; - } - } - }); - - /** - * Returns {@link fabric.Group} instance from an object representation - * @static - * @memberOf fabric.Group - * @param {Object} object Object to create a group from - * @param {Function} [callback] Callback to invoke when an group instance is created - * @return {fabric.Group} An instance of fabric.Group - */ - fabric.Group.fromObject = function(object, callback) { - fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { - delete object.objects; - callback && callback(new fabric.Group(enlivenedObjects, object)); - }); - }; - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Group - * @type Boolean - * @default - */ - fabric.Group.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var extend = fabric.util.object.extend; - - if (!global.fabric) { - global.fabric = { }; - } - - if (global.fabric.Image) { - fabric.warn('fabric.Image is already defined.'); - return; - } - - /** - * Image class - * @class fabric.Image - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} - * @see {@link fabric.Image#initialize} for constructor definition - */ - fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'image', - - /** - * crossOrigin value (one of "", "anonymous", "allow-credentials") - * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes - * @type String - * @default - */ - crossOrigin: '', - - /** - * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max") - * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute - * This parameter defines how the picture is aligned to its viewport when image element width differs from image width. - * @type String - * @default - */ - alignX: 'none', - - /** - * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max") - * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute - * This parameter defines how the picture is aligned to its viewport when image element height differs from image height. - * @type String - * @default - */ - alignY: 'none', - - /** - * meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice"). - * if meet the image is always fully visibile, if slice the viewport is always filled with image. - * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute - * @type String - * @default - */ - meetOrSlice: 'meet', - - /** - * private - * contains last value of scaleX to detect - * if the Image got resized after the last Render - * @type Number - */ - _lastScaleX: 1, - - /** - * private - * contains last value of scaleY to detect - * if the Image got resized after the last Render - * @type Number - */ - _lastScaleY: 1, - - /** - * Constructor - * @param {HTMLImageElement | String} element Image element - * @param {Object} [options] Options object - * @return {fabric.Image} thisArg - */ - initialize: function(element, options) { - options || (options = { }); - - this.filters = [ ]; - this.resizeFilters = [ ]; - this.callSuper('initialize', options); - - this._initElement(element, options); - this._initConfig(options); - - if (options.filters) { - this.filters = options.filters; - this.applyFilters(); - } - }, - - /** - * Returns image element which this instance if based on - * @return {HTMLImageElement} Image element - */ - getElement: function() { - return this._element; - }, - - /** - * Sets image element for this instance to a specified one. - * If filters defined they are applied to new image. - * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. - * @param {HTMLImageElement} element - * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated - * @param {Object} [options] Options object - * @return {fabric.Image} thisArg - * @chainable - */ - setElement: function(element, callback, options) { - this._element = element; - this._originalElement = element; - this._initConfig(options); - - if (this.filters.length !== 0) { - this.applyFilters(callback); - } - else if (callback) { - callback(); - } - - return this; - }, - - /** - * Sets crossOrigin value (on an instance and corresponding image element) - * @return {fabric.Image} thisArg - * @chainable - */ - setCrossOrigin: function(value) { - this.crossOrigin = value; - this._element.crossOrigin = value; - - return this; - }, - - /** - * Returns original size of an image - * @return {Object} Object with "width" and "height" properties - */ - getOriginalSize: function() { - var element = this.getElement(); - return { - width: element.width, - height: element.height - }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _stroke: function(ctx) { - ctx.save(); - this._setStrokeStyles(ctx); - ctx.beginPath(); - ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); - ctx.closePath(); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var x = -this.width / 2, - y = -this.height / 2, - w = this.width, - h = this.height; - - ctx.save(); - this._setStrokeStyles(ctx); - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); - ctx.closePath(); - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - src: this._originalElement.src || this._originalElement._src, - filters: this.filters.map(function(filterObj) { - return filterObj && filterObj.toObject(); - }), - crossOrigin: this.crossOrigin, - alignX: this.alignX, - alignY: this.alignY, - meetOrSlice: this.meetOrSlice - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = [], x = -this.width / 2, y = -this.height / 2, - preserveAspectRatio = 'none'; - if (this.group && this.group.type === 'path-group') { - x = this.left; - y = this.top; - } - if (this.alignX !== 'none' && this.alignY !== 'none') { - preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice; - } - markup.push( - '\n', - '\n' - ); - - if (this.stroke || this.strokeDashArray) { - var origFill = this.fill; - this.fill = null; - markup.push( - '\n' - ); - this.fill = origFill; - } - - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns source of an image - * @return {String} Source of an image - */ - getSrc: function() { - if (this.getElement()) { - return this.getElement().src || this.getElement()._src; - } - }, - - /** - * Sets source of an image - * @param {String} src Source string (URL) - * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) - * @param {Object} [options] Options object - * @return {fabric.Image} thisArg - * @chainable - */ - setSrc: function(src, callback, options) { - fabric.util.loadImage(src, function(img) { - return this.setElement(img, callback, options); - }, this, options && options.crossOrigin); - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Returns a clone of an instance - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - */ - clone: function(callback, propertiesToInclude) { - this.constructor.fromObject(this.toObject(propertiesToInclude), callback); - }, - - /** - * Applies filters assigned to this image (from "filters" array) - * @method applyFilters - * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated - * @return {fabric.Image} thisArg - * @chainable - */ - applyFilters: function(callback, filters, imgElement, forResizing) { - - filters = filters || this.filters; - imgElement = imgElement || this._originalElement; - - if (!imgElement) { - return; - } - - var imgEl = imgElement, - canvasEl = fabric.util.createCanvasElement(), - replacement = fabric.util.createImage(), - _this = this; - - canvasEl.width = imgEl.width; - canvasEl.height = imgEl.height; - canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); - - if (filters.length === 0) { - this._element = imgElement; - callback && callback(); - return canvasEl; - } - filters.forEach(function(filter) { - filter && filter.applyTo(canvasEl, filter.scaleX || _this.scaleX, filter.scaleY || _this.scaleY); - if (!forResizing && filter && filter.type === 'Resize') { - _this.width *= filter.scaleX; - _this.height *= filter.scaleY; - } - }); - - /** @ignore */ - replacement.width = canvasEl.width; - replacement.height = canvasEl.height; - - if (fabric.isLikelyNode) { - replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); - // onload doesn't fire in some node versions, so we invoke callback manually - _this._element = replacement; - !forResizing && (_this._filteredEl = replacement); - callback && callback(); - } - else { - replacement.onload = function() { - _this._element = replacement; - !forResizing && (_this._filteredEl = replacement); - callback && callback(); - replacement.onload = canvasEl = imgEl = null; - }; - replacement.src = canvasEl.toDataURL('image/png'); - } - return canvasEl; - }, - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx, noTransform) { - var x, y, imageMargins = this._findMargins(), elementToDraw; - - x = (noTransform ? this.left : -this.width / 2); - y = (noTransform ? this.top : -this.height / 2); - - if (this.meetOrSlice === 'slice') { - ctx.beginPath(); - ctx.rect(x, y, this.width, this.height); - ctx.clip(); - } - - if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) { - this._lastScaleX = this.scaleX; - this._lastScaleY = this.scaleY; - elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true); - } - else { - elementToDraw = this._element; - } - elementToDraw && ctx.drawImage(elementToDraw, - x + imageMargins.marginX, - y + imageMargins.marginY, - imageMargins.width, - imageMargins.height - ); - - this._renderStroke(ctx); - }, - /** - * @private, needed to check if image needs resize - */ - _needsResize: function() { - return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY); - }, - - /** - * @private - */ - _findMargins: function() { - var width = this.width, height = this.height, scales, - scale, marginX = 0, marginY = 0; - - if (this.alignX !== 'none' || this.alignY !== 'none') { - scales = [this.width / this._element.width, this.height / this._element.height]; - scale = this.meetOrSlice === 'meet' - ? Math.min.apply(null, scales) : Math.max.apply(null, scales); - width = this._element.width * scale; - height = this._element.height * scale; - if (this.alignX === 'Mid') { - marginX = (this.width - width) / 2; - } - if (this.alignX === 'Max') { - marginX = this.width - width; - } - if (this.alignY === 'Mid') { - marginY = (this.height - height) / 2; - } - if (this.alignY === 'Max') { - marginY = this.height - height; - } - } - return { - width: width, - height: height, - marginX: marginX, - marginY: marginY - }; - }, - - /** - * @private - */ - _resetWidthHeight: function() { - var element = this.getElement(); - - this.set('width', element.width); - this.set('height', element.height); - }, - - /** - * The Image class's initialization method. This method is automatically - * called by the constructor. - * @private - * @param {HTMLImageElement|String} element The element representing the image - */ - _initElement: function(element) { - this.setElement(fabric.util.getById(element)); - fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initConfig: function(options) { - options || (options = { }); - this.setOptions(options); - this._setWidthHeight(options); - if (this._element && this.crossOrigin) { - this._element.crossOrigin = this.crossOrigin; - } - }, - - /** - * @private - * @param {Object} object Object with filters property - * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created - */ - _initFilters: function(object, callback) { - if (object.filters && object.filters.length) { - fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { - callback && callback(enlivenedObjects); - }, 'fabric.Image.filters'); - } - else { - callback && callback(); - } - }, - - /** - * @private - * @param {Object} [options] Object with width/height properties - */ - _setWidthHeight: function(options) { - this.width = 'width' in options - ? options.width - : (this.getElement() - ? this.getElement().width || 0 - : 0); - - this.height = 'height' in options - ? options.height - : (this.getElement() - ? this.getElement().height || 0 - : 0); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /** - * Default CSS class name for canvas - * @static - * @type String - * @default - */ - fabric.Image.CSS_CANVAS = 'canvas-img'; - - /** - * Alias for getSrc - * @static - */ - fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; - - /** - * Creates an instance of fabric.Image from its object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an image instance is created - */ - fabric.Image.fromObject = function(object, callback) { - fabric.util.loadImage(object.src, function(img) { - fabric.Image.prototype._initFilters.call(object, object, function(filters) { - object.filters = filters || [ ]; - var instance = new fabric.Image(img, object); - callback && callback(instance); - }); - }, null, object.crossOrigin); - }; - - /** - * Creates an instance of fabric.Image from an URL string - * @static - * @param {String} url URL to create an image from - * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) - * @param {Object} [imgOptions] Options object - */ - fabric.Image.fromURL = function(url, callback, imgOptions) { - fabric.util.loadImage(url, function(img) { - callback && callback(new fabric.Image(img, imgOptions)); - }, null, imgOptions && imgOptions.crossOrigin); - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) - * @static - * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} - */ - fabric.Image.ATTRIBUTE_NAMES = - fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' ')); - - /** - * Returns {@link fabric.Image} instance from an SVG element - * @static - * @param {SVGElement} element Element to parse - * @param {Function} callback Callback to execute when fabric.Image object is created - * @param {Object} [options] Options object - * @return {fabric.Image} Instance of fabric.Image - */ - fabric.Image.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES), - align = 'xMidYMid', meetOrSlice = 'meet', alignX, alignY, aspectRatioAttrs; - - if (parsedAttributes.preserveAspectRatio) { - aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(' '); - } - - if (aspectRatioAttrs && aspectRatioAttrs.length) { - meetOrSlice = aspectRatioAttrs.pop(); - if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { - align = meetOrSlice; - meetOrSlice = 'meet'; - } - else if (aspectRatioAttrs.length) { - align = aspectRatioAttrs.pop(); - } - } - //divide align in alignX and alignY - alignX = align !== 'none' ? align.slice(1, 4) : 'none'; - alignY = align !== 'none' ? align.slice(5, 8) : 'none'; - parsedAttributes.alignX = alignX; - parsedAttributes.alignY = alignY; - parsedAttributes.meetOrSlice = meetOrSlice; - fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, - extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.async = true; - - /** - * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 - * @static - * @type Number - * @default - */ - fabric.Image.pngCompression = 1; - -})(typeof exports !== 'undefined' ? exports : this); - - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * @private - * @return {Number} angle value - */ - _getAngleValueForStraighten: function() { - var angle = this.getAngle() % 360; - if (angle > 0) { - return Math.round((angle - 1) / 90) * 90; - } - return Math.round(angle / 90) * 90; - }, - - /** - * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) - * @return {fabric.Object} thisArg - * @chainable - */ - straighten: function() { - this.setAngle(this._getAngleValueForStraighten()); - return this; - }, - - /** - * Same as {@link fabric.Object.prototype.straighten} but with animation - * @param {Object} callbacks Object with callback functions - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Object} thisArg - * @chainable - */ - fxStraighten: function(callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: this.get('angle'), - endValue: this._getAngleValueForStraighten(), - duration: this.FX_DURATION, - onChange: function(value) { - _this.setAngle(value); - onChange(); - }, - onComplete: function() { - _this.setCoords(); - onComplete(); - }, - onStart: function() { - _this.set('active', false); - } - }); - - return this; - } -}); - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Straightens object, then rerenders canvas - * @param {fabric.Object} object Object to straighten - * @return {fabric.Canvas} thisArg - * @chainable - */ - straightenObject: function (object) { - object.straighten(); - this.renderAll(); - return this; - }, - - /** - * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated - * @param {fabric.Object} object Object to straighten - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxStraightenObject: function (object) { - object.fxStraighten({ - onChange: this.renderAll.bind(this) - }); - return this; - } -}); - - -/** - * @namespace fabric.Image.filters - * @memberOf fabric.Image - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - */ -fabric.Image.filters = fabric.Image.filters || { }; - -/** - * Root filter class from which all filter classes inherit from - * @class fabric.Image.filters.BaseFilter - * @memberOf fabric.Image.filters - */ -fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'BaseFilter', - - /** - * Constructor - * @param {Object} [options] Options object - */ - initialize: function(options) { - if (options) { - this.setOptions(options); - } - }, - - /** - * Sets filter's properties from options - * @param {Object} [options] Options object - */ - setOptions: function(options) { - for (var prop in options) { - this[prop] = options[prop]; - } - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { type: this.type }; - }, - - /** - * Returns a JSON representation of an instance - * @return {Object} JSON - */ - toJSON: function() { - // delegate, not alias - return this.toObject(); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Brightness filter class - * @class fabric.Image.filters.Brightness - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Brightness({ - * brightness: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Brightness', - - /** - * Constructor - * @memberOf fabric.Image.filters.Brightness.prototype - * @param {Object} [options] Options object - * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) - */ - initialize: function(options) { - options = options || { }; - this.brightness = options.brightness || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - brightness = this.brightness; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i] += brightness; - data[i + 1] += brightness; - data[i + 2] += brightness; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - brightness: this.brightness - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness - */ - fabric.Image.filters.Brightness.fromObject = function(object) { - return new fabric.Image.filters.Brightness(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Adapted from html5rocks article - * @class fabric.Image.filters.Convolute - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example Sharpen filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 0, -1, 0, - * -1, 5, -1, - * 0, -1, 0 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Blur filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter with opaqueness - * var filter = new fabric.Image.filters.Convolute({ - * opaque: true, - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Convolute', - - /** - * Constructor - * @memberOf fabric.Image.filters.Convolute.prototype - * @param {Object} [options] Options object - * @param {Boolean} [options.opaque=false] Opaque value (true/false) - * @param {Array} [options.matrix] Filter matrix - */ - initialize: function(options) { - options = options || { }; - - this.opaque = options.opaque; - this.matrix = options.matrix || [ - 0, 0, 0, - 0, 1, 0, - 0, 0, 0 - ]; - - var canvasEl = fabric.util.createCanvasElement(); - this.tmpCtx = canvasEl.getContext('2d'); - }, - - /** - * @private - */ - _createImageData: function(w, h) { - return this.tmpCtx.createImageData(w, h); - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - - var weights = this.matrix, - context = canvasEl.getContext('2d'), - pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - - side = Math.round(Math.sqrt(weights.length)), - halfSide = Math.floor(side/2), - src = pixels.data, - sw = pixels.width, - sh = pixels.height, - - // pad output by the convolution matrix - w = sw, - h = sh, - output = this._createImageData(w, h), - - dst = output.data, - - // go through the destination image pixels - alphaFac = this.opaque ? 1 : 0; - - for (var y = 0; y < h; y++) { - for (var x = 0; x < w; x++) { - var sy = y, - sx = x, - dstOff = (y * w + x) * 4, - // calculate the weighed sum of the source image pixels that - // fall under the convolution matrix - r = 0, g = 0, b = 0, a = 0; - - for (var cy = 0; cy < side; cy++) { - for (var cx = 0; cx < side; cx++) { - - var scy = sy + cy - halfSide, - scx = sx + cx - halfSide; - - /* jshint maxdepth:5 */ - if (scy < 0 || scy > sh || scx < 0 || scx > sw) { - continue; - } - - var srcOff = (scy * sw + scx) * 4, - wt = weights[cy * side + cx]; - - r += src[srcOff] * wt; - g += src[srcOff + 1] * wt; - b += src[srcOff + 2] * wt; - a += src[srcOff + 3] * wt; - } - } - dst[dstOff] = r; - dst[dstOff + 1] = g; - dst[dstOff + 2] = b; - dst[dstOff + 3] = a + alphaFac * (255 - a); - } - } - - context.putImageData(output, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - opaque: this.opaque, - matrix: this.matrix - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute - */ - fabric.Image.filters.Convolute.fromObject = function(object) { - return new fabric.Image.filters.Convolute(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * GradientTransparency filter class - * @class fabric.Image.filters.GradientTransparency - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.GradientTransparency({ - * threshold: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'GradientTransparency', - - /** - * Constructor - * @memberOf fabric.Image.filters.GradientTransparency.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=100] Threshold value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 100; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - total = data.length; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i + 3] = threshold + 255 * (total - i) / total; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency - */ - fabric.Image.filters.GradientTransparency.fromObject = function(object) { - return new fabric.Image.filters.GradientTransparency(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Grayscale image filter class - * @class fabric.Image.filters.Grayscale - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Grayscale(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Grayscale', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Grayscale.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - len = imageData.width * imageData.height * 4, - index = 0, - average; - - while (index < len) { - average = (data[index] + data[index + 1] + data[index + 2]) / 3; - data[index] = average; - data[index + 1] = average; - data[index + 2] = average; - index += 4; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale - */ - fabric.Image.filters.Grayscale.fromObject = function() { - return new fabric.Image.filters.Grayscale(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Invert filter class - * @class fabric.Image.filters.Invert - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Invert(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Invert', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Invert.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i; - - for (i = 0; i < iLen; i+=4) { - data[i] = 255 - data[i]; - data[i + 1] = 255 - data[i + 1]; - data[i + 2] = 255 - data[i + 2]; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert - */ - fabric.Image.filters.Invert.fromObject = function() { - return new fabric.Image.filters.Invert(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Mask filter class - * See http://resources.aleph-1.com/mask/ - * @class fabric.Image.filters.Mask - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition - */ - fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Mask', - - /** - * Constructor - * @memberOf fabric.Image.filters.Mask.prototype - * @param {Object} [options] Options object - * @param {fabric.Image} [options.mask] Mask image object - * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) - */ - initialize: function(options) { - options = options || { }; - - this.mask = options.mask; - this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - if (!this.mask) { - return; - } - - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - maskEl = this.mask.getElement(), - maskCanvasEl = fabric.util.createCanvasElement(), - channel = this.channel, - i, - iLen = imageData.width * imageData.height * 4; - - maskCanvasEl.width = maskEl.width; - maskCanvasEl.height = maskEl.height; - - maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); - - var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), - maskData = maskImageData.data; - - for (i = 0; i < iLen; i += 4) { - data[i + 3] = maskData[i + channel]; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - mask: this.mask.toObject(), - channel: this.channel - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when a mask filter instance is created - */ - fabric.Image.filters.Mask.fromObject = function(object, callback) { - fabric.util.loadImage(object.mask.src, function(img) { - object.mask = new fabric.Image(img, object.mask); - callback && callback(new fabric.Image.filters.Mask(object)); - }); - }; - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.filters.Mask.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Noise filter class - * @class fabric.Image.filters.Noise - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Noise({ - * noise: 700 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Noise', - - /** - * Constructor - * @memberOf fabric.Image.filters.Noise.prototype - * @param {Object} [options] Options object - * @param {Number} [options.noise=0] Noise value - */ - initialize: function(options) { - options = options || { }; - this.noise = options.noise || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - noise = this.noise, rand; - - for (var i = 0, len = data.length; i < len; i += 4) { - - rand = (0.5 - Math.random()) * noise; - - data[i] += rand; - data[i + 1] += rand; - data[i + 2] += rand; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - noise: this.noise - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise - */ - fabric.Image.filters.Noise.fromObject = function(object) { - return new fabric.Image.filters.Noise(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Pixelate filter class - * @class fabric.Image.filters.Pixelate - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Pixelate({ - * blocksize: 8 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Pixelate', - - /** - * Constructor - * @memberOf fabric.Image.filters.Pixelate.prototype - * @param {Object} [options] Options object - * @param {Number} [options.blocksize=4] Blocksize for pixelate - */ - initialize: function(options) { - options = options || { }; - this.blocksize = options.blocksize || 4; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = imageData.height, - jLen = imageData.width, - index, i, j, r, g, b, a; - - for (i = 0; i < iLen; i += this.blocksize) { - for (j = 0; j < jLen; j += this.blocksize) { - - index = (i * 4) * jLen + (j * 4); - - r = data[index]; - g = data[index + 1]; - b = data[index + 2]; - a = data[index + 3]; - - /* - blocksize: 4 - - [1,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [1,1,1,1,1] - */ - - for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { - for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { - index = (_i * 4) * jLen + (_j * 4); - data[index] = r; - data[index + 1] = g; - data[index + 2] = b; - data[index + 3] = a; - } - } - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - blocksize: this.blocksize - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate - */ - fabric.Image.filters.Pixelate.fromObject = function(object) { - return new fabric.Image.filters.Pixelate(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Remove white filter class - * @class fabric.Image.filters.RemoveWhite - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.RemoveWhite({ - * threshold: 40, - * distance: 140 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'RemoveWhite', - - /** - * Constructor - * @memberOf fabric.Image.filters.RemoveWhite.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=30] Threshold value - * @param {Number} [options.distance=20] Distance value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 30; - this.distance = options.distance || 20; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - distance = this.distance, - limit = 255 - threshold, - abs = Math.abs, - r, g, b; - - for (var i = 0, len = data.length; i < len; i += 4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - if (r > limit && - g > limit && - b > limit && - abs(r - g) < distance && - abs(r - b) < distance && - abs(g - b) < distance - ) { - data[i + 3] = 1; - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold, - distance: this.distance - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite - */ - fabric.Image.filters.RemoveWhite.fromObject = function(object) { - return new fabric.Image.filters.RemoveWhite(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Sepia filter class - * @class fabric.Image.filters.Sepia - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, avg; - - for (i = 0; i < iLen; i+=4) { - avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; - data[i] = avg + 100; - data[i + 1] = avg + 50; - data[i + 2] = avg + 255; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia - */ - fabric.Image.filters.Sepia.fromObject = function() { - return new fabric.Image.filters.Sepia(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Sepia2 filter class - * @class fabric.Image.filters.Sepia2 - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia2(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia2', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, r, g, b; - - for (i = 0; i < iLen; i+=4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; - data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; - data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 - */ - fabric.Image.filters.Sepia2.fromObject = function() { - return new fabric.Image.filters.Sepia2(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Tint filter class - * Adapted from https://github.com/mezzoblue/PaintbrushJS - * @class fabric.Image.filters.Tint - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example Tint filter with hex color and opacity - * var filter = new fabric.Image.filters.Tint({ - * color: '#3513B0', - * opacity: 0.5 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Tint filter with rgba color - * var filter = new fabric.Image.filters.Tint({ - * color: 'rgba(53, 21, 176, 0.5)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Tint', - - /** - * Constructor - * @memberOf fabric.Image.filters.Tint.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to tint the image with - * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - this.opacity = typeof options.opacity !== 'undefined' - ? options.opacity - : new fabric.Color(this.color).getAlpha(); - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - tintR, tintG, tintB, - r, g, b, alpha1, - source; - - source = new fabric.Color(this.color).getSource(); - - tintR = source[0] * this.opacity; - tintG = source[1] * this.opacity; - tintB = source[2] * this.opacity; - - alpha1 = 1 - this.opacity; - - for (i = 0; i < iLen; i+=4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - // alpha compositing - data[i] = tintR + r * alpha1; - data[i + 1] = tintG + g * alpha1; - data[i + 2] = tintB + b * alpha1; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color, - opacity: this.opacity - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint - */ - fabric.Image.filters.Tint.fromObject = function(object) { - return new fabric.Image.filters.Tint(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Multiply filter class - * Adapted from http://www.laurenscorijn.com/articles/colormath-basics - * @class fabric.Image.filters.Multiply - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @example Multiply filter with hex color - * var filter = new fabric.Image.filters.Multiply({ - * color: '#F0F' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Multiply filter with rgb color - * var filter = new fabric.Image.filters.Multiply({ - * color: 'rgb(53, 21, 176)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Multiply', - - /** - * Constructor - * @memberOf fabric.Image.filters.Multiply.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to multiply the image pixels with - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - source; - - source = new fabric.Color(this.color).getSource(); - - for (i = 0; i < iLen; i+=4) { - data[i] *= source[0] / 255; - data[i + 1] *= source[1] / 255; - data[i + 2] *= source[2] / 255; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply - */ - fabric.Image.filters.Multiply.fromObject = function(object) { - return new fabric.Image.filters.Multiply(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - 'use strict'; - - var fabric = global.fabric; - - /** - * Color Blend filter class - * @class fabric.Image.filter.Blend - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @example - * var filter = new fabric.Image.filters.Blend({ - * color: '#000', - * mode: 'multiply' - * }); - * - * var filter = new fabric.Image.filters.Blend({ - * image: fabricImageObject, - * mode: 'multiply', - * alpha: 0.5 - * }); - - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Blend = fabric.util.createClass({ - type: 'Blend', - - initialize: function(options) { - options = options || {}; - this.color = options.color || '#000'; - this.image = options.image || false; - this.mode = options.mode || 'multiply'; - this.alpha = options.alpha || 1; - }, - - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - tr, tg, tb, - r, g, b, - _r, _g, _b, - source, - isImage = false; - - if (this.image) { - // Blend images - isImage = true; - - var _el = fabric.util.createCanvasElement(); - _el.width = this.image.width; - _el.height = this.image.height; - - var tmpCanvas = new fabric.StaticCanvas(_el); - tmpCanvas.add(this.image); - var context2 = tmpCanvas.getContext('2d'); - source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; - } - else { - // Blend color - source = new fabric.Color(this.color).getSource(); - - tr = source[0] * this.alpha; - tg = source[1] * this.alpha; - tb = source[2] * this.alpha; - } - - for (var i = 0, len = data.length; i < len; i += 4) { - - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - if (isImage) { - tr = source[i] * this.alpha; - tg = source[i + 1] * this.alpha; - tb = source[i + 2] * this.alpha; - } - - switch (this.mode) { - case 'multiply': - data[i] = r * tr / 255; - data[i + 1] = g * tg / 255; - data[i + 2] = b * tb / 255; - break; - case 'screen': - data[i] = 1 - (1 - r) * (1 - tr); - data[i + 1] = 1 - (1 - g) * (1 - tg); - data[i + 2] = 1 - (1 - b) * (1 - tb); - break; - case 'add': - data[i] = Math.min(255, r + tr); - data[i + 1] = Math.min(255, g + tg); - data[i + 2] = Math.min(255, b + tb); - break; - case 'diff': - case 'difference': - data[i] = Math.abs(r - tr); - data[i + 1] = Math.abs(g - tg); - data[i + 2] = Math.abs(b - tb); - break; - case 'subtract': - _r = r - tr; - _g = g - tg; - _b = b - tb; - - data[i] = (_r < 0) ? 0 : _r; - data[i + 1] = (_g < 0) ? 0 : _g; - data[i + 2] = (_b < 0) ? 0 : _b; - break; - case 'darken': - data[i] = Math.min(r, tr); - data[i + 1] = Math.min(g, tg); - data[i + 2] = Math.min(b, tb); - break; - case 'lighten': - data[i] = Math.max(r, tr); - data[i + 1] = Math.max(g, tg); - data[i + 2] = Math.max(b, tb); - break; - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { - color: this.color, - image: this.image, - mode: this.mode, - alpha: this.alpha - }; - } - }); - - fabric.Image.filters.Blend.fromObject = function(object) { - return new fabric.Image.filters.Blend(object); - }; -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor, - sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin, - ceil = Math.ceil; - - /** - * Resize image filter class - * @class fabric.Image.filters.Resize - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Resize(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Resize = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Resize', - - /** - * Resize type - * @param {String} resizeType - * @default - */ - resizeType: 'hermite', - - /** - * Scale factor for resizing, x axis - * @param {Number} scaleX - * @default - */ - scaleX: 0, - - /** - * Scale factor for resizing, y axis - * @param {Number} scaleY - * @default - */ - scaleY: 0, - - /** - * LanczosLobes parameter for lanczos filter - * @param {Number} lanczosLobes - * @default - */ - lanczosLobes: 3, - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Resize.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl, scaleX, scaleY) { - - this.rcpScaleX = 1 / scaleX; - this.rcpScaleY = 1 / scaleY; - - var oW = canvasEl.width, oH = canvasEl.height, - dW = round(oW * scaleX), dH = round(oH * scaleY), - imageData; - - if (this.resizeType === 'sliceHack') { - imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH); - } - if (this.resizeType === 'hermite') { - imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH); - } - if (this.resizeType === 'bilinear') { - imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH); - } - if (this.resizeType === 'lanczos') { - imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH); - } - canvasEl.width = dW; - canvasEl.height = dH; - canvasEl.getContext('2d').putImageData(imageData, 0, 0); - }, - - sliceByTwo: function(canvasEl, width, height, newWidth, newHeight) { - var context = canvasEl.getContext('2d'), imageData, - multW = 0.5, multH = 0.5, signW = 1, signH = 1, - doneW = false, doneH = false, stepW = width, stepH = height, - tmpCanvas = fabric.util.createCanvasElement(), - tmpCtx = tmpCanvas.getContext('2d'); - newWidth = floor(newWidth); - newHeight = floor(newHeight); - tmpCanvas.width = max(newWidth, width); - tmpCanvas.height = max(newHeight, height); - - if (newWidth > width) { - multW = 2; - signW = -1; - } - if (newHeight > height) { - multH = 2; - signH = -1; - } - imageData = context.getImageData(0, 0, width, height); - canvasEl.width = max(newWidth, width); - canvasEl.height = max(newHeight, height); - context.putImageData(imageData, 0, 0); - - while (!doneW || !doneH) { - width = stepW; - height = stepH; - if (newWidth * signW < floor(stepW * multW * signW)) { - stepW = floor(stepW * multW); - } - else { - stepW = newWidth; - doneW = true; - } - if (newHeight * signH < floor(stepH * multH * signH)) { - stepH = floor(stepH * multH); - } - else { - stepH = newHeight; - doneH = true; - } - imageData = context.getImageData(0, 0, width, height); - tmpCtx.putImageData(imageData, 0, 0); - context.clearRect(0, 0, stepW, stepH); - context.drawImage(tmpCanvas, 0, 0, width, height, 0, 0, stepW, stepH); - } - return context.getImageData(0, 0, newWidth, newHeight); - }, - - lanczosResize: function(canvasEl, oW, oH, dW, dH) { - - function lanczosCreate(lobes) { - return function(x) { - if (x > lobes) { - return 0; - } - x *= Math.PI; - if (abs(x) < 1e-16) { - return 1; - } - var xx = x / lobes; - return sin(x) * sin(xx) / x / xx; - }; - } - - function process(u) { - var v, i, weight, idx, a, red, green, - blue, alpha, fX, fY; - center.x = (u + 0.5) * ratioX; - icenter.x = floor(center.x); - for (v = 0; v < dH; v++) { - center.y = (v + 0.5) * ratioY; - icenter.y = floor(center.y); - a = 0, red = 0, green = 0, blue = 0, alpha = 0; - for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { - if (i < 0 || i >= oW) { - continue; - } - fX = floor(1000 * abs(i - center.x)); - if (!cacheLanc[fX]) { - cacheLanc[fX] = { }; - } - for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { - if (j < 0 || j >= oH) { - continue; - } - fY = floor(1000 * abs(j - center.y)); - if (!cacheLanc[fX][fY]) { - cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000); - } - weight = cacheLanc[fX][fY]; - if (weight > 0) { - idx = (j * oW + i) * 4; - a += weight; - red += weight * srcData[idx]; - green += weight * srcData[idx + 1]; - blue += weight * srcData[idx + 2]; - alpha += weight * srcData[idx + 3]; - } - } - } - idx = (v * dW + u) * 4; - destData[idx] = red / a; - destData[idx + 1] = green / a; - destData[idx + 2] = blue / a; - destData[idx + 3] = alpha / a; - } - - if (++u < dW) { - return process(u); - } - else { - return destImg; - } - } - - var context = canvasEl.getContext('2d'), - srcImg = context.getImageData(0, 0, oW, oH), - destImg = context.getImageData(0, 0, dW, dH), - srcData = srcImg.data, destData = destImg.data, - lanczos = lanczosCreate(this.lanczosLobes), - ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, - rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, - range2X = ceil(ratioX * this.lanczosLobes / 2), - range2Y = ceil(ratioY * this.lanczosLobes / 2), - cacheLanc = { }, center = { }, icenter = { }; - - return process(0); - }, - - bilinearFiltering: function(canvasEl, w, h, w2, h2) { - var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, - color, offset = 0, origPix, ratioX = this.rcpScaleX, - ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'), - w4 = 4 * (w - 1), img = context.getImageData(0, 0, w, h), - pixels = img.data, destImage = context.getImageData(0, 0, w2, h2), - destPixels = destImage.data; - for (i = 0; i < h2; i++) { - for (j = 0; j < w2; j++) { - x = floor(ratioX * j); - y = floor(ratioY * i); - xDiff = ratioX * j - x; - yDiff = ratioY * i - y; - origPix = 4 * (y * w + x); - - for (chnl = 0; chnl < 4; chnl++) { - a = pixels[origPix + chnl]; - b = pixels[origPix + 4 + chnl]; - c = pixels[origPix + w4 + chnl]; - d = pixels[origPix + w4 + 4 + chnl]; - color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + - c * yDiff * (1 - xDiff) + d * xDiff * yDiff; - destPixels[offset++] = color; - } - } - } - return destImage; - }, - - hermiteFastResize: function(canvasEl, oW, oH, dW, dH) { - var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, - ratioWHalf = ceil(ratioW / 2), - ratioHHalf = ceil(ratioH / 2), - context = canvasEl.getContext('2d'), - img = context.getImageData(0, 0, oW, oH), data = img.data, - img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data; - for (var j = 0; j < dH; j++) { - for (var i = 0; i < dW; i++) { - var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, - gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH; - for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { - var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, - centerX = (i + 0.5) * ratioW, w0 = dy * dy; - for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { - var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, - w = sqrt(w0 + dx * dx); - /*jshint maxdepth:5 */ - if (w > 1 && w < -1) { - continue; - } - //hermite filter - weight = 2 * w * w * w - 3 * w * w + 1; - if (weight > 0) { - dx = 4 * (xx + yy * oW); - //alpha - gxA += weight * data[dx + 3]; - weightsAlpha += weight; - //colors - /*jshint maxdepth:6 */ - if (data[dx + 3] < 255) { - weight = weight * data[dx + 3] / 250; - } - /*jshint maxdepth:5 */ - gxR += weight * data[dx]; - gxG += weight * data[dx + 1]; - gxB += weight * data[dx + 2]; - weights += weight; - } - /*jshint maxdepth:4 */ - } - } - data2[x2] = gxR / weights; - data2[x2 + 1] = gxG / weights; - data2[x2 + 2] = gxB / weights; - data2[x2 + 3] = gxA / weightsAlpha; - } - } - return img2; - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { - type: this.type, - scaleX: this.scaleX, - scaley: this.scaleY, - resizeType: this.resizeType, - lanczosLobes: this.lanczosLobes - }; - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize - */ - fabric.Image.filters.Resize.fromObject = function() { - return new fabric.Image.filters.Resize(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Text) { - fabric.warn('fabric.Text is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push( - 'fontFamily', - 'fontWeight', - 'fontSize', - 'text', - 'textDecoration', - 'textAlign', - 'fontStyle', - 'lineHeight', - 'textBackgroundColor' - ); - - /** - * Text class - * @class fabric.Text - * @extends fabric.Object - * @return {fabric.Text} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} - * @see {@link fabric.Text#initialize} for constructor definition - */ - fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { - - /** - * Properties which when set cause object to change dimensions - * @type Object - * @private - */ - _dimensionAffectingProps: { - fontSize: true, - fontWeight: true, - fontFamily: true, - fontStyle: true, - lineHeight: true, - stroke: true, - strokeWidth: true, - text: true, - textAlign: true - }, - - /** - * @private - */ - _reNewline: /\r?\n/, - - /** - * Retrieves object's fontSize - * @method getFontSize - * @memberOf fabric.Text.prototype - * @return {String} Font size (in pixels) - */ - - /** - * Sets object's fontSize - * @method setFontSize - * @memberOf fabric.Text.prototype - * @param {Number} fontSize Font size (in pixels) - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontWeight - * @method getFontWeight - * @memberOf fabric.Text.prototype - * @return {(String|Number)} Font weight - */ - - /** - * Sets object's fontWeight - * @method setFontWeight - * @memberOf fabric.Text.prototype - * @param {(Number|String)} fontWeight Font weight - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontFamily - * @method getFontFamily - * @memberOf fabric.Text.prototype - * @return {String} Font family - */ - - /** - * Sets object's fontFamily - * @method setFontFamily - * @memberOf fabric.Text.prototype - * @param {String} fontFamily Font family - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's text - * @method getText - * @memberOf fabric.Text.prototype - * @return {String} text - */ - - /** - * Sets object's text - * @method setText - * @memberOf fabric.Text.prototype - * @param {String} text Text - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textDecoration - * @method getTextDecoration - * @memberOf fabric.Text.prototype - * @return {String} Text decoration - */ - - /** - * Sets object's textDecoration - * @method setTextDecoration - * @memberOf fabric.Text.prototype - * @param {String} textDecoration Text decoration - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontStyle - * @method getFontStyle - * @memberOf fabric.Text.prototype - * @return {String} Font style - */ - - /** - * Sets object's fontStyle - * @method setFontStyle - * @memberOf fabric.Text.prototype - * @param {String} fontStyle Font style - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's lineHeight - * @method getLineHeight - * @memberOf fabric.Text.prototype - * @return {Number} Line height - */ - - /** - * Sets object's lineHeight - * @method setLineHeight - * @memberOf fabric.Text.prototype - * @param {Number} lineHeight Line height - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textAlign - * @method getTextAlign - * @memberOf fabric.Text.prototype - * @return {String} Text alignment - */ - - /** - * Sets object's textAlign - * @method setTextAlign - * @memberOf fabric.Text.prototype - * @param {String} textAlign Text alignment - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textBackgroundColor - * @method getTextBackgroundColor - * @memberOf fabric.Text.prototype - * @return {String} Text background color - */ - - /** - * Sets object's textBackgroundColor - * @method setTextBackgroundColor - * @memberOf fabric.Text.prototype - * @param {String} textBackgroundColor Text background color - * @return {fabric.Text} - * @chainable - */ - - /** - * Type of an object - * @type String - * @default - */ - type: 'text', - - /** - * Font size (in pixels) - * @type Number - * @default - */ - fontSize: 40, - - /** - * Font weight (e.g. bold, normal, 400, 600, 800) - * @type {(Number|String)} - * @default - */ - fontWeight: 'normal', - - /** - * Font family - * @type String - * @default - */ - fontFamily: 'Times New Roman', - - /** - * Text decoration Possible values: "", "underline", "overline" or "line-through". - * @type String - * @default - */ - textDecoration: '', - - /** - * Text alignment. Possible values: "left", "center", or "right". - * @type String - * @default - */ - textAlign: 'left', - - /** - * Font style . Possible values: "", "normal", "italic" or "oblique". - * @type String - * @default - */ - fontStyle: '', - - /** - * Line height - * @type Number - * @default - */ - lineHeight: 1.16, - - /** - * Background color of text lines - * @type String - * @default - */ - textBackgroundColor: '', - - /** - * List of properties to consider when checking if - * state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * When defined, an object is rendered via stroke and this property specifies its color. - * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 - * @type String - * @default - */ - stroke: null, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * @private - */ - _fontSizeFraction: 0.25, - - /** - * Text Line proportion to font Size (in pixels) - * @type Number - * @default - */ - _fontSizeMult: 1.13, - - /** - * Constructor - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.Text} thisArg - */ - initialize: function(text, options) { - options = options || { }; - this.text = text; - this.__skipDimension = true; - this.setOptions(options); - this.__skipDimension = false; - this._initDimensions(); - }, - - /** - * Renders text object on offscreen canvas, so that it would get dimensions - * @private - */ - _initDimensions: function(ctx) { - if (this.__skipDimension) { - return; - } - if (!ctx) { - ctx = fabric.util.createCanvasElement().getContext('2d'); - this._setTextStyles(ctx); - } - this._textLines = this.text.split(this._reNewline); - this._clearCache(); - var currentTextAlign = this.textAlign; - this.textAlign = 'left'; - this.width = this._getTextWidth(ctx); - this.textAlign = currentTextAlign; - this.height = this._getTextHeight(ctx); - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of text object - */ - toString: function() { - return '#'; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - - this.clipTo && fabric.util.clipContext(this, ctx); - - this._renderTextBackground(ctx); - this._renderText(ctx); - - this._renderTextDecoration(ctx); - this.clipTo && ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderText: function(ctx) { - ctx.save(); - this._translateForTextAlign(ctx); - this._setOpacity(ctx); - this._setShadow(ctx); - this._setupCompositeOperation(ctx); - this._renderTextFill(ctx); - this._renderTextStroke(ctx); - this._restoreCompositeOperation(ctx); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _translateForTextAlign: function(ctx) { - if (this.textAlign !== 'left' && this.textAlign !== 'justify') { - ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setTextStyles: function(ctx) { - ctx.textBaseline = 'alphabetic'; - if (!this.skipTextAlign) { - ctx.textAlign = this.textAlign; - } - ctx.font = this._getFontDeclaration(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @return {Number} Height of fabric.Text object - */ - _getTextHeight: function() { - return this._textLines.length * this._getHeightOfLine(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @return {Number} Maximum width of fabric.Text object - */ - _getTextWidth: function(ctx) { - var maxWidth = this._getLineWidth(ctx, 0); - - for (var i = 1, len = this._textLines.length; i < len; i++) { - var currentLineWidth = this._getLineWidth(ctx, i); - if (currentLineWidth > maxWidth) { - maxWidth = currentLineWidth; - } - } - return maxWidth; - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} chars Chars to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - */ - _renderChars: function(method, ctx, chars, left, top) { - ctx[method](chars, left, top); - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Text to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - * @param {Number} lineIndex Index of a line in a text - */ - _renderTextLine: function(method, ctx, line, left, top, lineIndex) { - // lift the line by quarter of fontSize - top -= this.fontSize * this._fontSizeFraction; - - // short-circuit - if (this.textAlign !== 'justify') { - this._renderChars(method, ctx, line, left, top, lineIndex); - return; - } - - var lineWidth = this._getLineWidth(ctx, lineIndex), - totalWidth = this.width; - if (totalWidth >= lineWidth) { - // stretch the line - var words = line.split(/\s+/), - wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), - widthDiff = totalWidth - wordsWidth, - numSpaces = words.length - 1, - spaceWidth = widthDiff / numSpaces, - leftOffset = 0; - - for (var i = 0, len = words.length; i < len; i++) { - this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); - leftOffset += ctx.measureText(words[i]).width + spaceWidth; - } - } - else { - this._renderChars(method, ctx, line, left, top, lineIndex); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} line - */ - _getWidthOfWords: function (ctx, line) { - return ctx.measureText(line.replace(/\s+/g, '')).width; - }, - - /** - * @private - * @return {Number} Left offset - */ - _getLeftOffset: function() { - return -this.width / 2; - }, - - /** - * @private - * @return {Number} Top offset - */ - _getTopOffset: function() { - return -this.height / 2; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextFill: function(ctx) { - if (!this.fill && !this._skipFillStrokeCheck) { - return; - } - - var lineHeights = 0; - - for (var i = 0, len = this._textLines.length; i < len; i++) { - var heightOfLine = this._getHeightOfLine(ctx, i), - maxHeight = heightOfLine / this.lineHeight; - - this._renderTextLine( - 'fillText', - ctx, - this._textLines[i], - this._getLeftOffset(), - this._getTopOffset() + lineHeights + maxHeight, - i - ); - lineHeights += heightOfLine; - } - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextStroke: function(ctx) { - if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) { - return; - } - - var lineHeights = 0; - - ctx.save(); - - if (this.strokeDashArray) { - // Spec requires the concatenation of two copies the dash list when the number of elements is odd - if (1 & this.strokeDashArray.length) { - this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); - } - supportsLineDash && ctx.setLineDash(this.strokeDashArray); - } - - ctx.beginPath(); - for (var i = 0, len = this._textLines.length; i < len; i++) { - var heightOfLine = this._getHeightOfLine(ctx, i), - maxHeight = heightOfLine / this.lineHeight; - - this._renderTextLine( - 'strokeText', - ctx, - this._textLines[i], - this._getLeftOffset(), - this._getTopOffset() + lineHeights + maxHeight, - i - ); - lineHeights += heightOfLine; - } - ctx.closePath(); - ctx.restore(); - }, - - _getHeightOfLine: function() { - return this.fontSize * this._fontSizeMult * this.lineHeight; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextBackground: function(ctx) { - this._renderTextBoxBackground(ctx); - this._renderTextLinesBackground(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextBoxBackground: function(ctx) { - if (!this.backgroundColor) { - return; - } - - ctx.save(); - ctx.fillStyle = this.backgroundColor; - - ctx.fillRect( - this._getLeftOffset(), - this._getTopOffset(), - this.width, - this.height - ); - - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextLinesBackground: function(ctx) { - var lineTopOffset = 0, heightOfLine = this._getHeightOfLine(); - if (!this.textBackgroundColor) { - return; - } - - ctx.save(); - ctx.fillStyle = this.textBackgroundColor; - - for (var i = 0, len = this._textLines.length; i < len; i++) { - - if (this._textLines[i] !== '') { - - var lineWidth = this._getLineWidth(ctx, i), - lineLeftOffset = this._getLineLeftOffset(lineWidth); - - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset, - this._getTopOffset() + lineTopOffset, - lineWidth, - this.fontSize * this._fontSizeMult - ); - } - lineTopOffset += heightOfLine; - } - ctx.restore(); - }, - - /** - * @private - * @param {Number} lineWidth Width of text line - * @return {Number} Line left offset - */ - _getLineLeftOffset: function(lineWidth) { - if (this.textAlign === 'center') { - return (this.width - lineWidth) / 2; - } - if (this.textAlign === 'right') { - return this.width - lineWidth; - } - return 0; - }, - - /** - * @private - */ - _clearCache: function() { - this.__lineWidths = [ ]; - this.__lineHeights = [ ]; - this.__lineOffsets = [ ]; - }, - - /** - * @private - */ - _shouldClearCache: function() { - var shouldClear = false; - for (var prop in this._dimensionAffectingProps) { - if (this['__' + prop] !== this[prop]) { - this['__' + prop] = this[prop]; - shouldClear = true; - } - } - return shouldClear; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @return {Number} Line width - */ - _getLineWidth: function(ctx, lineIndex) { - if (this.__lineWidths[lineIndex]) { - return this.__lineWidths[lineIndex]; - } - this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width; - return this.__lineWidths[lineIndex]; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextDecoration: function(ctx) { - if (!this.textDecoration) { - return; - } - - var halfOfVerticalBox = this.height / 2, - _this = this, offsets = []; - - /** @ignore */ - function renderLinesAtOffset(offsets) { - var i, lineHeight = 0, len, j, oLen; - for (i = 0, len = _this._textLines.length; i < len; i++) { - - var lineWidth = _this._getLineWidth(ctx, i), - lineLeftOffset = _this._getLineLeftOffset(lineWidth), - heightOfLine = _this._getHeightOfLine(ctx, i); - - for (j = 0, oLen = offsets.length; j < oLen; j++) { - ctx.fillRect( - _this._getLeftOffset() + lineLeftOffset, - lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox, - lineWidth, - _this.fontSize / 15); - } - lineHeight += heightOfLine; - } - } - - if (this.textDecoration.indexOf('underline') > -1) { - offsets.push(0.85); // 1 - 3/16 - } - if (this.textDecoration.indexOf('line-through') > -1) { - offsets.push(0.43); - } - if (this.textDecoration.indexOf('overline') > -1) { - offsets.push(-0.12); - } - - if (offsets.length > 0) { - renderLinesAtOffset(offsets); - } - }, - - /** - * @private - */ - _getFontDeclaration: function() { - return [ - // node-canvas needs "weight style", while browsers need "style weight" - (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), - (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), - this.fontSize + 'px', - (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) - ].join(' '); - }, - - /** - * Renders text instance on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - this._setTextStyles(ctx); - - if (this._shouldClearCache()) { - this._initDimensions(ctx); - } - if (!noTransform) { - this.transform(ctx); - } - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - if (this.transformMatrix) { - ctx.transform.apply(ctx, this.transformMatrix); - } - if (this.group && this.group.type === 'path-group') { - ctx.translate(this.left, this.top); - } - this._render(ctx); - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var object = extend(this.callSuper('toObject', propertiesToInclude), { - text: this.text, - fontSize: this.fontSize, - fontWeight: this.fontWeight, - fontFamily: this.fontFamily, - fontStyle: this.fontStyle, - lineHeight: this.lineHeight, - textDecoration: this.textDecoration, - textAlign: this.textAlign, - textBackgroundColor: this.textBackgroundColor - }); - if (!this.includeDefaultValues) { - this._removeDefaultValues(object); - } - return object; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - offsets = this._getSVGLeftTopOffsets(this.ctx), - textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); - this._wrapSVGTextAndBg(markup, textAndBg); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - - /** - * @private - */ - _getSVGLeftTopOffsets: function(ctx) { - var lineTop = this._getHeightOfLine(ctx, 0), - textLeft = -this.width / 2, - textTop = 0; - - return { - textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), - textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0), - lineTop: lineTop - }; - }, - - /** - * @private - */ - _wrapSVGTextAndBg: function(markup, textAndBg) { - markup.push( - '\t\n', - textAndBg.textBgRects.join(''), - '\t\t', - textAndBg.textSpans.join(''), - '\n', - '\t\n' - ); - }, - - /** - * @private - * @param {Number} textTopOffset Text top offset - * @param {Number} textLeftOffset Text left offset - * @return {Object} - */ - _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { - var textSpans = [ ], - textBgRects = [ ], - height = 0; - // bounding-box background - this._setSVGBg(textBgRects); - - // text and text-background - for (var i = 0, len = this._textLines.length; i < len; i++) { - if (this.textBackgroundColor) { - this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height); - } - this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects); - height += this._getHeightOfLine(this.ctx, i); - } - - return { - textSpans: textSpans, - textBgRects: textBgRects - }; - }, - - _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) { - var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction) - - textTopOffset + height - this.height / 2; - textSpans.push( - ' elements since setting opacity - // on containing one doesn't work in Illustrator - this._getFillAttributes(this.fill), '>', - fabric.util.string.escapeXml(this._textLines[i]), - '' - ); - }, - - _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) { - textBgRects.push( - '\t\t\n'); - }, - - _setSVGBg: function(textBgRects) { - if (this.backgroundColor) { - textBgRects.push( - '\t\t\n'); - } - }, - - /** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * - * @private - * @param {Any} value - * @return {String} - */ - _getFillAttributes: function(value) { - var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; - if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { - return 'fill="' + value + '"'; - } - return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; - }, - /* _TO_SVG_END_ */ - - /** - * Sets specified property to a specified value - * @param {String} key - * @param {Any} value - * @return {fabric.Text} thisArg - * @chainable - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - - if (key in this._dimensionAffectingProps) { - this._initDimensions(); - this.setCoords(); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) - * @static - * @memberOf fabric.Text - * @see: http://www.w3.org/TR/SVG/text.html#TextElement - */ - fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( - 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); - - /** - * Default SVG font size - * @static - * @memberOf fabric.Text - */ - fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; - - /** - * Returns fabric.Text instance from an SVG element (not yet implemented) - * @static - * @memberOf fabric.Text - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromElement = function(element, options) { - if (!element) { - return null; - } - - var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); - options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); - - options.top = options.top || 0; - options.left = options.left || 0; - if ('dx' in parsedAttributes) { - options.left += parsedAttributes.dx; - } - if ('dy' in parsedAttributes) { - options.top += parsedAttributes.dy; - } - if (!('fontSize' in options)) { - options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; - } - - if (!options.originX) { - options.originX = 'left'; - } - var textContent = element.textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '), - text = new fabric.Text(textContent, options), - /* - Adjust positioning: - x/y attributes in SVG correspond to the bottom-left corner of text bounding box - top/left properties in Fabric correspond to center point of text bounding box - */ - offX = 0; - - if (text.originX === 'left') { - offX = text.getWidth() / 2; - } - if (text.originX === 'right') { - offX = -text.getWidth() / 2; - } - text.set({ - left: text.getLeft() + offX, - top: text.getTop() - text.getHeight() / 2 + text.fontSize * (0.18 + text._fontSizeFraction) /* 0.3 is the old lineHeight */ - }); - - return text; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Text instance from an object representation - * @static - * @memberOf fabric.Text - * @param {Object} object Object to create an instance from - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromObject = function(object) { - return new fabric.Text(object.text, clone(object)); - }; - - fabric.util.createAccessors(fabric.Text); - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var clone = fabric.util.object.clone; - - /** - * IText class (introduced in 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 or home, end
-   *   Select till start/end of line:  cmd + shift + left, cmd + shift + right or shift + home, shift + end
-   *   Jump to start/end of text:      cmd + up, cmd + down
-   *   Select till start/end of text:  cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown
-   *   Delete character:               backspace
-   *   Delete word:                    alt + backspace
-   *   Delete line:                    cmd + backspace
-   *   Forward delete:                 delete
-   *   Copy text:                      ctrl/cmd + c
-   *   Paste text:                     ctrl/cmd + v
-   *   Cut text:                       ctrl/cmd + x
-   *   Select entire text:             ctrl/cmd + a
-   *   Quit editing                    tab or esc
-   * 
- * - *

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: false, - - /** - * @private - */ - _reSpace: /\s|\n/, - - /** - * @private - */ - _currentCursorOpacity: 0, - - /** - * @private - */ - _selectionDirection: null, - - /** - * @private - */ - _abortCursorAnimation: false, - - /** - * @private - */ - _charWidthsCache: { }, - - /** - * Constructor - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.IText} thisArg - */ - initialize: function(text, options) { - this.styles = options ? (options.styles || { }) : { }; - this.callSuper('initialize', text, options); - this.initBehavior(); - }, - - /** - * @private - */ - _clearCache: function() { - this.callSuper('_clearCache'); - this.__maxFontHeights = [ ]; - this.__widthOfSpace = [ ]; - }, - - /** - * Returns true if object has no styling - */ - isEmptyStyles: function() { - if (!this.styles) { - return true; - } - var obj = this.styles; - - for (var p1 in obj) { - for (var p2 in obj[p1]) { - /*jshint unused:false */ - for (var p3 in obj[p1][p2]) { - return false; - } - } - } - return true; - }, - - /** - * Sets selection start (left boundary of a selection) - * @param {Number} index Index to set selection start to - */ - setSelectionStart: function(index) { - index = Math.max(index, 0); - if (this.selectionStart !== index) { - this.fire('selection:changed'); - this.canvas && this.canvas.fire('text:selection:changed', { target: this }); - this.selectionStart = index; - } - this._updateTextarea(); - }, - - /** - * Sets selection end (right boundary of a selection) - * @param {Number} index Index to set selection end to - */ - setSelectionEnd: function(index) { - index = Math.min(index, this.text.length); - if (this.selectionEnd !== index) { - this.fire('selection:changed'); - this.canvas && this.canvas.fire('text:selection:changed', { target: this }); - this.selectionEnd = index; - } - this._updateTextarea(); - }, - - /** - * Gets style of a current selection/cursor (at the start position) - * @param {Number} [startIndex] Start index to get styles at - * @param {Number} [endIndex] End index to get styles at - * @return {Object} styles Style object at a specified (or current) index - */ - getSelectionStyles: function(startIndex, endIndex) { - - if (arguments.length === 2) { - var styles = [ ]; - for (var i = startIndex; i < endIndex; i++) { - styles.push(this.getSelectionStyles(i)); - } - return styles; - } - - var loc = this.get2DCursorLocation(startIndex); - if (this.styles[loc.lineIndex]) { - return this.styles[loc.lineIndex][loc.charIndex] || { }; - } - - return { }; - }, - - /** - * Sets style of a current selection - * @param {Object} [styles] Styles object - * @return {fabric.IText} thisArg - * @chainable - */ - setSelectionStyles: function(styles) { - if (this.selectionStart === this.selectionEnd) { - this._extendStyles(this.selectionStart, styles); - } - else { - for (var i = this.selectionStart; i < this.selectionEnd; i++) { - this._extendStyles(i, styles); - } - } - /* not included in _extendStyles to avoid clearing cache more than once */ - this._clearCache(); - return this; - }, - - /** - * @private - */ - _extendStyles: function(index, styles) { - var loc = this.get2DCursorLocation(index); - - if (!this.styles[loc.lineIndex]) { - this.styles[loc.lineIndex] = { }; - } - if (!this.styles[loc.lineIndex][loc.charIndex]) { - this.styles[loc.lineIndex][loc.charIndex] = { }; - } - fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - this.callSuper('_render', ctx); - this.ctx = ctx; - this.isEditing && this.renderCursorOrSelection(); - }, - - /** - * Renders cursor or selection (depending on what exists) - */ - renderCursorOrSelection: function() { - if (!this.active) { - return; - } - - var chars = this.text.split(''), - boundaries, ctx; - - if (this.canvas.contextTop) { - ctx = this.canvas.contextTop; - ctx.save(); - ctx.transform.apply(ctx, this.canvas.viewportTransform); - this.transform(ctx); - } - else { - ctx = this.ctx; - ctx.save(); - } - - if (this.selectionStart === this.selectionEnd) { - boundaries = this._getCursorBoundaries(chars, 'cursor'); - this.renderCursor(boundaries, ctx); - } - else { - boundaries = this._getCursorBoundaries(chars, 'selection'); - this.renderSelection(chars, boundaries, ctx); - } - - ctx.restore(); - }, - - /** - * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) - * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. - */ - get2DCursorLocation: function(selectionStart) { - if (typeof selectionStart === 'undefined') { - selectionStart = this.selectionStart; - } - var textBeforeCursor = this.text.slice(0, selectionStart), - linesBeforeCursor = textBeforeCursor.split(this._reNewline); - - return { - lineIndex: linesBeforeCursor.length - 1, - charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length - }; - }, - - /** - * Returns complete style of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {Object} Character style - */ - getCurrentCharStyle: function(lineIndex, charIndex) { - var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)]; - - return { - fontSize: style && style.fontSize || this.fontSize, - fill: style && style.fill || this.fill, - textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, - textDecoration: style && style.textDecoration || this.textDecoration, - fontFamily: style && style.fontFamily || this.fontFamily, - fontWeight: style && style.fontWeight || this.fontWeight, - fontStyle: style && style.fontStyle || this.fontStyle, - stroke: style && style.stroke || this.stroke, - strokeWidth: style && style.strokeWidth || this.strokeWidth - }; - }, - - /** - * Returns fontSize of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {Number} Character font size - */ - getCurrentCharFontSize: function(lineIndex, charIndex) { - return ( - this.styles[lineIndex] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize; - }, - - /** - * Returns color (fill) of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {String} Character color (fill) - */ - getCurrentCharColor: function(lineIndex, charIndex) { - return ( - this.styles[lineIndex] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor; - }, - - /** - * Returns cursor boundaries (left, top, leftOffset, topOffset) - * @private - * @param {Array} chars Array of characters - * @param {String} typeOfBoundaries - */ - _getCursorBoundaries: function(chars, typeOfBoundaries) { - - // left/top are left/top of entire text box - // leftOffset/topOffset are offset from that left/top point of a text box - - var left = Math.round(this._getLeftOffset()), - top = this._getTopOffset(), - - offsets = this._getCursorBoundariesOffsets( - chars, typeOfBoundaries); - - return { - left: left, - top: top, - leftOffset: offsets.left + offsets.lineLeft, - topOffset: offsets.top - }; - }, - - /** - * @private - */ - _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) { - - var lineLeftOffset = 0, - - lineIndex = 0, - charIndex = 0, - topOffset = 0, - leftOffset = 0; - - for (var i = 0; i < this.selectionStart; i++) { - if (chars[i] === '\n') { - leftOffset = 0; - topOffset += this._getHeightOfLine(this.ctx, lineIndex); - - lineIndex++; - charIndex = 0; - } - else { - leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); - charIndex++; - } - - lineLeftOffset = this._getCachedLineOffset(lineIndex); - } - if (typeOfBoundaries === 'cursor') { - topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight - - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction); - } - - return { - top: topOffset, - left: leftOffset, - lineLeft: lineLeftOffset - }; - }, - - /** - * @private - */ - _getCachedLineOffset: function(lineIndex) { - var widthOfLine = this._getLineWidth(this.ctx, lineIndex); - - return this.__lineOffsets[lineIndex] || - (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine)); - }, - - /** - * Renders cursor - * @param {Object} boundaries - * @param {CanvasRenderingContext2D} ctx transformed context to draw on - */ - renderCursor: function(boundaries, ctx) { - - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex, - charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), - leftOffset = (lineIndex === 0 && charIndex === 0) - ? this._getCachedLineOffset(lineIndex) - : boundaries.leftOffset; - - ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); - ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; - - ctx.fillRect( - boundaries.left + leftOffset, - boundaries.top + boundaries.topOffset, - this.cursorWidth / this.scaleX, - charHeight); - - }, - - /** - * Renders text selection - * @param {Array} chars Array of characters - * @param {Object} boundaries Object with left/top/leftOffset/topOffset - * @param {CanvasRenderingContext2D} ctx transformed context to draw on - */ - renderSelection: function(chars, boundaries, ctx) { - - ctx.fillStyle = this.selectionColor; - - var start = this.get2DCursorLocation(this.selectionStart), - end = this.get2DCursorLocation(this.selectionEnd), - startLine = start.lineIndex, - endLine = end.lineIndex; - - for (var i = startLine; i <= endLine; i++) { - var lineOffset = this._getCachedLineOffset(i) || 0, - lineHeight = this._getHeightOfLine(this.ctx, i), - boxWidth = 0, line = this._textLines[i]; - - if (i === startLine) { - for (var j = 0, len = line.length; j < len; j++) { - if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { - boxWidth += this._getWidthOfChar(ctx, line[j], i, j); - } - if (j < start.charIndex) { - lineOffset += this._getWidthOfChar(ctx, line[j], i, j); - } - } - } - else if (i > startLine && i < endLine) { - boxWidth += this._getLineWidth(ctx, i) || 5; - } - else if (i === endLine) { - for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { - boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2); - } - } - - ctx.fillRect( - boundaries.left + lineOffset, - boundaries.top + boundaries.topOffset, - boxWidth, - lineHeight); - - boundaries.topOffset += lineHeight; - } - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderChars: function(method, ctx, line, left, top, lineIndex) { - - if (this.isEmptyStyles()) { - return this._renderCharsFast(method, ctx, line, left, top); - } - - this.skipTextAlign = true; - - // set proper box offset - left -= this.textAlign === 'center' - ? (this.width / 2) - : (this.textAlign === 'right') - ? this.width - : 0; - - // set proper line offset - var lineHeight = this._getHeightOfLine(ctx, lineIndex), - lineLeftOffset = this._getCachedLineOffset(lineIndex), - chars = line.split(''), - prevStyle, - charsToRender = ''; - - left += lineLeftOffset || 0; - - ctx.save(); - top -= lineHeight / this.lineHeight * this._fontSizeFraction; - for (var i = 0, len = chars.length; i <= len; i++) { - prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); - var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); - - if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { - this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); - charsToRender = ''; - prevStyle = thisStyle; - } - charsToRender += chars[i]; - } - - ctx.restore(); - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Content of the line - * @param {Number} left Left coordinate - * @param {Number} top Top coordinate - */ - _renderCharsFast: function(method, ctx, line, left, top) { - this.skipTextAlign = false; - - if (method === 'fillText' && this.fill) { - this.callSuper('_renderChars', method, ctx, line, left, top); - } - if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) { - this.callSuper('_renderChars', method, ctx, line, left, top); - } - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex - * @param {Number} i - * @param {String} _char - * @param {Number} left Left coordinate - * @param {Number} top Top coordinate - * @param {Number} lineHeight Height of the line - */ - _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { - var decl, charWidth, charHeight, - offset = this._fontSizeFraction * lineHeight / this.lineHeight; - - if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) { - - var shouldStroke = decl.stroke || this.stroke, - shouldFill = decl.fill || this.fill; - - ctx.save(); - charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl); - charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); - - if (shouldFill) { - ctx.fillText(_char, left, top); - } - if (shouldStroke) { - ctx.strokeText(_char, left, top); - } - - this._renderCharDecoration(ctx, decl, left, top, offset, charWidth, charHeight); - ctx.restore(); - - ctx.translate(charWidth, 0); - } - else { - if (method === 'strokeText' && this.stroke) { - ctx[method](_char, left, top); - } - if (method === 'fillText' && this.fill) { - ctx[method](_char, left, top); - } - charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i); - this._renderCharDecoration(ctx, null, left, top, offset, charWidth, this.fontSize); - - ctx.translate(ctx.measureText(_char).width, 0); - } - }, - - /** - * @private - * @param {Object} prevStyle - * @param {Object} thisStyle - */ - _hasStyleChanged: function(prevStyle, thisStyle) { - return (prevStyle.fill !== thisStyle.fill || - prevStyle.fontSize !== thisStyle.fontSize || - prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || - prevStyle.textDecoration !== thisStyle.textDecoration || - prevStyle.fontFamily !== thisStyle.fontFamily || - prevStyle.fontWeight !== thisStyle.fontWeight || - prevStyle.fontStyle !== thisStyle.fontStyle || - prevStyle.stroke !== thisStyle.stroke || - prevStyle.strokeWidth !== thisStyle.strokeWidth - ); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderCharDecoration: function(ctx, styleDeclaration, left, top, offset, charWidth, charHeight) { - - var textDecoration = styleDeclaration - ? (styleDeclaration.textDecoration || this.textDecoration) - : this.textDecoration; - - if (!textDecoration) { - return; - } - - if (textDecoration.indexOf('underline') > -1) { - ctx.fillRect( - left, - top + charHeight / 10, - charWidth , - charHeight / 15 - ); - } - if (textDecoration.indexOf('line-through') > -1) { - ctx.fillRect( - left, - top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + charHeight / 15, - charWidth, - charHeight / 15 - ); - } - if (textDecoration.indexOf('overline') > -1) { - ctx.fillRect( - left, - top - (this._fontSizeMult - this._fontSizeFraction) * charHeight, - charWidth, - charHeight / 15 - ); - } - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line - */ - _renderTextLine: function(method, ctx, line, left, top, lineIndex) { - // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine - // the adding 0.03 is just to align text with itext by overlap test - if (!this.isEmptyStyles()) { - top += this.fontSize * (this._fontSizeFraction + 0.03); - } - this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextDecoration: function(ctx) { - if (this.isEmptyStyles()) { - return this.callSuper('_renderTextDecoration', ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextLinesBackground: function(ctx) { - if (!this.textBackgroundColor && !this.styles) { - return; - } - - ctx.save(); - - if (this.textBackgroundColor) { - ctx.fillStyle = this.textBackgroundColor; - } - - var lineHeights = 0; - - for (var i = 0, len = this._textLines.length; i < len; i++) { - - var heightOfLine = this._getHeightOfLine(ctx, i); - if (this._textLines[i] === '') { - lineHeights += heightOfLine; - continue; - } - - var lineWidth = this._getLineWidth(ctx, i), - lineLeftOffset = this._getCachedLineOffset(i); - - if (this.textBackgroundColor) { - ctx.fillStyle = this.textBackgroundColor; - - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset, - this._getTopOffset() + lineHeights, - lineWidth, - heightOfLine / this.lineHeight - ); - } - if (this.styles[i]) { - for (var j = 0, jlen = this._textLines[i].length; j < jlen; j++) { - if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) { - - var _char = this._textLines[i][j]; - - ctx.fillStyle = this.styles[i][j].textBackgroundColor; - - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j), - this._getTopOffset() + lineHeights, - this._getWidthOfChar(ctx, _char, i, j) + 1, - heightOfLine / this.lineHeight - ); - } - } - } - lineHeights += heightOfLine; - } - ctx.restore(); - }, - - /** - * @private - */ - _getCacheProp: function(_char, styleDeclaration) { - return _char + - styleDeclaration.fontFamily + - styleDeclaration.fontSize + - styleDeclaration.fontWeight + - styleDeclaration.fontStyle + - styleDeclaration.shadow; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} _char - * @param {Number} lineIndex - * @param {Number} charIndex - * @param {Object} [decl] - */ - _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { - var styleDeclaration = decl || - (this.styles[lineIndex] && - this.styles[lineIndex][charIndex]); - - if (styleDeclaration) { - // cloning so that original style object is not polluted with following font 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, this) - : fill; - - if (styleDeclaration.stroke) { - ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive) - ? styleDeclaration.stroke.toLive(ctx, this) - : styleDeclaration.stroke; - } - - ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; - ctx.font = this._getFontDeclaration.call(styleDeclaration); - this._setShadow.call(styleDeclaration, ctx); - - if (!this.caching) { - return ctx.measureText(_char).width; - } - - if (!this._charWidthsCache[cacheProp]) { - this._charWidthsCache[cacheProp] = ctx.measureText(_char).width; - } - - return this._charWidthsCache[cacheProp]; - }, - - /** - * @private - * @param {Object} styleDeclaration - */ - _applyFontStyles: function(styleDeclaration) { - if (!styleDeclaration.fontFamily) { - styleDeclaration.fontFamily = this.fontFamily; - } - if (!styleDeclaration.fontSize) { - styleDeclaration.fontSize = this.fontSize; - } - if (!styleDeclaration.fontWeight) { - styleDeclaration.fontWeight = this.fontWeight; - } - if (!styleDeclaration.fontStyle) { - styleDeclaration.fontStyle = this.fontStyle; - } - }, - - /** - * @private - * @param {Number} lineIndex - * @param {Number} charIndex - */ - _getStyleDeclaration: function(lineIndex, charIndex) { - return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) - ? clone(this.styles[lineIndex][charIndex]) - : { }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { - if (this.textAlign === 'justify' && /\s/.test(_char)) { - return this._getWidthOfSpace(ctx, lineIndex); - } - - var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); - this._applyFontStyles(styleDeclaration); - var cacheProp = this._getCacheProp(_char, styleDeclaration); - - if (this._charWidthsCache[cacheProp] && this.caching) { - return this._charWidthsCache[cacheProp]; - } - else if (ctx) { - ctx.save(); - var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); - ctx.restore(); - return width; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getHeightOfChar: function(ctx, _char, lineIndex, charIndex) { - if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) { - return this.styles[lineIndex][charIndex].fontSize || this.fontSize; - } - return this.fontSize; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getHeightOfCharAt: function(ctx, lineIndex, charIndex) { - var _char = this._textLines[lineIndex][charIndex]; - return this._getHeightOfChar(ctx, _char, lineIndex, charIndex); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) { - var width = 0, i, _char; - for (i = 0; i < charIndex; i++) { - _char = this._textLines[lineIndex][i]; - width += this._getWidthOfChar(ctx, _char, lineIndex, i); - } - return width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getLineWidth: function(ctx, lineIndex) { - if (this.__lineWidths[lineIndex]) { - return this.__lineWidths[lineIndex]; - } - this.__lineWidths[lineIndex] = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length); - return this.__lineWidths[lineIndex]; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex - */ - _getWidthOfSpace: function (ctx, lineIndex) { - if (this.__widthOfSpace[lineIndex]) { - return this.__widthOfSpace[lineIndex]; - } - var line = this._textLines[lineIndex], - wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), - widthDiff = this.width - wordsWidth, - numSpaces = line.length - line.replace(/\s+/g, '').length, - width = widthDiff / numSpaces; - this.__widthOfSpace[lineIndex] = width; - return width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} line - * @param {Number} lineIndex - */ - _getWidthOfWords: function (ctx, line, lineIndex) { - var width = 0; - - for (var charIndex = 0; charIndex < line.length; charIndex++) { - var _char = line[charIndex]; - - if (!_char.match(/\s/)) { - width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex); - } - } - - return width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getHeightOfLine: function(ctx, lineIndex) { - if (this.__lineHeights[lineIndex]) { - return this.__lineHeights[lineIndex]; - } - - var line = this._textLines[lineIndex], - maxHeight = this._getHeightOfChar(ctx, line[0], lineIndex, 0); - - for (var i = 1, len = line.length; i < len; i++) { - var currentCharHeight = this._getHeightOfChar(ctx, line[i], lineIndex, i); - if (currentCharHeight > maxHeight) { - maxHeight = currentCharHeight; - } - } - this.__maxFontHeights[lineIndex] = maxHeight; - this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; - return this.__lineHeights[lineIndex]; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getTextHeight: function(ctx) { - var height = 0; - for (var i = 0, len = this._textLines.length; i < len; i++) { - height += this._getHeightOfLine(ctx, i); - } - return height; - }, - - /** - * This method is overwritten to account for different top offset - * @private - */ - _renderTextBoxBackground: function(ctx) { - if (!this.backgroundColor) { - return; - } - - ctx.save(); - ctx.fillStyle = this.backgroundColor; - - ctx.fillRect( - this._getLeftOffset(), - this._getTopOffset(), - this.width, - this.height - ); - - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @method toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { - styles: clone(this.styles) - }); - } - }); - - /** - * Returns fabric.IText instance from an object representation - * @static - * @memberOf fabric.IText - * @param {Object} object Object to create an instance from - * @return {fabric.IText} instance of fabric.IText - */ - fabric.IText.fromObject = function(object) { - return new fabric.IText(object.text, clone(object)); - }; -})(); - - -(function() { - - var clone = fabric.util.object.clone; - - fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * Initializes all the interactive behavior of IText - */ - initBehavior: function() { - this.initAddedHandler(); - this.initRemovedHandler(); - this.initCursorSelectionHandlers(); - this.initDoubleClickSimulation(); - }, - - /** - * Initializes "selected" event handler - */ - initSelectedHandler: function() { - this.on('selected', function() { - - var _this = this; - setTimeout(function() { - _this.selected = true; - }, 100); - }); - }, - - /** - * Initializes "added" event handler - */ - initAddedHandler: function() { - var _this = this; - this.on('added', function() { - if (this.canvas && !this.canvas._hasITextHandlers) { - this.canvas._hasITextHandlers = true; - this._initCanvasHandlers(); - } - - // Track IText instances per-canvas. Only register in this array once added - // to a canvas; we don't want to leak a reference to the instance forever - // simply because it existed at some point. - // - // (Might be added to a collection, but not on a canvas.) - if (_this.canvas) { - _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; - _this.canvas._iTextInstances.push(_this); - } - }); - }, - - initRemovedHandler: function() { - var _this = this; - this.on('removed', function() { - // (Might be removed from a collection, but not on a canvas.) - if (_this.canvas) { - _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; - fabric.util.removeFromArray(_this.canvas._iTextInstances, _this); - } - }); - }, - - /** - * @private - */ - _initCanvasHandlers: function() { - var _this = this; - - this.canvas.on('selection:cleared', function() { - fabric.IText.prototype.exitEditingOnOthers(_this.canvas); - }); - - this.canvas.on('mouse:up', function() { - if (_this.canvas._iTextInstances) { - _this.canvas._iTextInstances.forEach(function(obj) { - obj.__isMousedown = false; - }); - } - }); - - this.canvas.on('object:selected', function() { - fabric.IText.prototype.exitEditingOnOthers(_this.canvas); - }); - }, - - /** - * @private - */ - _tick: function() { - this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete'); - }, - - /** - * @private - */ - _animateCursor: function(obj, targetOpacity, duration, completeMethod) { - - var tickState; - - tickState = { - isAborted: false, - abort: function() { - this.isAborted = true; - }, - }; - - obj.animate('_currentCursorOpacity', targetOpacity, { - duration: duration, - onComplete: function() { - if (!tickState.isAborted) { - obj[completeMethod](); - } - }, - onChange: function() { - if (obj.canvas) { - obj.canvas.clearContext(obj.canvas.contextTop || obj.ctx); - obj.renderCursorOrSelection(); - } - }, - abort: function() { - return tickState.isAborted; - } - }); - return tickState; - }, - - /** - * @private - */ - _onTickComplete: function() { - - var _this = this; - - if (this._cursorTimeout1) { - clearTimeout(this._cursorTimeout1); - } - this._cursorTimeout1 = setTimeout(function() { - _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick'); - }, 100); - }, - - /** - * Initializes delayed cursor - */ - initDelayedCursor: function(restart) { - var _this = this, - delay = restart ? 0 : this.cursorDelay; - - this._currentTickState && this._currentTickState.abort(); - this._currentTickCompleteState && this._currentTickCompleteState.abort(); - clearTimeout(this._cursorTimeout1); - this._currentCursorOpacity = 1; - if (this.canvas) { - this.canvas.clearContext(this.canvas.contextTop || this.ctx); - this.renderCursorOrSelection(); - } - if (this._cursorTimeout2) { - clearTimeout(this._cursorTimeout2); - } - this._cursorTimeout2 = setTimeout(function() { - _this._tick(); - }, delay); - }, - - /** - * Aborts cursor animation and clears all timeouts - */ - abortCursorAnimation: function() { - this._currentTickState && this._currentTickState.abort(); - this._currentTickCompleteState && this._currentTickCompleteState.abort(); - - clearTimeout(this._cursorTimeout1); - clearTimeout(this._cursorTimeout2); - - this._currentCursorOpacity = 0; - this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx); - }, - - /** - * Selects entire text - */ - selectAll: function() { - this.setSelectionStart(0); - this.setSelectionEnd(this.text.length); - }, - - /** - * Returns selected text - * @return {String} - */ - getSelectedText: function() { - return this.text.slice(this.selectionStart, this.selectionEnd); - }, - - /** - * Find new selection index representing start of current word according to current selection index - * @param {Number} startFrom Surrent selection index - * @return {Number} New selection index - */ - findWordBoundaryLeft: function(startFrom) { - var offset = 0, index = startFrom - 1; - - // remove space before cursor 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); - }, - - /** - * Selects a line based on the index - * @param {Number} selectionStart Index of a character - */ - selectLine: function(selectionStart) { - var newSelectionStart = this.findLineBoundaryLeft(selectionStart), - newSelectionEnd = this.findLineBoundaryRight(selectionStart); - - this.setSelectionStart(newSelectionStart); - this.setSelectionEnd(newSelectionEnd); - }, - - /** - * Enters editing state - * @return {fabric.IText} thisArg - * @chainable - */ - enterEditing: function() { - if (this.isEditing || !this.editable) { - return; - } - - if (this.canvas) { - this.exitEditingOnOthers(this.canvas); - } - - this.isEditing = true; - - this.initHiddenTextarea(); - this.hiddenTextarea.focus(); - this._updateTextarea(); - this._saveEditingProps(); - this._setEditingProps(); - - this._tick(); - this.fire('editing:entered'); - - if (!this.canvas) { - return this; - } - - this.canvas.renderAll(); - this.canvas.fire('text:editing:entered', { target: this }); - this.initMouseMoveHandler(); - return this; - }, - - exitEditingOnOthers: function(canvas) { - if (canvas._iTextInstances) { - canvas._iTextInstances.forEach(function(obj) { - obj.selected = false; - if (obj.isEditing) { - obj.exitEditing(); - } - }); - } - }, - - /** - * Initializes "mousemove" event handler - */ - initMouseMoveHandler: function() { - var _this = this; - this.canvas.on('mouse:move', function(options) { - if (!_this.__isMousedown || !_this.isEditing) { - return; - } - - var newSelectionStart = _this.getSelectionStartFromPointer(options.e); - if (newSelectionStart >= _this.__selectionStartOnMouseDown) { - _this.setSelectionStart(_this.__selectionStartOnMouseDown); - _this.setSelectionEnd(newSelectionStart); - } - else { - _this.setSelectionStart(newSelectionStart); - _this.setSelectionEnd(_this.__selectionStartOnMouseDown); - } - }); - }, - - /** - * @private - */ - _setEditingProps: function() { - this.hoverCursor = 'text'; - - if (this.canvas) { - this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; - } - - this.borderColor = this.editingBorderColor; - - this.hasControls = this.selectable = false; - this.lockMovementX = this.lockMovementY = true; - }, - - /** - * @private - */ - _updateTextarea: function() { - if (!this.hiddenTextarea) { - return; - } - - this.hiddenTextarea.value = this.text; - this.hiddenTextarea.selectionStart = this.selectionStart; - this.hiddenTextarea.selectionEnd = this.selectionEnd; - }, - - /** - * @private - */ - _saveEditingProps: function() { - this._savedProps = { - hasControls: this.hasControls, - borderColor: this.borderColor, - lockMovementX: this.lockMovementX, - lockMovementY: this.lockMovementY, - hoverCursor: this.hoverCursor, - defaultCursor: this.canvas && this.canvas.defaultCursor, - moveCursor: this.canvas && this.canvas.moveCursor - }; - }, - - /** - * @private - */ - _restoreEditingProps: function() { - if (!this._savedProps) { - return; - } - - this.hoverCursor = this._savedProps.overCursor; - this.hasControls = this._savedProps.hasControls; - this.borderColor = this._savedProps.borderColor; - this.lockMovementX = this._savedProps.lockMovementX; - this.lockMovementY = this._savedProps.lockMovementY; - - if (this.canvas) { - this.canvas.defaultCursor = this._savedProps.defaultCursor; - this.canvas.moveCursor = this._savedProps.moveCursor; - } - }, - - /** - * Exits from editing state - * @return {fabric.IText} thisArg - * @chainable - */ - exitEditing: function() { - - this.selected = false; - this.isEditing = false; - this.selectable = true; - - this.selectionEnd = this.selectionStart; - this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); - this.hiddenTextarea = null; - - this.abortCursorAnimation(); - this._restoreEditingProps(); - this._currentCursorOpacity = 0; - - this.fire('editing:exited'); - this.canvas && this.canvas.fire('text:editing:exited', { target: this }); - - return this; - }, - - /** - * @private - */ - _removeExtraneousStyles: function() { - for (var prop in this.styles) { - if (!this._textLines[prop]) { - delete this.styles[prop]; - } - } - }, - - /** - * @private - */ - _removeCharsFromTo: function(start, end) { - - var i = end; - while (i !== start) { - - var prevIndex = this.get2DCursorLocation(i).charIndex; - i--; - - var index = this.get2DCursorLocation(i).charIndex, - isNewline = index > prevIndex; - - if (isNewline) { - this.removeStyleObject(isNewline, i + 1); - } - else { - this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i); - } - - } - - this.text = this.text.slice(0, start) + - this.text.slice(end); - this._clearCache(); - }, - - /** - * Inserts a character where cursor is (replacing selection if one exists) - * @param {String} _chars Characters to insert - */ - insertChars: function(_chars, useCopiedStyle) { - var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n'; - - this.text = this.text.slice(0, this.selectionStart) + - _chars + - this.text.slice(this.selectionEnd); - - if (this.selectionStart === this.selectionEnd) { - this.insertStyleObjects(_chars, isEndOfLine, useCopiedStyle); - } - // else if (this.selectionEnd - this.selectionStart > 1) { - // TODO: replace styles properly - // console.log('replacing MORE than 1 char'); - // } - this.setSelectionStart(this.selectionStart + _chars.length); - this.setSelectionEnd(this.selectionStart); - this._clearCache(); - this.canvas && this.canvas.renderAll(); - - this.setCoords(); - this.fire('changed'); - this.canvas && this.canvas.fire('text:changed', { target: this }); - }, - - /** - * Inserts new style object - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Boolean} isEndOfLine True if it's end of line - */ - insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { - - this.shiftLineStyles(lineIndex, +1); - - if (!this.styles[lineIndex + 1]) { - this.styles[lineIndex + 1] = { }; - } - - var currentCharStyle = this.styles[lineIndex][charIndex - 1], - newLineStyles = { }; - - // if there's nothing after cursor, - // we clone current char style onto the next (otherwise empty) 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; - } - this._clearCache(); - }, - - /** - * Inserts style object for a given line/char index - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Object} [style] Style object to insert, if given - */ - insertCharStyleObject: function(lineIndex, charIndex, style) { - - var currentLineStyles = this.styles[lineIndex], - currentLineStylesCloned = clone(currentLineStyles); - - if (charIndex === 0 && !style) { - charIndex = 1; - } - - // shift all char styles by 1 forward - // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,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]); - this._clearCache(); - }, - - /** - * Inserts style object(s) - * @param {String} _chars Characters at the location where style is inserted - * @param {Boolean} isEndOfLine True if it's end of line - * @param {Boolean} [useCopiedStyle] Style to insert - */ - insertStyleObjects: function(_chars, isEndOfLine, useCopiedStyle) { - // removed shortcircuit over isEmptyStyles - - 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 (useCopiedStyle) { - this._insertStyles(this.copiedStyles); - } - 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 textOnPreviousLine = this._textLines[lineIndex - 1], - newCharIndexOnPrevLine = textOnPreviousLine - ? textOnPreviousLine.length - : 0; - - if (!this.styles[lineIndex - 1]) { - this.styles[lineIndex - 1] = { }; - } - - for (charIndex in this.styles[lineIndex]) { - this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] - = this.styles[lineIndex][charIndex]; - } - - this.shiftLineStyles(lineIndex, -1); - } - else { - var currentLineStyles = this.styles[lineIndex]; - - if (currentLineStyles) { - var offset = this.selectionStart === this.selectionEnd ? -1 : 0; - delete currentLineStyles[charIndex + offset]; - // console.log('deleting', lineIndex, charIndex + offset); - } - - var currentLineStylesCloned = clone(currentLineStyles); - - // shift all styles by 1 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.initMouseupHandler(); - this.initClicks(); - }, - - /** - * Initializes double and triple click event handlers - */ - initClicks: function() { - this.on('dblclick', function(options) { - this.selectWord(this.getSelectionStartFromPointer(options.e)); - }); - this.on('tripleclick', function(options) { - this.selectLine(this.getSelectionStartFromPointer(options.e)); - }); - }, - - /** - * Initializes "mousedown" event handler - */ - initMousedownHandler: function() { - this.on('mousedown', function(options) { - - var pointer = this.canvas.getPointer(options.e); - - this.__mousedownX = pointer.x; - this.__mousedownY = pointer.y; - this.__isMousedown = true; - - if (this.hiddenTextarea && this.canvas) { - this.canvas.wrapperEl.appendChild(this.hiddenTextarea); - } - - if (this.selected) { - this.setCursorByClick(options.e); - } - - if (this.isEditing) { - this.__selectionStartOnMouseDown = this.selectionStart; - this.initDelayedCursor(true); - } - }); - }, - - /** - * @private - */ - _isObjectMoved: function(e) { - var pointer = this.canvas.getPointer(e); - - return this.__mousedownX !== pointer.x || - this.__mousedownY !== pointer.y; - }, - - /** - * Initializes "mouseup" event handler - */ - initMouseupHandler: function() { - this.on('mouseup', function(options) { - this.__isMousedown = false; - if (this._isObjectMoved(options.e)) { - return; - } - - if (this.__lastSelected) { - this.enterEditing(); - this.initDelayedCursor(true); - } - this.selected = true; - }); - }, - - /** - * Changes cursor location in a text depending on passed pointer (x/y) object - * @param {Event} e Event object - */ - setCursorByClick: function(e) { - var newSelectionStart = this.getSelectionStartFromPointer(e); - - if (e.shiftKey) { - if (newSelectionStart < this.selectionStart) { - this.setSelectionEnd(this.selectionStart); - this.setSelectionStart(newSelectionStart); - } - else { - this.setSelectionEnd(newSelectionStart); - } - } - else { - this.setSelectionStart(newSelectionStart); - this.setSelectionEnd(newSelectionStart); - } - }, - - /** - * @private - * @param {Event} e Event object - * @return {Object} Coordinates of a pointer (x, y) - */ - _getLocalRotatedPointer: function(e) { - var pointer = this.canvas.getPointer(e), - - pClicked = new fabric.Point(pointer.x, pointer.y), - pLeftTop = new fabric.Point(this.left, this.top), - - rotated = fabric.util.rotatePoint( - pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle)); - - return this.getLocalPointer(e, rotated); - }, - - /** - * Returns index of a character corresponding to where an object was clicked - * @param {Event} e Event object - * @return {Number} Index of a character - */ - getSelectionStartFromPointer: function(e) { - var mouseOffset = this._getLocalRotatedPointer(e), - prevWidth = 0, - width = 0, - height = 0, - charIndex = 0, - newSelectionStart, - line; - - for (var i = 0, len = this._textLines.length; i < len; i++) { - line = this._textLines[i].split(''); - height += this._getHeightOfLine(this.ctx, i) * this.scaleY; - - var widthOfLine = this._getLineWidth(this.ctx, i), - lineLeftOffset = this._getLineLeftOffset(widthOfLine); - - width = lineLeftOffset * this.scaleX; - - if (this.flipX) { - // when oject is horizontally flipped we reverse chars - this._textLines[i] = line.reverse().join(''); - } - - for (var j = 0, jlen = line.length; j < jlen; j++) { - - var _char = line[j]; - prevWidth = width; - - width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) * - this.scaleX; - - if (height <= mouseOffset.y || width <= mouseOffset.x) { - charIndex++; - continue; - } - - return this._getNewSelectionStartFromOffset( - mouseOffset, prevWidth, width, charIndex + i, jlen); - } - - if (mouseOffset.y < height) { - return this._getNewSelectionStartFromOffset( - mouseOffset, prevWidth, width, charIndex + i, jlen); - } - } - - // clicked somewhere after all chars, so set at the 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: fixed; bottom: 20px; left: 0px; opacity: 0;' - + ' width: 0px; height: 0px; z-index: -999;'; - fabric.document.body.appendChild(this.hiddenTextarea); - - fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); - - if (!this._clickHandlerInitialized && this.canvas) { - fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); - this._clickHandlerInitialized = true; - } - }, - - /** - * @private - */ - _keysMap: { - 8: 'removeChars', - 9: 'exitEditing', - 27: 'exitEditing', - 13: 'insertNewline', - 33: 'moveCursorUp', - 34: 'moveCursorDown', - 35: 'moveCursorRight', - 36: 'moveCursorLeft', - 37: 'moveCursorLeft', - 38: 'moveCursorUp', - 39: 'moveCursorRight', - 40: 'moveCursorDown', - 46: 'forwardDelete' - }, - - /** - * @private - */ - _ctrlKeysMap: { - 65: 'selectAll', - 88: 'cut' - }, - - onClick: function() { - // No need to trigger click event here, focus is enough to have the keyboard appear on 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, true); - } - }, - - /** - * Cuts text - * @param {Event} e Event object - */ - cut: function(e) { - if (this.selectionStart === this.selectionEnd) { - return; - } - - this.copy(); - this.removeChars(e); - }, - - /** - * @private - * @param {Event} e Event object - * @return {Object} Clipboard data object - */ - _getClipboardData: function(e) { - return e && (e.clipboardData || fabric.window.clipboardData); - }, - - /** - * Handles keypress event - * @param {Event} e Event object - */ - onKeyPress: function(e) { - if (!this.isEditing || e.metaKey || e.ctrlKey) { - return; - } - if (e.which !== 0) { - this.insertChars(String.fromCharCode(e.which)); - } - e.stopPropagation(); - }, - - /** - * Gets start offset of a selection - * @param {Event} e Event object - * @param {Boolean} isRight - * @return {Number} - */ - getDownCursorOffset: function(e, isRight) { - var selectionProp = isRight ? this.selectionEnd : this.selectionStart, - _char, lineLeftOffset, - textBeforeCursor = this.text.slice(0, selectionProp), - textAfterCursor = this.text.slice(selectionProp), - - textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), - textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1], - textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || { })[1] || '', - - cursorLocation = this.get2DCursorLocation(selectionProp); - - // if on last line, down cursor goes to end of line - if (cursorLocation.lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { - - // move to the end of a text - return this.text.length - selectionProp; - } - - var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex); - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, - lineIndex = cursorLocation.lineIndex; - - for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { - _char = textOnSameLineBeforeCursor[i]; - widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); - } - - var indexOnNextLine = this._getIndexOnNextLine( - cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor); - - return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; - }, - - /** - * @private - */ - _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) { - var lineIndex = cursorLocation.lineIndex + 1, - widthOfNextLine = this._getLineWidth(this.ctx, lineIndex), - lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), - widthOfCharsOnNextLine = lineLeftOffset, - indexOnNextLine = 0, - foundMatch; - - for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { - - var _char = textOnNextLine[j], - widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); - - widthOfCharsOnNextLine += widthOfChar; - - if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { - - foundMatch = true; - - var leftEdge = widthOfCharsOnNextLine - widthOfChar, - rightEdge = widthOfCharsOnNextLine, - offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), - offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); - - indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; - - break; - } - } - - // reached 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.setSelectionStart(this.selectionStart + offset); - this.setSelectionEnd(this.selectionStart); - }, - - /** - * private - */ - swapSelectionPoints: function() { - var swapSel = this.selectionEnd; - this.setSelectionEnd(this.selectionStart); - this.setSelectionStart(swapSel); - }, - - /** - * Moves cursor down while keeping selection - * @param {Number} offset - */ - moveCursorDownWithShift: function(offset) { - if (this.selectionEnd === this.selectionStart) { - this._selectionDirection = 'right'; - } - if (this._selectionDirection === 'right') { - this.setSelectionEnd(this.selectionEnd + offset); - } - else { - this.setSelectionStart(this.selectionStart + offset); - } - if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'left') { - this.swapSelectionPoints(); - this._selectionDirection = 'right'; - } - if (this.selectionEnd > this.text.length) { - this.setSelectionEnd(this.text.length); - } - }, - - /** - * @param {Event} e Event object - * @param {Boolean} isRight - * @return {Number} - */ - getUpCursorOffset: function(e, isRight) { - var selectionProp = isRight ? this.selectionEnd : this.selectionStart, - cursorLocation = this.get2DCursorLocation(selectionProp); - // if on first line, up cursor goes to start of line - if (cursorLocation.lineIndex === 0 || e.metaKey || e.keyCode === 33) { - return selectionProp; - } - - var textBeforeCursor = this.text.slice(0, selectionProp), - textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), - textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '', - _char, - widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex), - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), - widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, - lineIndex = cursorLocation.lineIndex; - - for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { - _char = textOnSameLineBeforeCursor[i]; - widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); - } - - var indexOnPrevLine = this._getIndexOnPrevLine( - cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor); - - return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; - }, - - /** - * @private - */ - _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) { - - var lineIndex = cursorLocation.lineIndex - 1, - widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex), - lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), - widthOfCharsOnPreviousLine = lineLeftOffset, - indexOnPrevLine = 0, - foundMatch; - - for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { - - var _char = textOnPreviousLine[j], - widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); - - widthOfCharsOnPreviousLine += widthOfChar; - - if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { - - foundMatch = true; - - var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, - rightEdge = widthOfCharsOnPreviousLine, - offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), - offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); - - indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); - - break; - } - } - - // reached 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.selectionEnd === this.selectionStart) { - this._selectionDirection = 'left'; - } - if (this._selectionDirection === 'right') { - this.setSelectionEnd(this.selectionEnd - offset); - } - else { - this.setSelectionStart(this.selectionStart - offset); - } - if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') { - this.swapSelectionPoints(); - this._selectionDirection = 'left'; - } - }, - - /** - * Moves cursor up without shift - * @param {Number} offset - */ - moveCursorUpWithoutShift: function(offset) { - if (this.selectionStart === this.selectionEnd) { - this.setSelectionStart(this.selectionStart - offset); - } - this.setSelectionEnd(this.selectionStart); - - this._selectionDirection = 'left'; - }, - - /** - * Moves cursor left - * @param {Event} e Event object - */ - moveCursorLeft: function(e) { - if (this.selectionStart === 0 && this.selectionEnd === 0) { - return; - } - - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - - if (e.shiftKey) { - this.moveCursorLeftWithShift(e); - } - else { - this.moveCursorLeftWithoutShift(e); - } - - this.initDelayedCursor(); - }, - - /** - * @private - */ - _move: function(e, prop, direction) { - var propMethod = (prop === 'selectionStart' ? 'setSelectionStart' : 'setSelectionEnd'); - if (e.altKey) { - this[propMethod](this['findWordBoundary' + direction](this[prop])); - } - else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { - this[propMethod](this['findLineBoundary' + direction](this[prop])); - } - else { - this[propMethod](this[prop] + (direction === 'Left' ? -1 : 1)); - } - }, - - /** - * @private - */ - _moveLeft: function(e, prop) { - this._move(e, prop, 'Left'); - }, - - /** - * @private - */ - _moveRight: function(e, prop) { - this._move(e, prop, 'Right'); - }, - - /** - * Moves cursor left without keeping selection - * @param {Event} e - */ - moveCursorLeftWithoutShift: function(e) { - this._selectionDirection = 'left'; - - // only move cursor when there is no selection, - // otherwise we discard it, and leave cursor on same place - if (this.selectionEnd === this.selectionStart) { - this._moveLeft(e, 'selectionStart'); - } - this.setSelectionEnd(this.selectionStart); - }, - - /** - * Moves cursor left while keeping selection - * @param {Event} e - */ - moveCursorLeftWithShift: function(e) { - if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { - this._moveLeft(e, 'selectionEnd'); - } - else { - this._selectionDirection = 'left'; - this._moveLeft(e, 'selectionStart'); - - // increase selection by one if it's a newline - if (this.text.charAt(this.selectionStart) === '\n') { - this.setSelectionStart(this.selectionStart - 1); - } - } - }, - - /** - * Moves cursor right - * @param {Event} e Event object - */ - moveCursorRight: function(e) { - if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) { - return; - } - - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - - if (e.shiftKey) { - this.moveCursorRightWithShift(e); - } - else { - this.moveCursorRightWithoutShift(e); - } - - this.initDelayedCursor(); - }, - - /** - * Moves cursor right while keeping selection - * @param {Event} e - */ - moveCursorRightWithShift: function(e) { - if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { - this._moveRight(e, 'selectionStart'); - } - else { - this._selectionDirection = 'right'; - this._moveRight(e, 'selectionEnd'); - - // increase selection by one if it's a newline - if (this.text.charAt(this.selectionEnd - 1) === '\n') { - this.setSelectionEnd(this.selectionEnd + 1); - } - } - }, - - /** - * Moves cursor right without keeping selection - * @param {Event} e Event object - */ - moveCursorRightWithoutShift: function(e) { - this._selectionDirection = 'right'; - - if (this.selectionStart === this.selectionEnd) { - this._moveRight(e, 'selectionStart'); - this.setSelectionEnd(this.selectionStart); - } - else { - this.setSelectionEnd(this.selectionEnd + this.getNumNewLinesInSelectedText()); - this.setSelectionStart(this.selectionEnd); - } - }, - - /** - * Removes characters selected by selection - * @param {Event} e Event object - */ - removeChars: function(e) { - if (this.selectionStart === this.selectionEnd) { - this._removeCharsNearCursor(e); - } - else { - this._removeCharsFromTo(this.selectionStart, this.selectionEnd); - } - - this.setSelectionEnd(this.selectionStart); - - this._removeExtraneousStyles(); - - this._clearCache(); - this.canvas && this.canvas.renderAll(); - - this.setCoords(); - this.fire('changed'); - this.canvas && this.canvas.fire('text:changed', { target: this }); - }, - - /** - * @private - * @param {Event} e Event object - */ - _removeCharsNearCursor: function(e) { - if (this.selectionStart !== 0) { - - if (e.metaKey) { - // remove all till the start of current line - var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); - - this._removeCharsFromTo(leftLineBoundary, this.selectionStart); - this.setSelectionStart(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.setSelectionStart(leftWordBoundary); - } - else { - var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n'; - this.removeStyleObject(isBeginningOfLine); - this.setSelectionStart(this.selectionStart - 1); - this.text = this.text.slice(0, this.selectionStart) + - this.text.slice(this.selectionStart + 1); - } - } - } -}); - - -/* _TO_SVG_START_ */ -fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * @private - */ - _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) { - if (!this.styles[lineIndex]) { - this.callSuper('_setSVGTextLineText', - lineIndex, textSpans, height, textLeftOffset, textTopOffset); - } - else { - this._setSVGTextLineChars( - lineIndex, textSpans, height, textLeftOffset, textBgRects); - } - }, - - /** - * @private - */ - _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) { - - var chars = this._textLines[lineIndex].split(''), - charOffset = 0, - lineLeftOffset = this._getSVGLineLeftOffset(lineIndex) - this.width / 2, - lineOffset = this._getSVGLineTopOffset(lineIndex), - heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); - - for (var i = 0, len = chars.length; i < len; i++) { - var styleDecl = this.styles[lineIndex][i] || { }; - - textSpans.push( - this._createTextCharSpan( - chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset)); - - var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); - - if (styleDecl.textBackgroundColor) { - textBgRects.push( - this._createTextCharBg( - styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset)); - } - - charOffset += charWidth; - } - }, - - /** - * @private - */ - _getSVGLineLeftOffset: function(lineIndex) { - return fabric.util.toFixed(this._getLineLeftOffset(this.__lineWidths[lineIndex]), 2); - }, - - /** - * @private - */ - _getSVGLineTopOffset: function(lineIndex) { - var lineTopOffset = 0, lastHeight = 0; - for (var j = 0; j < lineIndex; j++) { - lineTopOffset += this._getHeightOfLine(this.ctx, j); - } - lastHeight = this._getHeightOfLine(this.ctx, j); - return { - lineTop: lineTopOffset, - offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) + }, repeatedCommands = { + m: "l", + M: "L" }; - }, + if (fabric.Path) { + fabric.warn("fabric.Path is already defined"); + return; + } + fabric.Path = fabric.util.createClass(fabric.Object, { + type: "path", + path: null, + minX: 0, + minY: 0, + initialize: function(path, options) { + options = options || {}; + this.setOptions(options); + if (!path) { + throw new Error("`path` argument is required"); + } + var fromArray = _toString.call(path) === "[object Array]"; + this.path = fromArray ? path : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); + if (!this.path) { + return; + } + if (!fromArray) { + this.path = this._parsePath(); + } + this._setPositionDimensions(); + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + _setPositionDimensions: function() { + var calcDim = this._parseDimensions(); + this.minX = calcDim.left; + this.minY = calcDim.top; + this.width = calcDim.width; + this.height = calcDim.height; + calcDim.left += this.originX === "center" ? this.width / 2 : this.originX === "right" ? this.width : 0; + calcDim.top += this.originY === "center" ? this.height / 2 : this.originY === "bottom" ? this.height : 0; + this.top = this.top || calcDim.top; + this.left = this.left || calcDim.left; + this.pathOffset = this.pathOffset || { + x: this.minX + this.width / 2, + y: this.minY + this.height / 2 + }; + }, + _render: function(ctx) { + var current, previous = null, subpathStartX = 0, subpathStartY = 0, x = 0, y = 0, controlX = 0, controlY = 0, tempX, tempY, l = -this.pathOffset.x, t = -this.pathOffset.y; + if (this.group && this.group.type === "path-group") { + l = 0; + t = 0; + } + ctx.beginPath(); + for (var i = 0, len = this.path.length; i < len; ++i) { + current = this.path[i]; + switch (current[0]) { + case "l": + x += current[1]; + y += current[2]; + ctx.lineTo(x + l, y + t); + break; - /** - * @private - */ - _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { - return [ - //jscs:disable validateIndentation - '' - //jscs:enable validateIndentation - ].join(''); - }, + case "L": + x = current[1]; + y = current[2]; + ctx.lineTo(x + l, y + t); + break; - /** - * @private - */ - _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) { + case "h": + x += current[1]; + ctx.lineTo(x + l, y + t); + break; - var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ - visible: true, - fill: this.fill, - stroke: this.stroke, - type: 'text' - }, styleDecl)); + case "H": + x = current[1]; + ctx.lineTo(x + l, y + t); + break; - return [ - //jscs:disable validateIndentation - '', - fabric.util.string.escapeXml(_char), - '' - //jscs:enable validateIndentation - ].join(''); - } + case "v": + y += current[1]; + ctx.lineTo(x + l, y + t); + break; + + case "V": + y = current[1]; + ctx.lineTo(x + l, y + t); + break; + + case "m": + x += current[1]; + y += current[2]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case "M": + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case "c": + tempX = x + current[5]; + tempY = y + current[6]; + controlX = x + current[3]; + controlY = y + current[4]; + ctx.bezierCurveTo(x + current[1] + l, y + current[2] + t, controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + break; + + case "C": + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + ctx.bezierCurveTo(current[1] + l, current[2] + t, controlX + l, controlY + t, x + l, y + t); + break; + + case "s": + tempX = x + current[3]; + tempY = y + current[4]; + if (previous[0].match(/[CcSs]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + ctx.bezierCurveTo(controlX + l, controlY + t, x + current[1] + l, y + current[2] + t, tempX + l, tempY + t); + controlX = x + current[1]; + controlY = y + current[2]; + x = tempX; + y = tempY; + break; + + case "S": + tempX = current[3]; + tempY = current[4]; + if (previous[0].match(/[CcSs]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + ctx.bezierCurveTo(controlX + l, controlY + t, current[1] + l, current[2] + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + break; + + case "q": + tempX = x + current[3]; + tempY = y + current[4]; + controlX = x + current[1]; + controlY = y + current[2]; + ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + break; + + case "Q": + tempX = current[3]; + tempY = current[4]; + ctx.quadraticCurveTo(current[1] + l, current[2] + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + break; + + case "t": + tempX = x + current[1]; + tempY = y + current[2]; + if (previous[0].match(/[QqTt]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + break; + + case "T": + tempX = current[1]; + tempY = current[2]; + if (previous[0].match(/[QqTt]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + break; + + case "a": + drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + x + l, current[7] + y + t ]); + x += current[6]; + y += current[7]; + break; + + case "A": + drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + l, current[7] + t ]); + x = current[6]; + y = current[7]; + break; + + case "z": + case "Z": + x = subpathStartX; + y = subpathStartY; + ctx.closePath(); + break; + } + previous = current; + } + this._renderFill(ctx); + this._renderStroke(ctx); + }, + toString: function() { + return "#"; + }, + toObject: function(propertiesToInclude) { + var o = extend(this.callSuper("toObject", propertiesToInclude), { + path: this.path.map(function(item) { + return item.slice(); + }), + pathOffset: this.pathOffset + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + if (this.transformMatrix) { + o.transformMatrix = this.transformMatrix; + } + return o; + }, + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.path = this.sourcePath; + } + delete o.sourcePath; + return o; + }, + toSVG: function(reviver) { + var chunks = [], markup = this._createBaseSVGMarkup(), addTransform = ""; + for (var i = 0, len = this.path.length; i < len; i++) { + chunks.push(this.path[i].join(" ")); + } + var path = chunks.join(" "); + if (!(this.group && this.group.type === "path-group")) { + addTransform = " translate(" + -this.pathOffset.x + ", " + -this.pathOffset.y + ") "; + } + markup.push("\n"); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return this.path.length; + }, + _parsePath: function() { + var result = [], coords = [], currentPath, parsed, re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi, match, coordsStr; + for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { + currentPath = this.path[i]; + coordsStr = currentPath.slice(1).trim(); + coords.length = 0; + while (match = re.exec(coordsStr)) { + coords.push(match[0]); + } + coordsParsed = [ currentPath.charAt(0) ]; + for (var j = 0, jlen = coords.length; j < jlen; j++) { + parsed = parseFloat(coords[j]); + if (!isNaN(parsed)) { + coordsParsed.push(parsed); + } + } + var command = coordsParsed[0], commandLength = commandLengths[command.toLowerCase()], repeatedCommand = repeatedCommands[command] || command; + if (coordsParsed.length - 1 > commandLength) { + for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { + result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; + } + } else { + result.push(coordsParsed); + } + } + return result; + }, + _parseDimensions: function() { + var aX = [], aY = [], current, previous = null, subpathStartX = 0, subpathStartY = 0, x = 0, y = 0, controlX = 0, controlY = 0, tempX, tempY, bounds; + for (var i = 0, len = this.path.length; i < len; ++i) { + current = this.path[i]; + switch (current[0]) { + case "l": + x += current[1]; + y += current[2]; + bounds = []; + break; + + case "L": + x = current[1]; + y = current[2]; + bounds = []; + break; + + case "h": + x += current[1]; + bounds = []; + break; + + case "H": + x = current[1]; + bounds = []; + break; + + case "v": + y += current[1]; + bounds = []; + break; + + case "V": + y = current[1]; + bounds = []; + break; + + case "m": + x += current[1]; + y += current[2]; + subpathStartX = x; + subpathStartY = y; + bounds = []; + break; + + case "M": + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + bounds = []; + break; + + case "c": + tempX = x + current[5]; + tempY = y + current[6]; + controlX = x + current[3]; + controlY = y + current[4]; + bounds = fabric.util.getBoundsOfCurve(x, y, x + current[1], y + current[2], controlX, controlY, tempX, tempY); + x = tempX; + y = tempY; + break; + + case "C": + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + bounds = fabric.util.getBoundsOfCurve(x, y, current[1], current[2], controlX, controlY, x, y); + break; + + case "s": + tempX = x + current[3]; + tempY = y + current[4]; + if (previous[0].match(/[CcSs]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, x + current[1], y + current[2], tempX, tempY); + controlX = x + current[1]; + controlY = y + current[2]; + x = tempX; + y = tempY; + break; + + case "S": + tempX = current[3]; + tempY = current[4]; + if (previous[0].match(/[CcSs]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, current[1], current[2], tempX, tempY); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + break; + + case "q": + tempX = x + current[3]; + tempY = y + current[4]; + controlX = x + current[1]; + controlY = y + current[2]; + bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY); + x = tempX; + y = tempY; + break; + + case "Q": + controlX = current[1]; + controlY = current[2]; + bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, current[3], current[4]); + x = current[3]; + y = current[4]; + break; + + case "t": + tempX = x + current[1]; + tempY = y + current[2]; + if (previous[0].match(/[QqTt]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY); + x = tempX; + y = tempY; + break; + + case "T": + tempX = current[1]; + tempY = current[2]; + if (previous[0].match(/[QqTt]/) === null) { + controlX = x; + controlY = y; + } else { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY); + x = tempX; + y = tempY; + break; + + case "a": + bounds = fabric.util.getBoundsOfArc(x, y, current[1], current[2], current[3], current[4], current[5], current[6] + x, current[7] + y); + x += current[6]; + y += current[7]; + break; + + case "A": + bounds = fabric.util.getBoundsOfArc(x, y, current[1], current[2], current[3], current[4], current[5], current[6], current[7]); + x = current[6]; + y = current[7]; + break; + + case "z": + case "Z": + x = subpathStartX; + y = subpathStartY; + break; + } + previous = current; + bounds.forEach(function(point) { + aX.push(point.x); + aY.push(point.y); + }); + aX.push(x); + aY.push(y); + } + var minX = min(aX), minY = min(aY), maxX = max(aX), maxY = max(aY), deltaX = maxX - minX, deltaY = maxY - minY, o = { + left: minX, + top: minY, + width: deltaX, + height: deltaY + }; + return o; + } + }); + fabric.Path.fromObject = function(object, callback) { + if (typeof object.path === "string") { + fabric.loadSVGFromURL(object.path, function(elements) { + var path = elements[0], pathUrl = object.path; + delete object.path; + fabric.util.object.extend(path, object); + path.setSourcePath(pathUrl); + callback(path); + }); + } else { + callback(new fabric.Path(object.path, object)); + } + }; + fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat([ "d" ]); + fabric.Path.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); + callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); + }; + fabric.Path.async = true; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, invoke = fabric.util.array.invoke, parentToObject = fabric.Object.prototype.toObject; + if (fabric.PathGroup) { + fabric.warn("fabric.PathGroup is already defined"); + return; + } + fabric.PathGroup = fabric.util.createClass(fabric.Path, { + type: "path-group", + fill: "", + initialize: function(paths, options) { + options = options || {}; + this.paths = paths || []; + for (var i = this.paths.length; i--; ) { + this.paths[i].group = this; + } + if (options.toBeParsed) { + this.parseDimensionsFromPaths(options); + delete options.toBeParsed; + } + this.setOptions(options); + this.setCoords(); + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + parseDimensionsFromPaths: function(options) { + var points, p, xC = [], yC = [], path, height, width, m = this.transformMatrix; + for (var j = this.paths.length; j--; ) { + path = this.paths[j]; + height = path.height + path.strokeWidth; + width = path.width + path.strokeWidth; + points = [ { + x: path.left, + y: path.top + }, { + x: path.left + width, + y: path.top + }, { + x: path.left, + y: path.top + height + }, { + x: path.left + width, + y: path.top + height + } ]; + for (var i = 0; i < points.length; i++) { + p = points[i]; + if (m) { + p = fabric.util.transformPoint(p, m, false); + } + xC.push(p.x); + yC.push(p.y); + } + } + options.width = Math.max.apply(null, xC); + options.height = Math.max.apply(null, yC); + }, + render: function(ctx) { + if (!this.visible) { + return; + } + ctx.save(); + if (this.transformMatrix) { + ctx.transform.apply(ctx, this.transformMatrix); + } + this.transform(ctx); + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + ctx.translate(-this.width / 2, -this.height / 2); + for (var i = 0, l = this.paths.length; i < l; ++i) { + this.paths[i].render(ctx, true); + } + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + ctx.restore(); + }, + _set: function(prop, value) { + if (prop === "fill" && value && this.isSameColor()) { + var i = this.paths.length; + while (i--) { + this.paths[i]._set(prop, value); + } + } + return this.callSuper("_set", prop, value); + }, + toObject: function(propertiesToInclude) { + var o = extend(parentToObject.call(this, propertiesToInclude), { + paths: invoke(this.getObjects(), "toObject", propertiesToInclude) + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + return o; + }, + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.paths = this.sourcePath; + } + return o; + }, + toSVG: function(reviver) { + var objects = this.getObjects(), p = this.getPointByOrigin("left", "top"), translatePart = "translate(" + p.x + " " + p.y + ")", markup = [ "\n" ]; + for (var i = 0, len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG(reviver)); + } + markup.push("\n"); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + toString: function() { + return "#"; + }, + isSameColor: function() { + var firstPathFill = (this.getObjects()[0].get("fill") || "").toLowerCase(); + return this.getObjects().every(function(path) { + return (path.get("fill") || "").toLowerCase() === firstPathFill; + }); + }, + complexity: function() { + return this.paths.reduce(function(total, path) { + return total + (path && path.complexity ? path.complexity() : 0); + }, 0); + }, + getObjects: function() { + return this.paths; + } + }); + fabric.PathGroup.fromObject = function(object, callback) { + if (typeof object.paths === "string") { + fabric.loadSVGFromURL(object.paths, function(elements) { + var pathUrl = object.paths; + delete object.paths; + var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); + callback(pathGroup); + }); + } else { + fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { + delete object.paths; + callback(new fabric.PathGroup(enlivenedObjects, object)); + }); + } + }; + fabric.PathGroup.async = true; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, invoke = fabric.util.array.invoke; + if (fabric.Group) { + return; + } + var _lockProperties = { + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + lockUniScaling: true + }; + fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, { + type: "group", + initialize: function(objects, options) { + options = options || {}; + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + this.originalState = {}; + this.callSuper("initialize"); + if (options.originX) { + this.originX = options.originX; + } + if (options.originY) { + this.originY = options.originY; + } + this._calcBounds(); + this._updateObjectsCoords(); + this.callSuper("initialize", options); + this.setCoords(); + this.saveCoords(); + }, + _updateObjectsCoords: function() { + this.forEachObject(this._updateObjectCoords, this); + }, + _updateObjectCoords: function(object) { + var objectLeft = object.getLeft(), objectTop = object.getTop(), center = this.getCenterPoint(); + object.set({ + originalLeft: objectLeft, + originalTop: objectTop, + left: objectLeft - center.x, + top: objectTop - center.y + }); + object.setCoords(); + object.__origHasControls = object.hasControls; + object.hasControls = false; + }, + toString: function() { + return "#"; + }, + addWithUpdate: function(object) { + this._restoreObjectsState(); + if (object) { + this._objects.push(object); + object.group = this; + } + this.forEachObject(this._setObjectActive, this); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + _setObjectActive: function(object) { + object.set("active", true); + object.group = this; + }, + removeWithUpdate: function(object) { + this._moveFlippedObject(object); + this._restoreObjectsState(); + this.forEachObject(this._setObjectActive, this); + this.remove(object); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + _onObjectAdded: function(object) { + object.group = this; + }, + _onObjectRemoved: function(object) { + delete object.group; + object.set("active", false); + }, + delegatedProperties: { + fill: true, + opacity: true, + fontFamily: true, + fontWeight: true, + fontSize: true, + fontStyle: true, + lineHeight: true, + textDecoration: true, + textAlign: true, + backgroundColor: true + }, + _set: function(key, value) { + if (key in this.delegatedProperties) { + var i = this._objects.length; + while (i--) { + this._objects[i].set(key, value); + } + } + this.callSuper("_set", key, value); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + objects: invoke(this._objects, "toObject", propertiesToInclude) + }); + }, + render: function(ctx) { + if (!this.visible) { + return; + } + ctx.save(); + this.clipTo && fabric.util.clipContext(this, ctx); + this.transform(ctx); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._renderObject(this._objects[i], ctx); + } + this.clipTo && ctx.restore(); + ctx.restore(); + }, + _renderControls: function(ctx, noTransform) { + this.callSuper("_renderControls", ctx, noTransform); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx); + } + }, + _renderObject: function(object, ctx) { + var originalHasRotatingPoint = object.hasRotatingPoint; + if (!object.visible) { + return; + } + object.hasRotatingPoint = false; + object.render(ctx); + object.hasRotatingPoint = originalHasRotatingPoint; + }, + _restoreObjectsState: function() { + this._objects.forEach(this._restoreObjectState, this); + return this; + }, + realizeTransform: function(object) { + this._moveFlippedObject(object); + this._setObjectPosition(object); + return object; + }, + _moveFlippedObject: function(object) { + var oldOriginX = object.get("originX"), oldOriginY = object.get("originY"), center = object.getCenterPoint(); + object.set({ + originX: "center", + originY: "center", + left: center.x, + top: center.y + }); + this._toggleFlipping(object); + var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); + object.set({ + originX: oldOriginX, + originY: oldOriginY, + left: newOrigin.x, + top: newOrigin.y + }); + return this; + }, + _toggleFlipping: function(object) { + if (this.flipX) { + object.toggle("flipX"); + object.set("left", -object.get("left")); + object.setAngle(-object.getAngle()); + } + if (this.flipY) { + object.toggle("flipY"); + object.set("top", -object.get("top")); + object.setAngle(-object.getAngle()); + } + }, + _restoreObjectState: function(object) { + this._setObjectPosition(object); + object.setCoords(); + object.hasControls = object.__origHasControls; + delete object.__origHasControls; + object.set("active", false); + object.setCoords(); + delete object.group; + return this; + }, + _setObjectPosition: function(object) { + var center = this.getCenterPoint(), rotated = this._getRotatedLeftTop(object); + object.set({ + angle: object.getAngle() + this.getAngle(), + left: center.x + rotated.left, + top: center.y + rotated.top, + scaleX: object.get("scaleX") * this.get("scaleX"), + scaleY: object.get("scaleY") * this.get("scaleY") + }); + }, + _getRotatedLeftTop: function(object) { + var groupAngle = this.getAngle() * (Math.PI / 180); + return { + left: -Math.sin(groupAngle) * object.getTop() * this.get("scaleY") + Math.cos(groupAngle) * object.getLeft() * this.get("scaleX"), + top: Math.cos(groupAngle) * object.getTop() * this.get("scaleY") + Math.sin(groupAngle) * object.getLeft() * this.get("scaleX") + }; + }, + destroy: function() { + this._objects.forEach(this._moveFlippedObject, this); + return this._restoreObjectsState(); + }, + saveCoords: function() { + this._originalLeft = this.get("left"); + this._originalTop = this.get("top"); + return this; + }, + hasMoved: function() { + return this._originalLeft !== this.get("left") || this._originalTop !== this.get("top"); + }, + setObjectsCoords: function() { + this.forEachObject(function(object) { + object.setCoords(); + }); + return this; + }, + _calcBounds: function(onlyWidthHeight) { + var aX = [], aY = [], o, prop, props = [ "tr", "br", "bl", "tl" ]; + for (var i = 0, len = this._objects.length; i < len; ++i) { + o = this._objects[i]; + o.setCoords(); + for (var j = 0; j < props.length; j++) { + prop = props[j]; + aX.push(o.oCoords[prop].x); + aY.push(o.oCoords[prop].y); + } + } + this.set(this._getBounds(aX, aY, onlyWidthHeight)); + }, + _getBounds: function(aX, aY, onlyWidthHeight) { + var ivt = fabric.util.invertTransform(this.getViewportTransform()), minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), obj = { + width: maxXY.x - minXY.x || 0, + height: maxXY.y - minXY.y || 0 + }; + if (!onlyWidthHeight) { + obj.left = minXY.x || 0; + obj.top = minXY.y || 0; + if (this.originX === "center") { + obj.left += obj.width / 2; + } + if (this.originX === "right") { + obj.left += obj.width; + } + if (this.originY === "center") { + obj.top += obj.height / 2; + } + if (this.originY === "bottom") { + obj.top += obj.height; + } + } + return obj; + }, + toSVG: function(reviver) { + var markup = [ "\n' ]; + for (var i = 0, len = this._objects.length; i < len; i++) { + markup.push(this._objects[i].toSVG(reviver)); + } + markup.push("\n"); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + get: function(prop) { + if (prop in _lockProperties) { + if (this[prop]) { + return this[prop]; + } else { + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i][prop]) { + return true; + } + } + return false; + } + } else { + if (prop in this.delegatedProperties) { + return this._objects[0] && this._objects[0].get(prop); + } + return this[prop]; + } + } + }); + fabric.Group.fromObject = function(object, callback) { + fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { + delete object.objects; + callback && callback(new fabric.Group(enlivenedObjects, object)); + }); + }; + fabric.Group.async = true; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var extend = fabric.util.object.extend; + if (!global.fabric) { + global.fabric = {}; + } + if (global.fabric.Image) { + fabric.warn("fabric.Image is already defined."); + return; + } + fabric.Image = fabric.util.createClass(fabric.Object, { + type: "image", + crossOrigin: "", + alignX: "none", + alignY: "none", + meetOrSlice: "meet", + _lastScaleX: 1, + _lastScaleY: 1, + initialize: function(element, options) { + options || (options = {}); + this.filters = []; + this.resizeFilters = []; + this.callSuper("initialize", options); + this._initElement(element, options); + this._initConfig(options); + if (options.filters) { + this.filters = options.filters; + this.applyFilters(); + } + }, + getElement: function() { + return this._element; + }, + setElement: function(element, callback, options) { + this._element = element; + this._originalElement = element; + this._initConfig(options); + if (this.filters.length !== 0) { + this.applyFilters(callback); + } else if (callback) { + callback(); + } + return this; + }, + setCrossOrigin: function(value) { + this.crossOrigin = value; + this._element.crossOrigin = value; + return this; + }, + getOriginalSize: function() { + var element = this.getElement(); + return { + width: element.width, + height: element.height + }; + }, + _stroke: function(ctx) { + ctx.save(); + this._setStrokeStyles(ctx); + ctx.beginPath(); + ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); + ctx.closePath(); + ctx.restore(); + }, + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; + ctx.save(); + this._setStrokeStyles(ctx); + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); + ctx.closePath(); + ctx.restore(); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + src: this._originalElement.src || this._originalElement._src, + filters: this.filters.map(function(filterObj) { + return filterObj && filterObj.toObject(); + }), + crossOrigin: this.crossOrigin, + alignX: this.alignX, + alignY: this.alignY, + meetOrSlice: this.meetOrSlice + }); + }, + toSVG: function(reviver) { + var markup = [], x = -this.width / 2, y = -this.height / 2, preserveAspectRatio = "none"; + if (this.group && this.group.type === "path-group") { + x = this.left; + y = this.top; + } + if (this.alignX !== "none" && this.alignY !== "none") { + preserveAspectRatio = "x" + this.alignX + "Y" + this.alignY + " " + this.meetOrSlice; + } + markup.push('\n', '\n"); + if (this.stroke || this.strokeDashArray) { + var origFill = this.fill; + this.fill = null; + markup.push("\n'); + this.fill = origFill; + } + markup.push("\n"); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + getSrc: function() { + if (this.getElement()) { + return this.getElement().src || this.getElement()._src; + } + }, + setSrc: function(src, callback, options) { + fabric.util.loadImage(src, function(img) { + return this.setElement(img, callback, options); + }, this, options && options.crossOrigin); + }, + toString: function() { + return '#'; + }, + clone: function(callback, propertiesToInclude) { + this.constructor.fromObject(this.toObject(propertiesToInclude), callback); + }, + applyFilters: function(callback, filters, imgElement, forResizing) { + filters = filters || this.filters; + imgElement = imgElement || this._originalElement; + if (!imgElement) { + return; + } + var imgEl = imgElement, canvasEl = fabric.util.createCanvasElement(), replacement = fabric.util.createImage(), _this = this; + canvasEl.width = imgEl.width; + canvasEl.height = imgEl.height; + canvasEl.getContext("2d").drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); + if (filters.length === 0) { + this._element = imgElement; + callback && callback(); + return canvasEl; + } + filters.forEach(function(filter) { + filter && filter.applyTo(canvasEl, filter.scaleX || _this.scaleX, filter.scaleY || _this.scaleY); + if (!forResizing && filter && filter.type === "Resize") { + _this.width *= filter.scaleX; + _this.height *= filter.scaleY; + } + }); + replacement.width = canvasEl.width; + replacement.height = canvasEl.height; + if (fabric.isLikelyNode) { + replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); + _this._element = replacement; + !forResizing && (_this._filteredEl = replacement); + callback && callback(); + } else { + replacement.onload = function() { + _this._element = replacement; + !forResizing && (_this._filteredEl = replacement); + callback && callback(); + replacement.onload = canvasEl = imgEl = null; + }; + replacement.src = canvasEl.toDataURL("image/png"); + } + return canvasEl; + }, + _render: function(ctx, noTransform) { + var x, y, imageMargins = this._findMargins(), elementToDraw; + x = noTransform ? this.left : -this.width / 2; + y = noTransform ? this.top : -this.height / 2; + if (this.meetOrSlice === "slice") { + ctx.beginPath(); + ctx.rect(x, y, this.width, this.height); + ctx.clip(); + } + if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) { + this._lastScaleX = this.scaleX; + this._lastScaleY = this.scaleY; + elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true); + } else { + elementToDraw = this._element; + } + elementToDraw && ctx.drawImage(elementToDraw, x + imageMargins.marginX, y + imageMargins.marginY, imageMargins.width, imageMargins.height); + this._renderStroke(ctx); + }, + _needsResize: function() { + return this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY; + }, + _findMargins: function() { + var width = this.width, height = this.height, scales, scale, marginX = 0, marginY = 0; + if (this.alignX !== "none" || this.alignY !== "none") { + scales = [ this.width / this._element.width, this.height / this._element.height ]; + scale = this.meetOrSlice === "meet" ? Math.min.apply(null, scales) : Math.max.apply(null, scales); + width = this._element.width * scale; + height = this._element.height * scale; + if (this.alignX === "Mid") { + marginX = (this.width - width) / 2; + } + if (this.alignX === "Max") { + marginX = this.width - width; + } + if (this.alignY === "Mid") { + marginY = (this.height - height) / 2; + } + if (this.alignY === "Max") { + marginY = this.height - height; + } + } + return { + width: width, + height: height, + marginX: marginX, + marginY: marginY + }; + }, + _resetWidthHeight: function() { + var element = this.getElement(); + this.set("width", element.width); + this.set("height", element.height); + }, + _initElement: function(element) { + this.setElement(fabric.util.getById(element)); + fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); + }, + _initConfig: function(options) { + options || (options = {}); + this.setOptions(options); + this._setWidthHeight(options); + if (this._element && this.crossOrigin) { + this._element.crossOrigin = this.crossOrigin; + } + }, + _initFilters: function(object, callback) { + if (object.filters && object.filters.length) { + fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, "fabric.Image.filters"); + } else { + callback && callback(); + } + }, + _setWidthHeight: function(options) { + this.width = "width" in options ? options.width : this.getElement() ? this.getElement().width || 0 : 0; + this.height = "height" in options ? options.height : this.getElement() ? this.getElement().height || 0 : 0; + }, + complexity: function() { + return 1; + } + }); + fabric.Image.CSS_CANVAS = "canvas-img"; + fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; + fabric.Image.fromObject = function(object, callback) { + fabric.util.loadImage(object.src, function(img) { + fabric.Image.prototype._initFilters.call(object, object, function(filters) { + object.filters = filters || []; + var instance = new fabric.Image(img, object); + callback && callback(instance); + }); + }, null, object.crossOrigin); + }; + fabric.Image.fromURL = function(url, callback, imgOptions) { + fabric.util.loadImage(url, function(img) { + callback && callback(new fabric.Image(img, imgOptions)); + }, null, imgOptions && imgOptions.crossOrigin); + }; + fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y width height preserveAspectRatio xlink:href".split(" ")); + fabric.Image.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES), align = "xMidYMid", meetOrSlice = "meet", alignX, alignY, aspectRatioAttrs; + if (parsedAttributes.preserveAspectRatio) { + aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(" "); + } + if (aspectRatioAttrs && aspectRatioAttrs.length) { + meetOrSlice = aspectRatioAttrs.pop(); + if (meetOrSlice !== "meet" && meetOrSlice !== "slice") { + align = meetOrSlice; + meetOrSlice = "meet"; + } else if (aspectRatioAttrs.length) { + align = aspectRatioAttrs.pop(); + } + } + alignX = align !== "none" ? align.slice(1, 4) : "none"; + alignY = align !== "none" ? align.slice(5, 8) : "none"; + parsedAttributes.alignX = alignX; + parsedAttributes.alignY = alignY; + parsedAttributes.meetOrSlice = meetOrSlice; + fabric.Image.fromURL(parsedAttributes["xlink:href"], callback, extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes)); + }; + fabric.Image.async = true; + fabric.Image.pngCompression = 1; +})(typeof exports !== "undefined" ? exports : this); + +fabric.util.object.extend(fabric.Object.prototype, { + _getAngleValueForStraighten: function() { + var angle = this.getAngle() % 360; + if (angle > 0) { + return Math.round((angle - 1) / 90) * 90; + } + return Math.round(angle / 90) * 90; + }, + straighten: function() { + this.setAngle(this._getAngleValueForStraighten()); + return this; + }, + fxStraighten: function(callbacks) { + callbacks = callbacks || {}; + var empty = function() {}, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; + fabric.util.animate({ + startValue: this.get("angle"), + endValue: this._getAngleValueForStraighten(), + duration: this.FX_DURATION, + onChange: function(value) { + _this.setAngle(value); + onChange(); + }, + onComplete: function() { + _this.setCoords(); + onComplete(); + }, + onStart: function() { + _this.set("active", false); + } + }); + return this; + } }); -/* _TO_SVG_END_ */ +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + straightenObject: function(object) { + object.straighten(); + this.renderAll(); + return this; + }, + fxStraightenObject: function(object) { + object.fxStraighten({ + onChange: this.renderAll.bind(this) + }); + return this; + } +}); + +fabric.Image.filters = fabric.Image.filters || {}; + +fabric.Image.filters.BaseFilter = fabric.util.createClass({ + type: "BaseFilter", + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + setOptions: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + }, + toObject: function() { + return { + type: this.type + }; + }, + toJSON: function() { + return this.toObject(); + } +}); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Brightness", + initialize: function(options) { + options = options || {}; + this.brightness = options.brightness || 0; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, brightness = this.brightness; + for (var i = 0, len = data.length; i < len; i += 4) { + data[i] += brightness; + data[i + 1] += brightness; + data[i + 2] += brightness; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + brightness: this.brightness + }); + } + }); + fabric.Image.filters.Brightness.fromObject = function(object) { + return new fabric.Image.filters.Brightness(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Convolute", + initialize: function(options) { + options = options || {}; + this.opaque = options.opaque; + this.matrix = options.matrix || [ 0, 0, 0, 0, 1, 0, 0, 0, 0 ]; + var canvasEl = fabric.util.createCanvasElement(); + this.tmpCtx = canvasEl.getContext("2d"); + }, + _createImageData: function(w, h) { + return this.tmpCtx.createImageData(w, h); + }, + applyTo: function(canvasEl) { + var weights = this.matrix, context = canvasEl.getContext("2d"), pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), side = Math.round(Math.sqrt(weights.length)), halfSide = Math.floor(side / 2), src = pixels.data, sw = pixels.width, sh = pixels.height, w = sw, h = sh, output = this._createImageData(w, h), dst = output.data, alphaFac = this.opaque ? 1 : 0; + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var sy = y, sx = x, dstOff = (y * w + x) * 4, r = 0, g = 0, b = 0, a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = sy + cy - halfSide, scx = sx + cx - halfSide; + if (scy < 0 || scy > sh || scx < 0 || scx > sw) { + continue; + } + var srcOff = (scy * sw + scx) * 4, wt = weights[cy * side + cx]; + r += src[srcOff] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; + } + } + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); + } + } + context.putImageData(output, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + opaque: this.opaque, + matrix: this.matrix + }); + } + }); + fabric.Image.filters.Convolute.fromObject = function(object) { + return new fabric.Image.filters.Convolute(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "GradientTransparency", + initialize: function(options) { + options = options || {}; + this.threshold = options.threshold || 100; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, threshold = this.threshold, total = data.length; + for (var i = 0, len = data.length; i < len; i += 4) { + data[i + 3] = threshold + 255 * (total - i) / total; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + threshold: this.threshold + }); + } + }); + fabric.Image.filters.GradientTransparency.fromObject = function(object) { + return new fabric.Image.filters.GradientTransparency(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Grayscale", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, len = imageData.width * imageData.height * 4, index = 0, average; + while (index < len) { + average = (data[index] + data[index + 1] + data[index + 2]) / 3; + data[index] = average; + data[index + 1] = average; + data[index + 2] = average; + index += 4; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Grayscale.fromObject = function() { + return new fabric.Image.filters.Grayscale(); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Invert", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i; + for (i = 0; i < iLen; i += 4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Invert.fromObject = function() { + return new fabric.Image.filters.Invert(); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Mask", + initialize: function(options) { + options = options || {}; + this.mask = options.mask; + this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; + }, + applyTo: function(canvasEl) { + if (!this.mask) { + return; + } + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, maskEl = this.mask.getElement(), maskCanvasEl = fabric.util.createCanvasElement(), channel = this.channel, i, iLen = imageData.width * imageData.height * 4; + maskCanvasEl.width = maskEl.width; + maskCanvasEl.height = maskEl.height; + maskCanvasEl.getContext("2d").drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); + var maskImageData = maskCanvasEl.getContext("2d").getImageData(0, 0, maskEl.width, maskEl.height), maskData = maskImageData.data; + for (i = 0; i < iLen; i += 4) { + data[i + 3] = maskData[i + channel]; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + mask: this.mask.toObject(), + channel: this.channel + }); + } + }); + fabric.Image.filters.Mask.fromObject = function(object, callback) { + fabric.util.loadImage(object.mask.src, function(img) { + object.mask = new fabric.Image(img, object.mask); + callback && callback(new fabric.Image.filters.Mask(object)); + }); + }; + fabric.Image.filters.Mask.async = true; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Noise", + initialize: function(options) { + options = options || {}; + this.noise = options.noise || 0; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, noise = this.noise, rand; + for (var i = 0, len = data.length; i < len; i += 4) { + rand = (.5 - Math.random()) * noise; + data[i] += rand; + data[i + 1] += rand; + data[i + 2] += rand; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + noise: this.noise + }); + } + }); + fabric.Image.filters.Noise.fromObject = function(object) { + return new fabric.Image.filters.Noise(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Pixelate", + initialize: function(options) { + options = options || {}; + this.blocksize = options.blocksize || 4; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = imageData.height, jLen = imageData.width, index, i, j, r, g, b, a; + for (i = 0; i < iLen; i += this.blocksize) { + for (j = 0; j < jLen; j += this.blocksize) { + index = i * 4 * jLen + j * 4; + r = data[index]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; + for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { + for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { + index = _i * 4 * jLen + _j * 4; + data[index] = r; + data[index + 1] = g; + data[index + 2] = b; + data[index + 3] = a; + } + } + } + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + blocksize: this.blocksize + }); + } + }); + fabric.Image.filters.Pixelate.fromObject = function(object) { + return new fabric.Image.filters.Pixelate(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "RemoveWhite", + initialize: function(options) { + options = options || {}; + this.threshold = options.threshold || 30; + this.distance = options.distance || 20; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, threshold = this.threshold, distance = this.distance, limit = 255 - threshold, abs = Math.abs, r, g, b; + for (var i = 0, len = data.length; i < len; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + if (r > limit && g > limit && b > limit && abs(r - g) < distance && abs(r - b) < distance && abs(g - b) < distance) { + data[i + 3] = 1; + } + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + threshold: this.threshold, + distance: this.distance + }); + } + }); + fabric.Image.filters.RemoveWhite.fromObject = function(object) { + return new fabric.Image.filters.RemoveWhite(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Sepia", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, avg; + for (i = 0; i < iLen; i += 4) { + avg = .3 * data[i] + .59 * data[i + 1] + .11 * data[i + 2]; + data[i] = avg + 100; + data[i + 1] = avg + 50; + data[i + 2] = avg + 255; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Sepia.fromObject = function() { + return new fabric.Image.filters.Sepia(); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Sepia2", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, r, g, b; + for (i = 0; i < iLen; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + data[i] = (r * .393 + g * .769 + b * .189) / 1.351; + data[i + 1] = (r * .349 + g * .686 + b * .168) / 1.203; + data[i + 2] = (r * .272 + g * .534 + b * .131) / 2.14; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Sepia2.fromObject = function() { + return new fabric.Image.filters.Sepia2(); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Tint", + initialize: function(options) { + options = options || {}; + this.color = options.color || "#000000"; + this.opacity = typeof options.opacity !== "undefined" ? options.opacity : new fabric.Color(this.color).getAlpha(); + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, tintR, tintG, tintB, r, g, b, alpha1, source; + source = new fabric.Color(this.color).getSource(); + tintR = source[0] * this.opacity; + tintG = source[1] * this.opacity; + tintB = source[2] * this.opacity; + alpha1 = 1 - this.opacity; + for (i = 0; i < iLen; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + data[i] = tintR + r * alpha1; + data[i + 1] = tintG + g * alpha1; + data[i + 2] = tintB + b * alpha1; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + color: this.color, + opacity: this.opacity + }); + } + }); + fabric.Image.filters.Tint.fromObject = function(object) { + return new fabric.Image.filters.Tint(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Multiply", + initialize: function(options) { + options = options || {}; + this.color = options.color || "#000000"; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, source; + source = new fabric.Color(this.color).getSource(); + for (i = 0; i < iLen; i += 4) { + data[i] *= source[0] / 255; + data[i + 1] *= source[1] / 255; + data[i + 2] *= source[2] / 255; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + color: this.color + }); + } + }); + fabric.Image.filters.Multiply.fromObject = function(object) { + return new fabric.Image.filters.Multiply(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric; + fabric.Image.filters.Blend = fabric.util.createClass({ + type: "Blend", + initialize: function(options) { + options = options || {}; + this.color = options.color || "#000"; + this.image = options.image || false; + this.mode = options.mode || "multiply"; + this.alpha = options.alpha || 1; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, tr, tg, tb, r, g, b, _r, _g, _b, source, isImage = false; + if (this.image) { + isImage = true; + var _el = fabric.util.createCanvasElement(); + _el.width = this.image.width; + _el.height = this.image.height; + var tmpCanvas = new fabric.StaticCanvas(_el); + tmpCanvas.add(this.image); + var context2 = tmpCanvas.getContext("2d"); + source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; + } else { + source = new fabric.Color(this.color).getSource(); + tr = source[0] * this.alpha; + tg = source[1] * this.alpha; + tb = source[2] * this.alpha; + } + for (var i = 0, len = data.length; i < len; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + if (isImage) { + tr = source[i] * this.alpha; + tg = source[i + 1] * this.alpha; + tb = source[i + 2] * this.alpha; + } + switch (this.mode) { + case "multiply": + data[i] = r * tr / 255; + data[i + 1] = g * tg / 255; + data[i + 2] = b * tb / 255; + break; + + case "screen": + data[i] = 1 - (1 - r) * (1 - tr); + data[i + 1] = 1 - (1 - g) * (1 - tg); + data[i + 2] = 1 - (1 - b) * (1 - tb); + break; + + case "add": + data[i] = Math.min(255, r + tr); + data[i + 1] = Math.min(255, g + tg); + data[i + 2] = Math.min(255, b + tb); + break; + + case "diff": + case "difference": + data[i] = Math.abs(r - tr); + data[i + 1] = Math.abs(g - tg); + data[i + 2] = Math.abs(b - tb); + break; + + case "subtract": + _r = r - tr; + _g = g - tg; + _b = b - tb; + data[i] = _r < 0 ? 0 : _r; + data[i + 1] = _g < 0 ? 0 : _g; + data[i + 2] = _b < 0 ? 0 : _b; + break; + + case "darken": + data[i] = Math.min(r, tr); + data[i + 1] = Math.min(g, tg); + data[i + 2] = Math.min(b, tb); + break; + + case "lighten": + data[i] = Math.max(r, tr); + data[i + 1] = Math.max(g, tg); + data[i + 2] = Math.max(b, tb); + break; + } + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return { + color: this.color, + image: this.image, + mode: this.mode, + alpha: this.alpha + }; + } + }); + fabric.Image.filters.Blend.fromObject = function(object) { + return new fabric.Image.filters.Blend(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), pow = Math.pow, floor = Math.floor, sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin, ceil = Math.ceil; + fabric.Image.filters.Resize = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Resize", + resizeType: "hermite", + scaleX: 0, + scaleY: 0, + lanczosLobes: 3, + applyTo: function(canvasEl, scaleX, scaleY) { + this.rcpScaleX = 1 / scaleX; + this.rcpScaleY = 1 / scaleY; + var oW = canvasEl.width, oH = canvasEl.height, dW = round(oW * scaleX), dH = round(oH * scaleY), imageData; + if (this.resizeType === "sliceHack") { + imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH); + } + if (this.resizeType === "hermite") { + imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH); + } + if (this.resizeType === "bilinear") { + imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH); + } + if (this.resizeType === "lanczos") { + imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH); + } + canvasEl.width = dW; + canvasEl.height = dH; + canvasEl.getContext("2d").putImageData(imageData, 0, 0); + }, + sliceByTwo: function(canvasEl, width, height, newWidth, newHeight) { + var context = canvasEl.getContext("2d"), imageData, multW = .5, multH = .5, signW = 1, signH = 1, doneW = false, doneH = false, stepW = width, stepH = height, tmpCanvas = fabric.util.createCanvasElement(), tmpCtx = tmpCanvas.getContext("2d"); + newWidth = floor(newWidth); + newHeight = floor(newHeight); + tmpCanvas.width = max(newWidth, width); + tmpCanvas.height = max(newHeight, height); + if (newWidth > width) { + multW = 2; + signW = -1; + } + if (newHeight > height) { + multH = 2; + signH = -1; + } + imageData = context.getImageData(0, 0, width, height); + canvasEl.width = max(newWidth, width); + canvasEl.height = max(newHeight, height); + context.putImageData(imageData, 0, 0); + while (!doneW || !doneH) { + width = stepW; + height = stepH; + if (newWidth * signW < floor(stepW * multW * signW)) { + stepW = floor(stepW * multW); + } else { + stepW = newWidth; + doneW = true; + } + if (newHeight * signH < floor(stepH * multH * signH)) { + stepH = floor(stepH * multH); + } else { + stepH = newHeight; + doneH = true; + } + imageData = context.getImageData(0, 0, width, height); + tmpCtx.putImageData(imageData, 0, 0); + context.clearRect(0, 0, stepW, stepH); + context.drawImage(tmpCanvas, 0, 0, width, height, 0, 0, stepW, stepH); + } + return context.getImageData(0, 0, newWidth, newHeight); + }, + lanczosResize: function(canvasEl, oW, oH, dW, dH) { + function lanczosCreate(lobes) { + return function(x) { + if (x > lobes) { + return 0; + } + x *= Math.PI; + if (abs(x) < 1e-16) { + return 1; + } + var xx = x / lobes; + return sin(x) * sin(xx) / x / xx; + }; + } + function process(u) { + var v, i, weight, idx, a, red, green, blue, alpha, fX, fY; + center.x = (u + .5) * ratioX; + icenter.x = floor(center.x); + for (v = 0; v < dH; v++) { + center.y = (v + .5) * ratioY; + icenter.y = floor(center.y); + a = 0, red = 0, green = 0, blue = 0, alpha = 0; + for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { + if (i < 0 || i >= oW) { + continue; + } + fX = floor(1e3 * abs(i - center.x)); + if (!cacheLanc[fX]) { + cacheLanc[fX] = {}; + } + for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { + if (j < 0 || j >= oH) { + continue; + } + fY = floor(1e3 * abs(j - center.y)); + if (!cacheLanc[fX][fY]) { + cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1e3); + } + weight = cacheLanc[fX][fY]; + if (weight > 0) { + idx = (j * oW + i) * 4; + a += weight; + red += weight * srcData[idx]; + green += weight * srcData[idx + 1]; + blue += weight * srcData[idx + 2]; + alpha += weight * srcData[idx + 3]; + } + } + } + idx = (v * dW + u) * 4; + destData[idx] = red / a; + destData[idx + 1] = green / a; + destData[idx + 2] = blue / a; + destData[idx + 3] = alpha / a; + } + if (++u < dW) { + return process(u); + } else { + return destImg; + } + } + var context = canvasEl.getContext("2d"), srcImg = context.getImageData(0, 0, oW, oH), destImg = context.getImageData(0, 0, dW, dH), srcData = srcImg.data, destData = destImg.data, lanczos = lanczosCreate(this.lanczosLobes), ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, range2X = ceil(ratioX * this.lanczosLobes / 2), range2Y = ceil(ratioY * this.lanczosLobes / 2), cacheLanc = {}, center = {}, icenter = {}; + return process(0); + }, + bilinearFiltering: function(canvasEl, w, h, w2, h2) { + var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, color, offset = 0, origPix, ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, context = canvasEl.getContext("2d"), w4 = 4 * (w - 1), img = context.getImageData(0, 0, w, h), pixels = img.data, destImage = context.getImageData(0, 0, w2, h2), destPixels = destImage.data; + for (i = 0; i < h2; i++) { + for (j = 0; j < w2; j++) { + x = floor(ratioX * j); + y = floor(ratioY * i); + xDiff = ratioX * j - x; + yDiff = ratioY * i - y; + origPix = 4 * (y * w + x); + for (chnl = 0; chnl < 4; chnl++) { + a = pixels[origPix + chnl]; + b = pixels[origPix + 4 + chnl]; + c = pixels[origPix + w4 + chnl]; + d = pixels[origPix + w4 + 4 + chnl]; + color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + c * yDiff * (1 - xDiff) + d * xDiff * yDiff; + destPixels[offset++] = color; + } + } + } + return destImage; + }, + hermiteFastResize: function(canvasEl, oW, oH, dW, dH) { + var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, ratioWHalf = ceil(ratioW / 2), ratioHHalf = ceil(ratioH / 2), context = canvasEl.getContext("2d"), img = context.getImageData(0, 0, oW, oH), data = img.data, img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data; + for (var j = 0; j < dH; j++) { + for (var i = 0; i < dW; i++) { + var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + .5) * ratioH; + for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { + var dy = abs(centerY - (yy + .5)) / ratioHHalf, centerX = (i + .5) * ratioW, w0 = dy * dy; + for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { + var dx = abs(centerX - (xx + .5)) / ratioWHalf, w = sqrt(w0 + dx * dx); + if (w > 1 && w < -1) { + continue; + } + weight = 2 * w * w * w - 3 * w * w + 1; + if (weight > 0) { + dx = 4 * (xx + yy * oW); + gxA += weight * data[dx + 3]; + weightsAlpha += weight; + if (data[dx + 3] < 255) { + weight = weight * data[dx + 3] / 250; + } + gxR += weight * data[dx]; + gxG += weight * data[dx + 1]; + gxB += weight * data[dx + 2]; + weights += weight; + } + } + } + data2[x2] = gxR / weights; + data2[x2 + 1] = gxG / weights; + data2[x2 + 2] = gxB / weights; + data2[x2 + 3] = gxA / weightsAlpha; + } + } + return img2; + }, + toObject: function() { + return { + type: this.type, + scaleX: this.scaleX, + scaley: this.scaleY, + resizeType: this.resizeType, + lanczosLobes: this.lanczosLobes + }; + } + }); + fabric.Image.filters.Resize.fromObject = function() { + return new fabric.Image.filters.Resize(); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, supportsLineDash = fabric.StaticCanvas.supports("setLineDash"); + if (fabric.Text) { + fabric.warn("fabric.Text is already defined"); + return; + } + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push("fontFamily", "fontWeight", "fontSize", "text", "textDecoration", "textAlign", "fontStyle", "lineHeight", "textBackgroundColor"); + fabric.Text = fabric.util.createClass(fabric.Object, { + _dimensionAffectingProps: { + fontSize: true, + fontWeight: true, + fontFamily: true, + fontStyle: true, + lineHeight: true, + stroke: true, + strokeWidth: true, + text: true, + textAlign: true + }, + _reNewline: /\r?\n/, + type: "text", + fontSize: 40, + fontWeight: "normal", + fontFamily: "Times New Roman", + textDecoration: "", + textAlign: "left", + fontStyle: "", + lineHeight: 1.16, + textBackgroundColor: "", + stateProperties: stateProperties, + stroke: null, + shadow: null, + _fontSizeFraction: .25, + _fontSizeMult: 1.13, + initialize: function(text, options) { + options = options || {}; + this.text = text; + this.__skipDimension = true; + this.setOptions(options); + this.__skipDimension = false; + this._initDimensions(); + }, + _initDimensions: function(ctx) { + if (this.__skipDimension) { + return; + } + if (!ctx) { + ctx = fabric.util.createCanvasElement().getContext("2d"); + this._setTextStyles(ctx); + } + this._textLines = this.text.split(this._reNewline); + this._clearCache(); + var currentTextAlign = this.textAlign; + this.textAlign = "left"; + this.width = this._getTextWidth(ctx); + this.textAlign = currentTextAlign; + this.height = this._getTextHeight(ctx); + }, + toString: function() { + return "#'; + }, + _render: function(ctx) { + this.clipTo && fabric.util.clipContext(this, ctx); + this._renderTextBackground(ctx); + this._renderText(ctx); + this._renderTextDecoration(ctx); + this.clipTo && ctx.restore(); + }, + _renderText: function(ctx) { + ctx.save(); + this._translateForTextAlign(ctx); + this._setOpacity(ctx); + this._setShadow(ctx); + this._setupCompositeOperation(ctx); + this._renderTextFill(ctx); + this._renderTextStroke(ctx); + this._restoreCompositeOperation(ctx); + this._removeShadow(ctx); + ctx.restore(); + }, + _translateForTextAlign: function(ctx) { + if (this.textAlign !== "left" && this.textAlign !== "justify") { + ctx.translate(this.textAlign === "center" ? this.width / 2 : this.width, 0); + } + }, + _setTextStyles: function(ctx) { + ctx.textBaseline = "alphabetic"; + if (!this.skipTextAlign) { + ctx.textAlign = this.textAlign; + } + ctx.font = this._getFontDeclaration(); + }, + _getTextHeight: function() { + return this._textLines.length * this._getHeightOfLine(); + }, + _getTextWidth: function(ctx) { + var maxWidth = this._getLineWidth(ctx, 0); + for (var i = 1, len = this._textLines.length; i < len; i++) { + var currentLineWidth = this._getLineWidth(ctx, i); + if (currentLineWidth > maxWidth) { + maxWidth = currentLineWidth; + } + } + return maxWidth; + }, + _renderChars: function(method, ctx, chars, left, top) { + ctx[method](chars, left, top); + }, + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + top -= this.fontSize * this._fontSizeFraction; + if (this.textAlign !== "justify") { + this._renderChars(method, ctx, line, left, top, lineIndex); + return; + } + var lineWidth = this._getLineWidth(ctx, lineIndex), totalWidth = this.width; + if (totalWidth >= lineWidth) { + var words = line.split(/\s+/), wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), widthDiff = totalWidth - wordsWidth, numSpaces = words.length - 1, spaceWidth = widthDiff / numSpaces, leftOffset = 0; + for (var i = 0, len = words.length; i < len; i++) { + this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); + leftOffset += ctx.measureText(words[i]).width + spaceWidth; + } + } else { + this._renderChars(method, ctx, line, left, top, lineIndex); + } + }, + _getWidthOfWords: function(ctx, line) { + return ctx.measureText(line.replace(/\s+/g, "")).width; + }, + _getLeftOffset: function() { + return -this.width / 2; + }, + _getTopOffset: function() { + return -this.height / 2; + }, + _renderTextFill: function(ctx) { + if (!this.fill && !this._skipFillStrokeCheck) { + return; + } + var lineHeights = 0; + for (var i = 0, len = this._textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i), maxHeight = heightOfLine / this.lineHeight; + this._renderTextLine("fillText", ctx, this._textLines[i], this._getLeftOffset(), this._getTopOffset() + lineHeights + maxHeight, i); + lineHeights += heightOfLine; + } + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + }, + _renderTextStroke: function(ctx) { + if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) { + return; + } + var lineHeights = 0; + ctx.save(); + if (this.strokeDashArray) { + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + supportsLineDash && ctx.setLineDash(this.strokeDashArray); + } + ctx.beginPath(); + for (var i = 0, len = this._textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i), maxHeight = heightOfLine / this.lineHeight; + this._renderTextLine("strokeText", ctx, this._textLines[i], this._getLeftOffset(), this._getTopOffset() + lineHeights + maxHeight, i); + lineHeights += heightOfLine; + } + ctx.closePath(); + ctx.restore(); + }, + _getHeightOfLine: function() { + return this.fontSize * this._fontSizeMult * this.lineHeight; + }, + _renderTextBackground: function(ctx) { + this._renderTextBoxBackground(ctx); + this._renderTextLinesBackground(ctx); + }, + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) { + return; + } + ctx.save(); + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(this._getLeftOffset(), this._getTopOffset(), this.width, this.height); + ctx.restore(); + }, + _renderTextLinesBackground: function(ctx) { + var lineTopOffset = 0, heightOfLine = this._getHeightOfLine(); + if (!this.textBackgroundColor) { + return; + } + ctx.save(); + ctx.fillStyle = this.textBackgroundColor; + for (var i = 0, len = this._textLines.length; i < len; i++) { + if (this._textLines[i] !== "") { + var lineWidth = this._getLineWidth(ctx, i), lineLeftOffset = this._getLineLeftOffset(lineWidth); + ctx.fillRect(this._getLeftOffset() + lineLeftOffset, this._getTopOffset() + lineTopOffset, lineWidth, this.fontSize * this._fontSizeMult); + } + lineTopOffset += heightOfLine; + } + ctx.restore(); + }, + _getLineLeftOffset: function(lineWidth) { + if (this.textAlign === "center") { + return (this.width - lineWidth) / 2; + } + if (this.textAlign === "right") { + return this.width - lineWidth; + } + return 0; + }, + _clearCache: function() { + this.__lineWidths = []; + this.__lineHeights = []; + this.__lineOffsets = []; + }, + _shouldClearCache: function() { + var shouldClear = false; + for (var prop in this._dimensionAffectingProps) { + if (this["__" + prop] !== this[prop]) { + this["__" + prop] = this[prop]; + shouldClear = true; + } + } + return shouldClear; + }, + _getLineWidth: function(ctx, lineIndex) { + if (this.__lineWidths[lineIndex]) { + return this.__lineWidths[lineIndex]; + } + this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width; + return this.__lineWidths[lineIndex]; + }, + _renderTextDecoration: function(ctx) { + if (!this.textDecoration) { + return; + } + var halfOfVerticalBox = this.height / 2, _this = this, offsets = []; + function renderLinesAtOffset(offsets) { + var i, lineHeight = 0, len, j, oLen; + for (i = 0, len = _this._textLines.length; i < len; i++) { + var lineWidth = _this._getLineWidth(ctx, i), lineLeftOffset = _this._getLineLeftOffset(lineWidth), heightOfLine = _this._getHeightOfLine(ctx, i); + for (j = 0, oLen = offsets.length; j < oLen; j++) { + ctx.fillRect(_this._getLeftOffset() + lineLeftOffset, lineHeight + (_this._fontSizeMult - 1 + offsets[j]) * _this.fontSize - halfOfVerticalBox, lineWidth, _this.fontSize / 15); + } + lineHeight += heightOfLine; + } + } + if (this.textDecoration.indexOf("underline") > -1) { + offsets.push(.85); + } + if (this.textDecoration.indexOf("line-through") > -1) { + offsets.push(.43); + } + if (this.textDecoration.indexOf("overline") > -1) { + offsets.push(-.12); + } + if (offsets.length > 0) { + renderLinesAtOffset(offsets); + } + }, + _getFontDeclaration: function() { + return [ fabric.isLikelyNode ? this.fontWeight : this.fontStyle, fabric.isLikelyNode ? this.fontStyle : this.fontWeight, this.fontSize + "px", fabric.isLikelyNode ? '"' + this.fontFamily + '"' : this.fontFamily ].join(" "); + }, + render: function(ctx, noTransform) { + if (!this.visible) { + return; + } + ctx.save(); + this._setTextStyles(ctx); + if (this._shouldClearCache()) { + this._initDimensions(ctx); + } + if (!noTransform) { + this.transform(ctx); + } + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + if (this.transformMatrix) { + ctx.transform.apply(ctx, this.transformMatrix); + } + if (this.group && this.group.type === "path-group") { + ctx.translate(this.left, this.top); + } + this._render(ctx); + ctx.restore(); + }, + toObject: function(propertiesToInclude) { + var object = extend(this.callSuper("toObject", propertiesToInclude), { + text: this.text, + fontSize: this.fontSize, + fontWeight: this.fontWeight, + fontFamily: this.fontFamily, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + textDecoration: this.textDecoration, + textAlign: this.textAlign, + textBackgroundColor: this.textBackgroundColor + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), offsets = this._getSVGLeftTopOffsets(this.ctx), textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); + this._wrapSVGTextAndBg(markup, textAndBg); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _getSVGLeftTopOffsets: function(ctx) { + var lineTop = this._getHeightOfLine(ctx, 0), textLeft = -this.width / 2, textTop = 0; + return { + textLeft: textLeft + (this.group && this.group.type === "path-group" ? this.left : 0), + textTop: textTop + (this.group && this.group.type === "path-group" ? -this.top : 0), + lineTop: lineTop + }; + }, + _wrapSVGTextAndBg: function(markup, textAndBg) { + markup.push(' \n', textAndBg.textBgRects.join(""), " ', textAndBg.textSpans.join(""), "\n", " \n"); + }, + _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { + var textSpans = [], textBgRects = [], height = 0; + this._setSVGBg(textBgRects); + for (var i = 0, len = this._textLines.length; i < len; i++) { + if (this.textBackgroundColor) { + this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height); + } + this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects); + height += this._getHeightOfLine(this.ctx, i); + } + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) { + var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction) - textTopOffset + height - this.height / 2; + textSpans.push('", fabric.util.string.escapeXml(this._textLines[i]), ""); + }, + _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) { + textBgRects.push(" \n'); + }, + _setSVGBg: function(textBgRects) { + if (this.backgroundColor) { + textBgRects.push(" \n'); + } + }, + _getFillAttributes: function(value) { + var fillColor = value && typeof value === "string" ? new fabric.Color(value) : ""; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + _set: function(key, value) { + this.callSuper("_set", key, value); + if (key in this._dimensionAffectingProps) { + this._initDimensions(); + this.setCoords(); + } + }, + complexity: function() { + return 1; + } + }); + fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")); + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; + fabric.Text.fromElement = function(element, options) { + if (!element) { + return null; + } + var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); + options = fabric.util.object.extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes); + options.top = options.top || 0; + options.left = options.left || 0; + if ("dx" in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ("dy" in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!("fontSize" in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + if (!options.originX) { + options.originX = "left"; + } + var textContent = element.textContent.replace(/^\s+|\s+$|\n+/g, "").replace(/\s+/g, " "), text = new fabric.Text(textContent, options), offX = 0; + if (text.originX === "left") { + offX = text.getWidth() / 2; + } + if (text.originX === "right") { + offX = -text.getWidth() / 2; + } + text.set({ + left: text.getLeft() + offX, + top: text.getTop() - text.getHeight() / 2 + text.fontSize * (.18 + text._fontSizeFraction) + }); + return text; + }; + fabric.Text.fromObject = function(object) { + return new fabric.Text(object.text, clone(object)); + }; + fabric.util.createAccessors(fabric.Text); +})(typeof exports !== "undefined" ? exports : this); (function() { - - if (typeof document !== 'undefined' && typeof window !== 'undefined') { - return; - } - - var DOMParser = 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.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP, - req = reqHandler.request({ - hostname: oURL.hostname, - port: oURL.port, - path: oURL.path, - method: 'GET' - }, function(response) { - var body = ''; - if (encoding) { - response.setEncoding(encoding); - } - response.on('end', function () { - callback(body); - }); - response.on('data', function (chunk) { - if (response.statusCode === 200) { - body += chunk; + var clone = fabric.util.object.clone; + fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, { + type: "i-text", + selectionStart: 0, + selectionEnd: 0, + selectionColor: "rgba(17,119,255,0.3)", + isEditing: false, + editable: true, + editingBorderColor: "rgba(102,153,255,0.25)", + cursorWidth: 2, + cursorColor: "#333", + cursorDelay: 1e3, + cursorDuration: 600, + styles: null, + caching: true, + _skipFillStrokeCheck: false, + _reSpace: /\s|\n/, + _currentCursorOpacity: 0, + _selectionDirection: null, + _abortCursorAnimation: false, + _charWidthsCache: {}, + initialize: function(text, options) { + this.styles = options ? options.styles || {} : {}; + this.callSuper("initialize", text, options); + this.initBehavior(); + }, + _clearCache: function() { + this.callSuper("_clearCache"); + this.__maxFontHeights = []; + this.__widthOfSpace = []; + }, + isEmptyStyles: function() { + if (!this.styles) { + return true; } - }); - }); - - 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); - } + var obj = this.styles; + for (var p1 in obj) { + for (var p2 in obj[p1]) { + for (var p3 in obj[p1][p2]) { + return false; + } + } + } + return true; + }, + setSelectionStart: function(index) { + index = Math.max(index, 0); + if (this.selectionStart !== index) { + this.fire("selection:changed"); + this.canvas && this.canvas.fire("text:selection:changed", { + target: this + }); + this.selectionStart = index; + } + this._updateTextarea(); + }, + setSelectionEnd: function(index) { + index = Math.min(index, this.text.length); + if (this.selectionEnd !== index) { + this.fire("selection:changed"); + this.canvas && this.canvas.fire("text:selection:changed", { + target: this + }); + this.selectionEnd = index; + } + this._updateTextarea(); + }, + 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 {}; + }, + 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); + } + } + this._clearCache(); + return this; + }, + _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); + }, + _render: function(ctx) { + this.callSuper("_render", ctx); + this.ctx = ctx; + this.isEditing && this.renderCursorOrSelection(); + }, + renderCursorOrSelection: function() { + if (!this.active) { + return; + } + var chars = this.text.split(""), boundaries, ctx; + if (this.canvas.contextTop) { + ctx = this.canvas.contextTop; + ctx.save(); + ctx.transform.apply(ctx, this.canvas.viewportTransform); + this.transform(ctx); + } else { + ctx = this.ctx; + ctx.save(); + } + if (this.selectionStart === this.selectionEnd) { + boundaries = this._getCursorBoundaries(chars, "cursor"); + this.renderCursor(boundaries, ctx); + } else { + boundaries = this._getCursorBoundaries(chars, "selection"); + this.renderSelection(chars, boundaries, ctx); + } + ctx.restore(); + }, + 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 + }; + }, + 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 + }; + }, + 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; + }, + 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; + }, + _getCursorBoundaries: function(chars, typeOfBoundaries) { + var left = Math.round(this._getLeftOffset()), top = this._getTopOffset(), offsets = this._getCursorBoundariesOffsets(chars, typeOfBoundaries); + return { + left: left, + top: top, + leftOffset: offsets.left + offsets.lineLeft, + topOffset: offsets.top + }; + }, + _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) { + var lineLeftOffset = 0, lineIndex = 0, charIndex = 0, topOffset = 0, leftOffset = 0; + for (var i = 0; i < this.selectionStart; i++) { + if (chars[i] === "\n") { + leftOffset = 0; + topOffset += this._getHeightOfLine(this.ctx, lineIndex); + lineIndex++; + charIndex = 0; + } else { + leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); + charIndex++; + } + lineLeftOffset = this._getCachedLineOffset(lineIndex); + } + if (typeOfBoundaries === "cursor") { + topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction); + } + return { + top: topOffset, + left: leftOffset, + lineLeft: lineLeftOffset + }; + }, + _getCachedLineOffset: function(lineIndex) { + var widthOfLine = this._getLineWidth(this.ctx, lineIndex); + return this.__lineOffsets[lineIndex] || (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine)); + }, + renderCursor: function(boundaries, ctx) { + var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex, charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), leftOffset = lineIndex === 0 && charIndex === 0 ? this._getCachedLineOffset(lineIndex) : boundaries.leftOffset; + ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; + ctx.fillRect(boundaries.left + leftOffset, boundaries.top + boundaries.topOffset, this.cursorWidth / this.scaleX, charHeight); + }, + renderSelection: function(chars, boundaries, ctx) { + ctx.fillStyle = this.selectionColor; + var start = this.get2DCursorLocation(this.selectionStart), end = this.get2DCursorLocation(this.selectionEnd), startLine = start.lineIndex, endLine = end.lineIndex; + for (var i = startLine; i <= endLine; i++) { + var lineOffset = this._getCachedLineOffset(i) || 0, lineHeight = this._getHeightOfLine(this.ctx, i), boxWidth = 0, line = this._textLines[i]; + if (i === startLine) { + for (var j = 0, len = line.length; j < len; j++) { + if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { + boxWidth += this._getWidthOfChar(ctx, line[j], i, j); + } + if (j < start.charIndex) { + lineOffset += this._getWidthOfChar(ctx, line[j], i, j); + } + } + } else if (i > startLine && i < endLine) { + boxWidth += this._getLineWidth(ctx, i) || 5; + } else if (i === endLine) { + for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { + boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2); + } + } + ctx.fillRect(boundaries.left + lineOffset, boundaries.top + boundaries.topOffset, boxWidth, lineHeight); + boundaries.topOffset += lineHeight; + } + }, + _renderChars: function(method, ctx, line, left, top, lineIndex) { + if (this.isEmptyStyles()) { + return this._renderCharsFast(method, ctx, line, left, top); + } + this.skipTextAlign = true; + left -= this.textAlign === "center" ? this.width / 2 : this.textAlign === "right" ? this.width : 0; + var lineHeight = this._getHeightOfLine(ctx, lineIndex), lineLeftOffset = this._getCachedLineOffset(lineIndex), chars = line.split(""), prevStyle, charsToRender = ""; + left += lineLeftOffset || 0; + ctx.save(); + top -= lineHeight / this.lineHeight * this._fontSizeFraction; + for (var i = 0, len = chars.length; i <= len; i++) { + prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); + var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); + if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { + this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); + charsToRender = ""; + prevStyle = thisStyle; + } + charsToRender += chars[i]; + } + ctx.restore(); + }, + _renderCharsFast: function(method, ctx, line, left, top) { + this.skipTextAlign = false; + if (method === "fillText" && this.fill) { + this.callSuper("_renderChars", method, ctx, line, left, top); + } + if (method === "strokeText" && (this.stroke && this.strokeWidth > 0 || this.skipFillStrokeCheck)) { + this.callSuper("_renderChars", method, ctx, line, left, top); + } + }, + _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { + var decl, charWidth, charHeight, offset = this._fontSizeFraction * lineHeight / this.lineHeight; + if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) { + var shouldStroke = decl.stroke || this.stroke, shouldFill = decl.fill || this.fill; + ctx.save(); + charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl); + charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); + if (shouldFill) { + ctx.fillText(_char, left, top); + } + if (shouldStroke) { + ctx.strokeText(_char, left, top); + } + this._renderCharDecoration(ctx, decl, left, top, offset, charWidth, charHeight); + ctx.restore(); + ctx.translate(charWidth, 0); + } else { + if (method === "strokeText" && this.stroke) { + ctx[method](_char, left, top); + } + if (method === "fillText" && this.fill) { + ctx[method](_char, left, top); + } + charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i); + this._renderCharDecoration(ctx, null, left, top, offset, charWidth, this.fontSize); + ctx.translate(ctx.measureText(_char).width, 0); + } + }, + _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; + }, + _renderCharDecoration: function(ctx, styleDeclaration, left, top, offset, charWidth, charHeight) { + var textDecoration = styleDeclaration ? styleDeclaration.textDecoration || this.textDecoration : this.textDecoration; + if (!textDecoration) { + return; + } + if (textDecoration.indexOf("underline") > -1) { + ctx.fillRect(left, top + charHeight / 10, charWidth, charHeight / 15); + } + if (textDecoration.indexOf("line-through") > -1) { + ctx.fillRect(left, top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + charHeight / 15, charWidth, charHeight / 15); + } + if (textDecoration.indexOf("overline") > -1) { + ctx.fillRect(left, top - (this._fontSizeMult - this._fontSizeFraction) * charHeight, charWidth, charHeight / 15); + } + }, + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + if (!this.isEmptyStyles()) { + top += this.fontSize * (this._fontSizeFraction + .03); + } + this.callSuper("_renderTextLine", method, ctx, line, left, top, lineIndex); + }, + _renderTextDecoration: function(ctx) { + if (this.isEmptyStyles()) { + return this.callSuper("_renderTextDecoration", ctx); + } + }, + _renderTextLinesBackground: function(ctx) { + if (!this.textBackgroundColor && !this.styles) { + return; + } + ctx.save(); + if (this.textBackgroundColor) { + ctx.fillStyle = this.textBackgroundColor; + } + var lineHeights = 0; + for (var i = 0, len = this._textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i); + if (this._textLines[i] === "") { + lineHeights += heightOfLine; + continue; + } + var lineWidth = this._getLineWidth(ctx, i), lineLeftOffset = this._getCachedLineOffset(i); + if (this.textBackgroundColor) { + ctx.fillStyle = this.textBackgroundColor; + ctx.fillRect(this._getLeftOffset() + lineLeftOffset, this._getTopOffset() + lineHeights, lineWidth, heightOfLine / this.lineHeight); + } + if (this.styles[i]) { + for (var j = 0, jlen = this._textLines[i].length; j < jlen; j++) { + if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) { + var _char = this._textLines[i][j]; + ctx.fillStyle = this.styles[i][j].textBackgroundColor; + ctx.fillRect(this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j), this._getTopOffset() + lineHeights, this._getWidthOfChar(ctx, _char, i, j) + 1, heightOfLine / this.lineHeight); + } + } + } + lineHeights += heightOfLine; + } + ctx.restore(); + }, + _getCacheProp: function(_char, styleDeclaration) { + return _char + styleDeclaration.fontFamily + styleDeclaration.fontSize + styleDeclaration.fontWeight + styleDeclaration.fontStyle + styleDeclaration.shadow; + }, + _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { + var styleDeclaration = decl || this.styles[lineIndex] && this.styles[lineIndex][charIndex]; + if (styleDeclaration) { + styleDeclaration = clone(styleDeclaration); + } else { + styleDeclaration = {}; + } + this._applyFontStyles(styleDeclaration); + var cacheProp = this._getCacheProp(_char, styleDeclaration); + 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, this) : fill; + if (styleDeclaration.stroke) { + ctx.strokeStyle = styleDeclaration.stroke && styleDeclaration.stroke.toLive ? styleDeclaration.stroke.toLive(ctx, this) : styleDeclaration.stroke; + } + ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; + ctx.font = this._getFontDeclaration.call(styleDeclaration); + this._setShadow.call(styleDeclaration, ctx); + if (!this.caching) { + return ctx.measureText(_char).width; + } + if (!this._charWidthsCache[cacheProp]) { + this._charWidthsCache[cacheProp] = ctx.measureText(_char).width; + } + return this._charWidthsCache[cacheProp]; + }, + _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; + } + }, + _getStyleDeclaration: function(lineIndex, charIndex) { + return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? clone(this.styles[lineIndex][charIndex]) : {}; + }, + _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; + } + }, + _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; + }, + _getHeightOfCharAt: function(ctx, lineIndex, charIndex) { + var _char = this._textLines[lineIndex][charIndex]; + return this._getHeightOfChar(ctx, _char, lineIndex, charIndex); + }, + _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) { + var width = 0, i, _char; + for (i = 0; i < charIndex; i++) { + _char = this._textLines[lineIndex][i]; + width += this._getWidthOfChar(ctx, _char, lineIndex, i); + } + return width; + }, + _getLineWidth: function(ctx, lineIndex) { + if (this.__lineWidths[lineIndex]) { + return this.__lineWidths[lineIndex]; + } + this.__lineWidths[lineIndex] = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length); + return this.__lineWidths[lineIndex]; + }, + _getWidthOfSpace: function(ctx, lineIndex) { + if (this.__widthOfSpace[lineIndex]) { + return this.__widthOfSpace[lineIndex]; + } + var line = this._textLines[lineIndex], wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), widthDiff = this.width - wordsWidth, numSpaces = line.length - line.replace(/\s+/g, "").length, width = widthDiff / numSpaces; + this.__widthOfSpace[lineIndex] = width; + return width; + }, + _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; + }, + _getHeightOfLine: function(ctx, lineIndex) { + if (this.__lineHeights[lineIndex]) { + return this.__lineHeights[lineIndex]; + } + var line = this._textLines[lineIndex], maxHeight = this._getHeightOfChar(ctx, line[0], lineIndex, 0); + for (var i = 1, len = line.length; i < len; i++) { + var currentCharHeight = this._getHeightOfChar(ctx, line[i], lineIndex, i); + if (currentCharHeight > maxHeight) { + maxHeight = currentCharHeight; + } + } + this.__maxFontHeights[lineIndex] = maxHeight; + this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; + return this.__lineHeights[lineIndex]; + }, + _getTextHeight: function(ctx) { + var height = 0; + for (var i = 0, len = this._textLines.length; i < len; i++) { + height += this._getHeightOfLine(ctx, i); + } + return height; + }, + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) { + return; + } + ctx.save(); + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(this._getLeftOffset(), this._getTopOffset(), this.width, this.height); + ctx.restore(); + }, + toObject: function(propertiesToInclude) { + return fabric.util.object.extend(this.callSuper("toObject", propertiesToInclude), { + styles: clone(this.styles) + }); + } }); - - 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; - } - + fabric.IText.fromObject = function(object) { + return new fabric.IText(object.text, clone(object)); + }; })(); +(function() { + var clone = fabric.util.object.clone; + fabric.util.object.extend(fabric.IText.prototype, { + initBehavior: function() { + this.initAddedHandler(); + this.initRemovedHandler(); + this.initCursorSelectionHandlers(); + this.initDoubleClickSimulation(); + }, + initSelectedHandler: function() { + this.on("selected", function() { + var _this = this; + setTimeout(function() { + _this.selected = true; + }, 100); + }); + }, + initAddedHandler: function() { + var _this = this; + this.on("added", function() { + if (this.canvas && !this.canvas._hasITextHandlers) { + this.canvas._hasITextHandlers = true; + this._initCanvasHandlers(); + } + if (_this.canvas) { + _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; + _this.canvas._iTextInstances.push(_this); + } + }); + }, + initRemovedHandler: function() { + var _this = this; + this.on("removed", function() { + if (_this.canvas) { + _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; + fabric.util.removeFromArray(_this.canvas._iTextInstances, _this); + } + }); + }, + _initCanvasHandlers: function() { + var _this = this; + this.canvas.on("selection:cleared", function() { + fabric.IText.prototype.exitEditingOnOthers(_this.canvas); + }); + this.canvas.on("mouse:up", function() { + if (_this.canvas._iTextInstances) { + _this.canvas._iTextInstances.forEach(function(obj) { + obj.__isMousedown = false; + }); + } + }); + this.canvas.on("object:selected", function() { + fabric.IText.prototype.exitEditingOnOthers(_this.canvas); + }); + }, + _tick: function() { + this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, "_onTickComplete"); + }, + _animateCursor: function(obj, targetOpacity, duration, completeMethod) { + var tickState; + tickState = { + isAborted: false, + abort: function() { + this.isAborted = true; + } + }; + obj.animate("_currentCursorOpacity", targetOpacity, { + duration: duration, + onComplete: function() { + if (!tickState.isAborted) { + obj[completeMethod](); + } + }, + onChange: function() { + if (obj.canvas) { + obj.canvas.clearContext(obj.canvas.contextTop || obj.ctx); + obj.renderCursorOrSelection(); + } + }, + abort: function() { + return tickState.isAborted; + } + }); + return tickState; + }, + _onTickComplete: function() { + var _this = this; + if (this._cursorTimeout1) { + clearTimeout(this._cursorTimeout1); + } + this._cursorTimeout1 = setTimeout(function() { + _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, "_tick"); + }, 100); + }, + initDelayedCursor: function(restart) { + var _this = this, delay = restart ? 0 : this.cursorDelay; + this._currentTickState && this._currentTickState.abort(); + this._currentTickCompleteState && this._currentTickCompleteState.abort(); + clearTimeout(this._cursorTimeout1); + this._currentCursorOpacity = 1; + if (this.canvas) { + this.canvas.clearContext(this.canvas.contextTop || this.ctx); + this.renderCursorOrSelection(); + } + if (this._cursorTimeout2) { + clearTimeout(this._cursorTimeout2); + } + this._cursorTimeout2 = setTimeout(function() { + _this._tick(); + }, delay); + }, + abortCursorAnimation: function() { + this._currentTickState && this._currentTickState.abort(); + this._currentTickCompleteState && this._currentTickCompleteState.abort(); + clearTimeout(this._cursorTimeout1); + clearTimeout(this._cursorTimeout2); + this._currentCursorOpacity = 0; + this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx); + }, + selectAll: function() { + this.setSelectionStart(0); + this.setSelectionEnd(this.text.length); + }, + getSelectedText: function() { + return this.text.slice(this.selectionStart, this.selectionEnd); + }, + findWordBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + 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; + }, + findWordBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + 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; + }, + findLineBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + while (!/\n/.test(this.text.charAt(index)) && index > -1) { + offset++; + index--; + } + return startFrom - offset; + }, + findLineBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) { + offset++; + index++; + } + return startFrom + offset; + }, + 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; + }, + 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; + }, + selectWord: function(selectionStart) { + var newSelectionStart = this.searchWordBoundary(selectionStart, -1), newSelectionEnd = this.searchWordBoundary(selectionStart, 1); + this.setSelectionStart(newSelectionStart); + this.setSelectionEnd(newSelectionEnd); + }, + selectLine: function(selectionStart) { + var newSelectionStart = this.findLineBoundaryLeft(selectionStart), newSelectionEnd = this.findLineBoundaryRight(selectionStart); + this.setSelectionStart(newSelectionStart); + this.setSelectionEnd(newSelectionEnd); + }, + enterEditing: function() { + if (this.isEditing || !this.editable) { + return; + } + if (this.canvas) { + this.exitEditingOnOthers(this.canvas); + } + this.isEditing = true; + this.initHiddenTextarea(); + this.hiddenTextarea.focus(); + this._updateTextarea(); + this._saveEditingProps(); + this._setEditingProps(); + this._tick(); + this.fire("editing:entered"); + if (!this.canvas) { + return this; + } + this.canvas.renderAll(); + this.canvas.fire("text:editing:entered", { + target: this + }); + this.initMouseMoveHandler(); + return this; + }, + exitEditingOnOthers: function(canvas) { + if (canvas._iTextInstances) { + canvas._iTextInstances.forEach(function(obj) { + obj.selected = false; + if (obj.isEditing) { + obj.exitEditing(); + } + }); + } + }, + initMouseMoveHandler: function() { + var _this = this; + this.canvas.on("mouse:move", function(options) { + if (!_this.__isMousedown || !_this.isEditing) { + return; + } + var newSelectionStart = _this.getSelectionStartFromPointer(options.e); + if (newSelectionStart >= _this.__selectionStartOnMouseDown) { + _this.setSelectionStart(_this.__selectionStartOnMouseDown); + _this.setSelectionEnd(newSelectionStart); + } else { + _this.setSelectionStart(newSelectionStart); + _this.setSelectionEnd(_this.__selectionStartOnMouseDown); + } + }); + }, + _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; + }, + _updateTextarea: function() { + if (!this.hiddenTextarea) { + return; + } + this.hiddenTextarea.value = this.text; + this.hiddenTextarea.selectionStart = this.selectionStart; + this.hiddenTextarea.selectionEnd = this.selectionEnd; + }, + _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 + }; + }, + _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; + } + }, + 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; + }, + _removeExtraneousStyles: function() { + for (var prop in this.styles) { + if (!this._textLines[prop]) { + delete this.styles[prop]; + } + } + }, + _removeCharsFromTo: function(start, end) { + var i = end; + while (i !== start) { + var prevIndex = this.get2DCursorLocation(i).charIndex; + i--; + var index = this.get2DCursorLocation(i).charIndex, isNewline = index > prevIndex; + if (isNewline) { + this.removeStyleObject(isNewline, i + 1); + } else { + this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i); + } + } + this.text = this.text.slice(0, start) + this.text.slice(end); + this._clearCache(); + }, + insertChars: function(_chars, useCopiedStyle) { + var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === "\n"; + this.text = this.text.slice(0, this.selectionStart) + _chars + this.text.slice(this.selectionEnd); + if (this.selectionStart === this.selectionEnd) { + this.insertStyleObjects(_chars, isEndOfLine, useCopiedStyle); + } + this.setSelectionStart(this.selectionStart + _chars.length); + this.setSelectionEnd(this.selectionStart); + this._clearCache(); + this.canvas && this.canvas.renderAll(); + this.setCoords(); + this.fire("changed"); + this.canvas && this.canvas.fire("text:changed", { + target: this + }); + }, + 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 (isEndOfLine) { + newLineStyles[0] = clone(currentCharStyle); + this.styles[lineIndex + 1] = newLineStyles; + } else { + for (var index in this.styles[lineIndex]) { + if (parseInt(index, 10) >= charIndex) { + newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index]; + delete this.styles[lineIndex][index]; + } + } + this.styles[lineIndex + 1] = newLineStyles; + } + this._clearCache(); + }, + insertCharStyleObject: function(lineIndex, charIndex, style) { + var currentLineStyles = this.styles[lineIndex], currentLineStylesCloned = clone(currentLineStyles); + if (charIndex === 0 && !style) { + charIndex = 1; + } + for (var index in currentLineStylesCloned) { + var numericIndex = parseInt(index, 10); + if (numericIndex >= charIndex) { + currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex]; + } + } + this.styles[lineIndex][charIndex] = style || clone(currentLineStyles[charIndex - 1]); + this._clearCache(); + }, + insertStyleObjects: function(_chars, isEndOfLine, useCopiedStyle) { + 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 (useCopiedStyle) { + this._insertStyles(this.copiedStyles); + } else { + this.insertCharStyleObject(lineIndex, charIndex); + } + } + }, + _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]); + } + }, + shiftLineStyles: function(lineIndex, offset) { + 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]; + } + } + }, + removeStyleObject: function(isBeginningOfLine, index) { + var cursorLocation = this.get2DCursorLocation(index), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex; + if (isBeginningOfLine) { + var textOnPreviousLine = this._textLines[lineIndex - 1], newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0; + if (!this.styles[lineIndex - 1]) { + this.styles[lineIndex - 1] = {}; + } + for (charIndex in this.styles[lineIndex]) { + this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] = this.styles[lineIndex][charIndex]; + } + this.shiftLineStyles(lineIndex, -1); + } else { + var currentLineStyles = this.styles[lineIndex]; + if (currentLineStyles) { + var offset = this.selectionStart === this.selectionEnd ? -1 : 0; + delete currentLineStyles[charIndex + offset]; + } + var currentLineStylesCloned = clone(currentLineStyles); + for (var i in currentLineStylesCloned) { + var numericIndex = parseInt(i, 10); + if (numericIndex >= charIndex && numericIndex !== 0) { + currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex]; + delete currentLineStyles[numericIndex]; + } + } + } + }, + insertNewline: function() { + this.insertChars("\n"); + } + }); +})(); -/* Footer for requirejs AMD support */ +fabric.util.object.extend(fabric.IText.prototype, { + initDoubleClickSimulation: function() { + this.__lastClickTime = +new Date(); + 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; + }, + _stopEvent: function(e) { + e.preventDefault && e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + }, + initCursorSelectionHandlers: function() { + this.initSelectedHandler(); + this.initMousedownHandler(); + this.initMouseupHandler(); + this.initClicks(); + }, + initClicks: function() { + this.on("dblclick", function(options) { + this.selectWord(this.getSelectionStartFromPointer(options.e)); + }); + this.on("tripleclick", function(options) { + this.selectLine(this.getSelectionStartFromPointer(options.e)); + }); + }, + 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); + } + }); + }, + _isObjectMoved: function(e) { + var pointer = this.canvas.getPointer(e); + return this.__mousedownX !== pointer.x || this.__mousedownY !== pointer.y; + }, + 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; + }); + }, + 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); + } + }, + _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); + }, + getSelectionStartFromPointer: function(e) { + var mouseOffset = this._getLocalRotatedPointer(e), prevWidth = 0, width = 0, height = 0, charIndex = 0, newSelectionStart, line; + for (var i = 0, len = this._textLines.length; i < len; i++) { + line = this._textLines[i].split(""); + height += this._getHeightOfLine(this.ctx, i) * this.scaleY; + var widthOfLine = this._getLineWidth(this.ctx, i), lineLeftOffset = this._getLineLeftOffset(widthOfLine); + width = lineLeftOffset * this.scaleX; + if (this.flipX) { + this._textLines[i] = line.reverse().join(""); + } + for (var j = 0, jlen = line.length; j < jlen; j++) { + var _char = line[j]; + prevWidth = width; + width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) * this.scaleX; + if (height <= mouseOffset.y || width <= mouseOffset.x) { + charIndex++; + continue; + } + return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex + i, jlen); + } + if (mouseOffset.y < height) { + return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex + i, jlen); + } + } + if (typeof newSelectionStart === "undefined") { + return this.text.length; + } + }, + _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 (this.flipX) { + newSelectionStart = jlen - newSelectionStart; + } + if (newSelectionStart > this.text.length) { + newSelectionStart = this.text.length; + } + return newSelectionStart; + } +}); + +fabric.util.object.extend(fabric.IText.prototype, { + initHiddenTextarea: function() { + this.hiddenTextarea = fabric.document.createElement("textarea"); + this.hiddenTextarea.setAttribute("autocapitalize", "off"); + this.hiddenTextarea.style.cssText = "position: fixed; bottom: 20px; left: 0px; opacity: 0;" + " width: 0px; height: 0px; z-index: -999;"; + fabric.document.body.appendChild(this.hiddenTextarea); + fabric.util.addListener(this.hiddenTextarea, "keydown", this.onKeyDown.bind(this)); + fabric.util.addListener(this.hiddenTextarea, "keypress", this.onKeyPress.bind(this)); + fabric.util.addListener(this.hiddenTextarea, "copy", this.copy.bind(this)); + fabric.util.addListener(this.hiddenTextarea, "paste", this.paste.bind(this)); + if (!this._clickHandlerInitialized && this.canvas) { + fabric.util.addListener(this.canvas.upperCanvasEl, "click", this.onClick.bind(this)); + this._clickHandlerInitialized = true; + } + }, + _keysMap: { + 8: "removeChars", + 9: "exitEditing", + 27: "exitEditing", + 13: "insertNewline", + 33: "moveCursorUp", + 34: "moveCursorDown", + 35: "moveCursorRight", + 36: "moveCursorLeft", + 37: "moveCursorLeft", + 38: "moveCursorUp", + 39: "moveCursorRight", + 40: "moveCursorDown", + 46: "forwardDelete" + }, + _ctrlKeysMap: { + 65: "selectAll", + 88: "cut" + }, + onClick: function() { + this.hiddenTextarea && this.hiddenTextarea.focus(); + }, + 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(); + }, + forwardDelete: function(e) { + if (this.selectionStart === this.selectionEnd) { + this.moveCursorRight(e); + } + this.removeChars(e); + }, + copy: function(e) { + var selectedText = this.getSelectedText(), clipboardData = this._getClipboardData(e); + if (clipboardData) { + clipboardData.setData("text", selectedText); + } + this.copiedText = selectedText; + this.copiedStyles = this.getSelectionStyles(this.selectionStart, this.selectionEnd); + }, + paste: function(e) { + var copiedText = null, clipboardData = this._getClipboardData(e); + if (clipboardData) { + copiedText = clipboardData.getData("text"); + } else { + copiedText = this.copiedText; + } + if (copiedText) { + this.insertChars(copiedText, true); + } + }, + cut: function(e) { + if (this.selectionStart === this.selectionEnd) { + return; + } + this.copy(); + this.removeChars(e); + }, + _getClipboardData: function(e) { + return e && (e.clipboardData || fabric.window.clipboardData); + }, + onKeyPress: function(e) { + if (!this.isEditing || e.metaKey || e.ctrlKey) { + return; + } + if (e.which !== 0) { + this.insertChars(String.fromCharCode(e.which)); + } + e.stopPropagation(); + }, + getDownCursorOffset: function(e, isRight) { + var selectionProp = isRight ? this.selectionEnd : this.selectionStart, _char, lineLeftOffset, textBeforeCursor = this.text.slice(0, selectionProp), textAfterCursor = this.text.slice(selectionProp), textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n") + 1), textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1], textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || {})[1] || "", cursorLocation = this.get2DCursorLocation(selectionProp); + if (cursorLocation.lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { + return this.text.length - selectionProp; + } + var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex); + lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); + var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, lineIndex = cursorLocation.lineIndex; + for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { + _char = textOnSameLineBeforeCursor[i]; + widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); + } + var indexOnNextLine = this._getIndexOnNextLine(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor); + return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; + }, + _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) { + var lineIndex = cursorLocation.lineIndex + 1, widthOfNextLine = this._getLineWidth(this.ctx, lineIndex), lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), widthOfCharsOnNextLine = lineLeftOffset, indexOnNextLine = 0, foundMatch; + for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { + var _char = textOnNextLine[j], widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + widthOfCharsOnNextLine += widthOfChar; + if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { + foundMatch = true; + var leftEdge = widthOfCharsOnNextLine - widthOfChar, rightEdge = widthOfCharsOnNextLine, offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; + break; + } + } + if (!foundMatch) { + indexOnNextLine = textOnNextLine.length; + } + return indexOnNextLine; + }, + 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(); + }, + moveCursorDownWithoutShift: function(offset) { + this._selectionDirection = "right"; + this.setSelectionStart(this.selectionStart + offset); + this.setSelectionEnd(this.selectionStart); + }, + swapSelectionPoints: function() { + var swapSel = this.selectionEnd; + this.setSelectionEnd(this.selectionStart); + this.setSelectionStart(swapSel); + }, + moveCursorDownWithShift: function(offset) { + if (this.selectionEnd === this.selectionStart) { + this._selectionDirection = "right"; + } + if (this._selectionDirection === "right") { + this.setSelectionEnd(this.selectionEnd + offset); + } else { + this.setSelectionStart(this.selectionStart + offset); + } + if (this.selectionEnd < this.selectionStart && this._selectionDirection === "left") { + this.swapSelectionPoints(); + this._selectionDirection = "right"; + } + if (this.selectionEnd > this.text.length) { + this.setSelectionEnd(this.text.length); + } + }, + getUpCursorOffset: function(e, isRight) { + var selectionProp = isRight ? this.selectionEnd : this.selectionStart, cursorLocation = this.get2DCursorLocation(selectionProp); + if (cursorLocation.lineIndex === 0 || e.metaKey || e.keyCode === 33) { + return selectionProp; + } + var textBeforeCursor = this.text.slice(0, selectionProp), textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n") + 1), textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || "", _char, widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex), lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, lineIndex = cursorLocation.lineIndex; + for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { + _char = textOnSameLineBeforeCursor[i]; + widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); + } + var indexOnPrevLine = this._getIndexOnPrevLine(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor); + return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; + }, + _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) { + var lineIndex = cursorLocation.lineIndex - 1, widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex), lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), widthOfCharsOnPreviousLine = lineLeftOffset, indexOnPrevLine = 0, foundMatch; + for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { + var _char = textOnPreviousLine[j], widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + widthOfCharsOnPreviousLine += widthOfChar; + if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { + foundMatch = true; + var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, rightEdge = widthOfCharsOnPreviousLine, offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : j - 1; + break; + } + } + if (!foundMatch) { + indexOnPrevLine = textOnPreviousLine.length - 1; + } + return indexOnPrevLine; + }, + 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(); + }, + moveCursorUpWithShift: function(offset) { + if (this.selectionEnd === this.selectionStart) { + this._selectionDirection = "left"; + } + if (this._selectionDirection === "right") { + this.setSelectionEnd(this.selectionEnd - offset); + } else { + this.setSelectionStart(this.selectionStart - offset); + } + if (this.selectionEnd < this.selectionStart && this._selectionDirection === "right") { + this.swapSelectionPoints(); + this._selectionDirection = "left"; + } + }, + moveCursorUpWithoutShift: function(offset) { + if (this.selectionStart === this.selectionEnd) { + this.setSelectionStart(this.selectionStart - offset); + } + this.setSelectionEnd(this.selectionStart); + this._selectionDirection = "left"; + }, + 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(); + }, + _move: function(e, prop, direction) { + var propMethod = prop === "selectionStart" ? "setSelectionStart" : "setSelectionEnd"; + if (e.altKey) { + this[propMethod](this["findWordBoundary" + direction](this[prop])); + } else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36) { + this[propMethod](this["findLineBoundary" + direction](this[prop])); + } else { + this[propMethod](this[prop] + (direction === "Left" ? -1 : 1)); + } + }, + _moveLeft: function(e, prop) { + this._move(e, prop, "Left"); + }, + _moveRight: function(e, prop) { + this._move(e, prop, "Right"); + }, + moveCursorLeftWithoutShift: function(e) { + this._selectionDirection = "left"; + if (this.selectionEnd === this.selectionStart) { + this._moveLeft(e, "selectionStart"); + } + this.setSelectionEnd(this.selectionStart); + }, + moveCursorLeftWithShift: function(e) { + if (this._selectionDirection === "right" && this.selectionStart !== this.selectionEnd) { + this._moveLeft(e, "selectionEnd"); + } else { + this._selectionDirection = "left"; + this._moveLeft(e, "selectionStart"); + if (this.text.charAt(this.selectionStart) === "\n") { + this.setSelectionStart(this.selectionStart - 1); + } + } + }, + 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(); + }, + moveCursorRightWithShift: function(e) { + if (this._selectionDirection === "left" && this.selectionStart !== this.selectionEnd) { + this._moveRight(e, "selectionStart"); + } else { + this._selectionDirection = "right"; + this._moveRight(e, "selectionEnd"); + if (this.text.charAt(this.selectionEnd - 1) === "\n") { + this.setSelectionEnd(this.selectionEnd + 1); + } + } + }, + moveCursorRightWithoutShift: function(e) { + this._selectionDirection = "right"; + if (this.selectionStart === this.selectionEnd) { + this._moveRight(e, "selectionStart"); + this.setSelectionEnd(this.selectionStart); + } else { + this.setSelectionEnd(this.selectionEnd + this.getNumNewLinesInSelectedText()); + this.setSelectionStart(this.selectionEnd); + } + }, + removeChars: function(e) { + if (this.selectionStart === this.selectionEnd) { + this._removeCharsNearCursor(e); + } else { + this._removeCharsFromTo(this.selectionStart, this.selectionEnd); + } + this.setSelectionEnd(this.selectionStart); + this._removeExtraneousStyles(); + this._clearCache(); + this.canvas && this.canvas.renderAll(); + this.setCoords(); + this.fire("changed"); + this.canvas && this.canvas.fire("text:changed", { + target: this + }); + }, + _removeCharsNearCursor: function(e) { + if (this.selectionStart !== 0) { + if (e.metaKey) { + var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); + this._removeCharsFromTo(leftLineBoundary, this.selectionStart); + this.setSelectionStart(leftLineBoundary); + } else if (e.altKey) { + var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart); + this._removeCharsFromTo(leftWordBoundary, this.selectionStart); + this.setSelectionStart(leftWordBoundary); + } else { + var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === "\n"; + this.removeStyleObject(isBeginningOfLine); + this.setSelectionStart(this.selectionStart - 1); + this.text = this.text.slice(0, this.selectionStart) + this.text.slice(this.selectionStart + 1); + } + } + } +}); + +fabric.util.object.extend(fabric.IText.prototype, { + _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) { + if (!this.styles[lineIndex]) { + this.callSuper("_setSVGTextLineText", lineIndex, textSpans, height, textLeftOffset, textTopOffset); + } else { + this._setSVGTextLineChars(lineIndex, textSpans, height, textLeftOffset, textBgRects); + } + }, + _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) { + var chars = this._textLines[lineIndex].split(""), charOffset = 0, lineLeftOffset = this._getSVGLineLeftOffset(lineIndex) - this.width / 2, lineOffset = this._getSVGLineTopOffset(lineIndex), heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); + for (var i = 0, len = chars.length; i < len; i++) { + var styleDecl = this.styles[lineIndex][i] || {}; + textSpans.push(this._createTextCharSpan(chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset)); + var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); + if (styleDecl.textBackgroundColor) { + textBgRects.push(this._createTextCharBg(styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset)); + } + charOffset += charWidth; + } + }, + _getSVGLineLeftOffset: function(lineIndex) { + return fabric.util.toFixed(this._getLineLeftOffset(this.__lineWidths[lineIndex]), 2); + }, + _getSVGLineTopOffset: function(lineIndex) { + var lineTopOffset = 0, lastHeight = 0; + for (var j = 0; j < lineIndex; j++) { + lineTopOffset += this._getHeightOfLine(this.ctx, j); + } + lastHeight = this._getHeightOfLine(this.ctx, j); + return { + lineTop: lineTopOffset, + offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) + }; + }, + _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { + return [ '' ].join(""); + }, + _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) { + var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ + visible: true, + fill: this.fill, + stroke: this.stroke, + type: "text" + }, styleDecl)); + return [ '', fabric.util.string.escapeXml(_char), "" ].join(""); + } +}); + +(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; + function request(url, encoding, callback) { + var oURL = URL.parse(url); + if (!oURL.port) { + oURL.port = oURL.protocol.indexOf("https:") === 0 ? 443 : 80; + } + var reqHandler = oURL.protocol.indexOf("https:") === 0 ? HTTPS : HTTP, req = reqHandler.request({ + hostname: oURL.hostname, + port: oURL.port, + path: oURL.path, + method: "GET" + }, function(response) { + var body = ""; + if (encoding) { + response.setEncoding(encoding); + } + response.on("end", function() { + callback(body); + }); + response.on("data", function(chunk) { + if (response.statusCode === 200) { + body += chunk; + } + }); + }); + req.on("error", function(err) { + if (err.errno === process.ECONNREFUSED) { + fabric.log("ECONNREFUSED: connection refused to " + oURL.hostname + ":" + oURL.port); + } else { + fabric.log(err.message); + } + }); + req.end(); + } + 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"); + 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); + }); + }); + }; + fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { + nodeCanvasOptions = nodeCanvasOptions || options; + var canvasEl = fabric.document.createElement("canvas"), nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); + 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; + }; + 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.fabric = fabric; -if (typeof define === 'function' && define.amd) { - define([], function() { return fabric }); -} - +if (typeof define === "function" && define.amd) { + define([], function() { + return fabric; + }); +} \ No newline at end of file diff --git a/package.json b/package.json index 82f76832..507334b2 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,12 @@ "license": "MIT", "scripts": { "build": "node build.js modules=ALL exclude=json,gestures", - "test": "node test.js && jshint src" + "test": "node test.js", + "lint": "jshint src && jscs src", + "lint_tests": "jshint test/unit --config .jshintrc_tests", + "export_dist_to_site": "cp dist/fabric.js ../fabricjs.com/lib/fabric.js", + "export_tests_to_site": "cp test/unit/*.js ../fabricjs.com/test/unit", + "all": "npm run build && npm run test && npm run lint && npm run lint_tests && npm run export_dist_to_site && npm run export_tests_to_site" }, "dependencies": { "canvas": "1.1.x", diff --git a/test/unit/canvas.js b/test/unit/canvas.js index 243cfa7a..367c6604 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -2,7 +2,7 @@ var EMPTY_JSON = '{"objects":[],"background":""}'; - var emptyImageCanvasData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAAH7ElEQVR4nO3VMQ0AMAzAsPInvYHoMS2yEeTLHADge/M6AADYM3QACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIuMjH4b7osLFBAAAAAElFTkSuQmCC"; + // var emptyImageCanvasData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAAH7ElEQVR4nO3VMQ0AMAzAsPInvYHoMS2yEeTLHADge/M6AADYM3QACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIuMjH4b7osLFBAAAAAElFTkSuQmCC"; var PATH_JSON = '{"objects": [{"type": "path", "originX": "left", "originY": "top", "left": 268, "top": 266, "width": 51, "height": 49,'+ ' "fill": "rgb(0,0,0)", "stroke": null, "strokeWidth": 1, "scaleX": 1, "scaleY": 1, '+ @@ -221,7 +221,7 @@ test('toDataURL', function() { ok(typeof canvas.toDataURL == 'function'); if (!fabric.Canvas.supports('toDataURL')) { - alert("toDataURL is not supported by this environment. Some of the tests can not be run."); + window.alert("toDataURL is not supported by this environment. Some of the tests can not be run."); } else { var dataURL = canvas.toDataURL(); @@ -288,7 +288,7 @@ test('straightenObject', function() { ok(typeof canvas.straightenObject == 'function'); - var rect = makeRect({ angle: 10 }) + var rect = makeRect({ angle: 10 }); canvas.add(rect); equal(canvas.straightenObject(rect), canvas, 'should be chainable'); equal(rect.getAngle(), 0, 'angle should be coerced to 0 (from 10)'); @@ -805,7 +805,6 @@ var eventsFired = { selectionCleared: false }; - var target; canvas.on('selection:cleared', function(){ eventsFired.selectionCleared = true; diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index 1aa01284..9030656e 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -1,6 +1,6 @@ (function() { - var emptyImageCanvasData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAAH7ElEQVR4nO3VMQ0AMAzAsPInvYHoMS2yEeTLHADge/M6AADYM3QACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIuMjH4b7osLFBAAAAAElFTkSuQmCC"; + // var emptyImageCanvasData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAAH7ElEQVR4nO3VMQ0AMAzAsPInvYHoMS2yEeTLHADge/M6AADYM3QACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIMHQACDB0AAgwdAAIuMjH4b7osLFBAAAAAElFTkSuQmCC"; var CANVAS_SVG = '\n'+ 'Created with Fabric.js ' + fabric.version + ''; @@ -90,7 +90,7 @@ }; function _createImageElement() { - return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'); + return fabric.isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'); } function _createImageObject(width, height, callback) { @@ -439,7 +439,7 @@ test('toDataURL', function() { ok(typeof canvas.toDataURL == 'function'); if (!fabric.Canvas.supports('toDataURL')) { - alert("toDataURL is not supported by this environment. Some of the tests can not be run."); + window.alert("toDataURL is not supported by this environment. Some of the tests can not be run."); } else { var dataURL = canvas.toDataURL(); @@ -452,7 +452,7 @@ test('toDataURL jpg', function() { if (!fabric.Canvas.supports('toDataURL')) { - alert("toDataURL is not supported by this environment. Some of the tests can not be run."); + window.alert("toDataURL is not supported by this environment. Some of the tests can not be run."); } else { try { @@ -572,8 +572,9 @@ return svg; } - var svg = canvas.toSVG(null, reviver); + canvas.toSVG(null, reviver); equal(reviverCount, len); + canvas.renderOnAddRemove = true; }); @@ -1102,6 +1103,8 @@ canvas.setBackgroundImage(IMG_SRC, function() { equal(canvas.backgroundImage.left, 50); equal(canvas.backgroundImage.originX, 'right'); + + start(); }, { left: 50, originX: 'right' @@ -1113,6 +1116,8 @@ canvas.setBackgroundImage(imageInstance, function() { equal(canvas.backgroundImage.left, 100); equal(canvas.backgroundImage.originX, 'center'); + + start(); }, { left: 100, originX: 'center' diff --git a/test/unit/circle.js b/test/unit/circle.js index e79863e5..61ab4397 100644 --- a/test/unit/circle.js +++ b/test/unit/circle.js @@ -166,7 +166,7 @@ equal(oCircle.get('strokeLineJoin'), strokeLineJoin); equal(oCircle.get('strokeMiterLimit'), strokeMiterLimit); - elFaultyCircle = fabric.document.createElement('circle'); + var elFaultyCircle = fabric.document.createElement('circle'); elFaultyCircle.setAttribute('r', '-10'); var error; diff --git a/test/unit/color.js b/test/unit/color.js index e669a94f..a5e617a2 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -8,17 +8,17 @@ ok(oColor instanceof fabric.Color); equal(oColor.toHex(), 'FF5555'); - var oColor = new fabric.Color('rgb(100,100,100)'); + oColor = new fabric.Color('rgb(100,100,100)'); ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toRgb(), 'rgb(100,100,100)'); - var oColor = new fabric.Color('rgba(100,100,100, 0.5)'); + oColor = new fabric.Color('rgba(100,100,100, 0.5)'); ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toRgba(), 'rgba(100,100,100,0.5)'); - var oColor = new fabric.Color('hsl(262,80%,12%)'); + oColor = new fabric.Color('hsl(262,80%,12%)'); ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toHsl(), 'hsl(262,80%,12%)'); @@ -176,7 +176,7 @@ test('fromRgba (with whitespaces)', function() { var originalRgba = 'rgba( 255 , 255 , 255 , 0.5 )'; - oColor = fabric.Color.fromRgba(originalRgba); + var oColor = fabric.Color.fromRgba(originalRgba); ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); @@ -186,7 +186,7 @@ test('fromRgba (percentage values)', function() { var originalRgba = 'rgba(100%,100%,100%,0.5)'; - oColor = fabric.Color.fromRgba(originalRgba); + var oColor = fabric.Color.fromRgba(originalRgba); ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); @@ -196,7 +196,7 @@ test('fromRgba (percentage values with whitespaces)', function() { var originalRgba = 'rgba( 100% , 100% , 100% , 0.5 )'; - oColor = fabric.Color.fromRgba(originalRgba); + var oColor = fabric.Color.fromRgba(originalRgba); ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); @@ -206,7 +206,7 @@ test('fromRgba (percentage values with decimals)', function() { var originalRgba = 'rgba( 100.00%, 100.00%, 100.00% , 0.5 )'; - oColor = fabric.Color.fromRgba(originalRgba); + var oColor = fabric.Color.fromRgba(originalRgba); ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); diff --git a/test/unit/ellipse.js b/test/unit/ellipse.js index 3caea920..89dc01ae 100644 --- a/test/unit/ellipse.js +++ b/test/unit/ellipse.js @@ -82,7 +82,7 @@ ellipse._render = function(){ wasRenderCalled = true; - } + }; ellipse.render({}); equal(wasRenderCalled, false, 'should not render when rx/ry are 0'); diff --git a/test/unit/gradient.js b/test/unit/gradient.js index 21407df1..bbf525d7 100644 --- a/test/unit/gradient.js +++ b/test/unit/gradient.js @@ -248,7 +248,7 @@ equal(gradient.colorStops[0].color, 'rgb(0,0,0)'); equal(gradient.colorStops[1].color, 'rgb(255,255,255)'); deepEqual(gradient.gradientTransform, [ 3.321, -0.6998, 0.4077, 1.9347, -440.9168, -408.0598 ]); - }) + }); test('fromElement linearGradient colorStop attributes/styles', function() { ok(typeof fabric.Gradient.fromElement == 'function'); diff --git a/test/unit/group.js b/test/unit/group.js index 528d3276..e5a0541a 100644 --- a/test/unit/group.js +++ b/test/unit/group.js @@ -2,9 +2,9 @@ var canvas = this.canvas = new fabric.Canvas(); - function _createImageElement() { - return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'); - } + // function _createImageElement() { + // return fabric.isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'); + // } function makeGroupWith2Objects() { var rect1 = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10, strokeWidth: 0 }), diff --git a/test/unit/image.js b/test/unit/image.js index 63a9349b..751a1c30 100644 --- a/test/unit/image.js +++ b/test/unit/image.js @@ -50,7 +50,7 @@ }; function _createImageElement() { - return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'); + return fabric.isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'); } function _createImageObject(width, height, callback) { @@ -63,12 +63,12 @@ } function createImageObject(callback) { - return _createImageObject(IMG_WIDTH, IMG_HEIGHT, callback) + return _createImageObject(IMG_WIDTH, IMG_HEIGHT, callback); } - function createSmallImageObject(callback) { - return _createImageObject(IMG_WIDTH / 2, IMG_HEIGHT / 2, callback); - } + // function createSmallImageObject(callback) { + // return _createImageObject(IMG_WIDTH / 2, IMG_HEIGHT / 2, callback); + // } function setSrc(img, src, callback) { if (fabric.isLikelyNode) { @@ -159,14 +159,14 @@ var elImage = _createImageElement(); elImage.crossOrigin = 'anonymous'; - var image = new fabric.Image(elImage); + image = new fabric.Image(elImage); equal(image.crossOrigin, '', 'crossOrigin value on an instance takes precedence'); var objRepr = image.toObject(); equal(objRepr.crossOrigin, '', 'toObject should return proper crossOrigin value'); var elImage2 = _createImageElement(); - elImage2.crossOrigin = 'anonymous'; + elImage2.crossOrigin = 'anonymous'; image.setElement(elImage2); equal(elImage2.crossOrigin, 'anonymous', 'setElement should set proper crossOrigin on an img element'); @@ -175,7 +175,7 @@ start(); return; } - + fabric.Image.fromObject(objRepr, function(img) { equal(img.crossOrigin, ''); start(); diff --git a/test/unit/image_filters.js b/test/unit/image_filters.js index 9a87afc6..633c0949 100644 --- a/test/unit/image_filters.js +++ b/test/unit/image_filters.js @@ -14,42 +14,42 @@ IMG_WIDTH = 276, IMG_HEIGHT = 110; - var REFERENCE_IMG_OBJECT = { - 'type': 'image', - 'originX': 'left', - 'originY': 'top', - 'left': 0, - 'top': 0, - 'width': IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects - 'height': IMG_HEIGHT, // or does it now? - 'fill': 'rgb(0,0,0)', - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'strokeLineCap': 'butt', - 'strokeLineJoin': 'miter', - 'strokeMiterLimit': 10, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'src': fabric.isLikelyNode ? undefined : IMG_SRC, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, - 'transparentCorners': true, - 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true, - 'clipTo': null, - 'filters': [] - }; + // var REFERENCE_IMG_OBJECT = { + // 'type': 'image', + // 'originX': 'left', + // 'originY': 'top', + // 'left': 0, + // 'top': 0, + // 'width': IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects + // 'height': IMG_HEIGHT, // or does it now? + // 'fill': 'rgb(0,0,0)', + // 'stroke': null, + // 'strokeWidth': 1, + // 'strokeDashArray': null, + // 'strokeLineCap': 'butt', + // 'strokeLineJoin': 'miter', + // 'strokeMiterLimit': 10, + // 'scaleX': 1, + // 'scaleY': 1, + // 'angle': 0, + // 'flipX': false, + // 'flipY': false, + // 'opacity': 1, + // 'src': fabric.isLikelyNode ? undefined : IMG_SRC, + // 'selectable': true, + // 'hasControls': true, + // 'hasBorders': true, + // 'hasRotatingPoint': true, + // 'transparentCorners': true, + // 'perPixelTargetFind': false, + // 'shadow': null, + // 'visible': true, + // 'clipTo': null, + // 'filters': [] + // }; function _createImageElement() { - return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'); + return fabric.isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'); } function _createImageObject(width, height, callback) { @@ -62,7 +62,7 @@ } function createImageObject(callback) { - return _createImageObject(IMG_WIDTH, IMG_HEIGHT, callback) + return _createImageObject(IMG_WIDTH, IMG_HEIGHT, callback); } function setSrc(img, src, callback) { diff --git a/test/unit/itext.js b/test/unit/itext.js index fe7d38c9..7b680522 100644 --- a/test/unit/itext.js +++ b/test/unit/itext.js @@ -42,7 +42,7 @@ 'backgroundColor': '', 'textBackgroundColor': '', 'fillRule': 'nonzero', - 'globalCompositeOperation': 'source-over', + 'globalCompositeOperation': 'source-over', styles: { } }; @@ -144,7 +144,7 @@ // 'tes|t' iText.selectionStart = iText.selectionEnd = 3; - var loc = iText.get2DCursorLocation(); + loc = iText.get2DCursorLocation(); equal(loc.lineIndex, 0); equal(loc.charIndex, 3); @@ -152,7 +152,7 @@ // test // fo|o iText.selectionStart = iText.selectionEnd = 7; - var loc = iText.get2DCursorLocation(); + loc = iText.get2DCursorLocation(); equal(loc.lineIndex, 1); equal(loc.charIndex, 2); @@ -161,7 +161,7 @@ // foo // barba|z iText.selectionStart = iText.selectionEnd = 14; - var loc = iText.get2DCursorLocation(); + loc = iText.get2DCursorLocation(); equal(loc.lineIndex, 2); equal(loc.charIndex, 5); @@ -171,7 +171,7 @@ var iText = new fabric.IText('test'); ok(iText.isEmptyStyles()); - var iText = new fabric.IText('test', { + iText = new fabric.IText('test', { styles: { 0: { 0: { } @@ -183,7 +183,7 @@ }); ok(iText.isEmptyStyles()); - var iText = new fabric.IText('test', { + iText = new fabric.IText('test', { styles: { 0: { 0: { } diff --git a/test/unit/line.js b/test/unit/line.js index d470a2d3..d7e56cf0 100644 --- a/test/unit/line.js +++ b/test/unit/line.js @@ -145,12 +145,7 @@ }); test('stroke-width in a style', function() { - var lineEl = fabric.document.createElement('line'), - x1 = 0, - y1 = 0, - x2 = 10, - y2 = 10; - + var lineEl = fabric.document.createElement('line'); lineEl.setAttribute('style', 'stroke-width:4'); var oLine = fabric.Line.fromElement(lineEl); diff --git a/test/unit/object.js b/test/unit/object.js index e804f454..751b8604 100644 --- a/test/unit/object.js +++ b/test/unit/object.js @@ -17,7 +17,7 @@ IMG_HEIGHT = 110; function _createImageElement() { - return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'); + return fabric.isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'); } function createImageObject(callback) { @@ -575,7 +575,6 @@ test('getBoundingRectWithStroke', function() { } else { var image; - var _this = this; setTimeout(function() { ok(image); @@ -590,15 +589,15 @@ test('getBoundingRectWithStroke', function() { }); test('toDataURL', function() { - var data = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQA'+ - 'AABkCAYAAABw4pVUAAAA+UlEQVR4nO3RoRHAQBDEsOu/6YR+B2s'+ - 'gIO4Z3919pMwDMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AI'+ - 'akzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR'+ - '6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHoAhafMADEm'+ - 'bB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0'+ - 'uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPw'+ - 'JC0eQCGpM0DMCRtHsDjB5K06yueJFXJAAAAAElFTkSuQmCC'; + // var data = + // 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQA'+ + // 'AABkCAYAAABw4pVUAAAA+UlEQVR4nO3RoRHAQBDEsOu/6YR+B2s'+ + // 'gIO4Z3919pMwDMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AI'+ + // 'akzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR'+ + // '6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHoAhafMADEm'+ + // 'bB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0'+ + // 'uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPw'+ + // 'JC0eQCGpM0DMCRtHsDjB5K06yueJFXJAAAAAElFTkSuQmCC'; var cObj = new fabric.Rect({ width: 100, height: 100, fill: 'red' @@ -607,7 +606,7 @@ test('getBoundingRectWithStroke', function() { ok(typeof cObj.toDataURL == 'function'); if (!fabric.Canvas.supports('toDataURL')) { - alert('toDataURL is not supported by this environment. Some of the tests can not be run.'); + window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.'); } else { var dataURL = cObj.toDataURL(); @@ -615,7 +614,7 @@ test('getBoundingRectWithStroke', function() { equal(dataURL.substring(0, 21), 'data:image/png;base64'); try { - var dataURL = cObj.toDataURL({ format: 'jpeg' }); + dataURL = cObj.toDataURL({ format: 'jpeg' }); equal(dataURL.substring(0, 22), 'data:image/jpeg;base64'); } catch(err) { @@ -625,15 +624,15 @@ test('getBoundingRectWithStroke', function() { }); test('toDataURL & reference to canvas', function() { - var data = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQA'+ - 'AABkCAYAAABw4pVUAAAA+UlEQVR4nO3RoRHAQBDEsOu/6YR+B2s'+ - 'gIO4Z3919pMwDMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AI'+ - 'akzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR'+ - '6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHoAhafMADEm'+ - 'bB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0'+ - 'uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPw'+ - 'JC0eQCGpM0DMCRtHsDjB5K06yueJFXJAAAAAElFTkSuQmCC'; + // var data = + // 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQA'+ + // 'AABkCAYAAABw4pVUAAAA+UlEQVR4nO3RoRHAQBDEsOu/6YR+B2s'+ + // 'gIO4Z3919pMwDMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AI'+ + // 'akzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR'+ + // '6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHoAhafMADEm'+ + // 'bB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0'+ + // 'uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPw'+ + // 'JC0eQCGpM0DMCRtHsDjB5K06yueJFXJAAAAAElFTkSuQmCC'; var cObj = new fabric.Rect({ width: 100, height: 100, fill: 'red' @@ -641,7 +640,7 @@ test('toDataURL & reference to canvas', function() { canvas.add(cObj); if (!fabric.Canvas.supports('toDataURL')) { - alert('toDataURL is not supported by this environment. Some of the tests can not be run.'); + window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.'); } else { var objCanvas = cObj.canvas; @@ -1113,11 +1112,11 @@ test('toDataURL & reference to canvas', function() { equal(object.shadow.blur, 10); equal(object.shadow.offsetX, 5); equal(object.shadow.offsetY, 15); - + equal(object.setShadow(null), object, 'should be chainable'); ok(!(object.shadow instanceof fabric.Shadow)); equal(object.shadow, null); - + }); test('set shadow', function() { diff --git a/test/unit/parser.js b/test/unit/parser.js index 64fcdce0..9a7939b1 100644 --- a/test/unit/parser.js +++ b/test/unit/parser.js @@ -18,7 +18,7 @@ return element; } - var EXPECTED_PATH_JSON = "{\"type\":\"path\",\"left\":0,\"top\":0,\"width\":93,\"height\":137,\"fill\":\"#99CCFF\",\"stroke\":null,\"strokeWidth\":1,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"selectable\":true,\"hasControls\":true,\"hasBorders\":true,\"hasRotatingPoint\":false,\"path\":[[\"M\",62.022,30.848],[\"c\",4.251,0.038,9.565,-2.206,13.424,-3.924],[\"c\",3.131,-1.396,4.47,-1.299,7.833,0.263],[\"c\",2.18,1.012,3.883,-1.551,5.824,-2.048],[\"c\",0.243,-0.062,0.537,0.464,0.374,0.652],[\"c\",-0.553,0.639,-2.932,1.753,-2.679,2.821],[\"c\",0.184,0.779,4.081,0.817,5.226,1.347],[\"c\",1.008,0.653,-3.22,0.665,-3.17,1.028],[\"c\",1.038,0.191,2.175,0.279,3.03,0.703],[\"c\",0.482,0.238,-0.707,0.815,-1.245,0.731],[\"c\",0.194,0.103,0.711,0.257,0.583,0.436],[\"c\",-0.144,0.891,-3.265,0.128,-4.132,-0.003],[\"c\",-0.688,-0.104,-3.754,-0.843,-3.892,0.039],[\"c\",-0.092,0.586,0.47,1.079,0.133,2.617],[\"c\",-0.314,1.438,-1.942,1.633,-1.831,1.024],[\"c\",0.273,-1.496,1.201,-1.914,-0.4,-3.564],[\"c\",-0.979,-1.01,-1.908,-2.344,-2.138,-3.425],[\"c\",-7.581,1.092,-9.456,6.321,-17.365,7.858],[\"c\",-2.787,0.541,-5.233,-1.016,-7.887,-2.27],[\"c\",0.168,0.259,0.457,0.272,0.169,1.184],[\"c\",-0.29,0.918,-0.479,2.081,-0.225,3.104],[\"c\",0.539,2.169,1.73,4.464,2.5,6.755],[\"c\",1.481,4.415,0.996,11.273,0.42,15.21],[\"c\",-0.105,0.715,0.497,1.432,0.129,2.608],[\"c\",-0.128,0.413,0.384,1.027,0.347,1.458],[\"c\",-0.195,2.236,1.106,2.01,3.446,4.502],[\"c\",-0.21,0.252,-0.926,0.504,-1.136,0.756],[\"c\",4.353,5.205,8.404,10.612,11.622,16.651],[\"c\",0.805,1.512,1.511,3.199,1.511,4.913],[\"c\",0,1.955,-1.154,2.843,-2.057,4.577],[\"c\",-0.741,1.423,-2.081,2.305,-3.121,3.526],[\"c\",-5.631,6.614,-9.57,11.287,-15.186,17.936],[\"c\",-0.976,3.091,1.141,4.783,1.093,6.394],[\"c\",-0.011,0.372,-0.267,0.74,-0.555,1.119],[\"c\",-0.452,0.595,-2.752,-1.225,-4.01,-2.617],[\"c\",-1.657,8.48,5.22,10.332,8.284,12.274],[\"c\",0.37,0.234,0.076,1.004,-0.05,1.424],[\"c\",-4.442,0.217,-7.603,0.246,-11.857,-1.172],[\"c\",-0.783,-0.963,-2.958,-5.188,-4.535,-3.406],[\"c\",-0.735,0.831,-1.092,1.932,-1.637,2.897],[\"c\",-0.462,0,-0.76,-0.247,-1.222,-0.247],[\"c\",-0.042,-1.553,0.19,-2.878,-0.044,-4.413],[\"c\",-0.633,-4.152,-1.551,-4.467,2.037,-7.866],[\"c\",1.782,-1.689,2.374,-2.065,4.045,-3.916],[\"c\",-0.552,-1.562,0.385,-2.303,-1.192,-3],[\"c\",-0.936,-0.041,-3.255,1.205,-3.535,2.152],[\"c\",-0.378,-0.042,-1.001,-0.701,-1.379,-0.742],[\"c\",0.896,-1.794,1.155,-1.791,0.926,-2.913],[\"c\",-0.796,-3.892,1.304,-4.478,3.593,-5.779],[\"c\",3.523,-3.523,6.666,-10.464,10.145,-14.605],[\"c\",1.05,-1.25,2.885,-2.043,4.019,-3.219],[\"c\",-1.26,-1.175,-2.805,-2.106,-3.779,-3.526],[\"c\",-2.437,-3.554,-6.445,-7.633,-9.421,-8.945],[\"c\",-0.756,0.168,-1.513,0.336,-2.269,0.504],[\"c\",-3.89,-2.843,-8.766,-8.817,-6.814,-16.892],[\"c\",1.413,-5.846,8.545,-7.913,2.791,-13.009],[\"c\",-1.299,-1.15,-7.22,-6.915,-8.904,-6.021],[\"c\",-1.257,0.667,-3.774,2.431,-3.966,4.015],[\"c\",-0.299,2.472,-4.275,17.925,-7.829,14.167],[\"C\",9.169,53.682,7.55,47.517,6.059,43.276],[\"c\",-0.873,-2.481,-4.009,-2.109,-5.077,-5],[\"c\",-0.368,-0.997,-1.229,-2.067,-0.914,-3.082],[\"c\",0.169,-0.545,0.63,-0.336,1.175,-0.504],[\"c\",0.535,-2.002,0.199,-1.216,1.704,-1.318],[\"c\",0,-1.215,0.604,-0.978,1.498,-0.978],[\"c\",0.987,-1.624,1.841,-0.106,4.696,1.74],[\"c\",1.461,0.945,1.292,2.708,0.987,4.319],[\"c\",-0.281,1.483,-0.582,2.403,-0.018,3.626],[\"c\",1.14,2.472,4.709,6.794,6.412,9.063],[\"c\",2.12,-2.974,1.531,-6.198,1.788,-10.647],[\"c\",0.1,-1.729,0.84,-3.361,1.26,-5.041],[\"c\",-1.504,-0.111,-2.596,-0.532,-3.277,-1.261],[\"c\",0.336,-0.588,0.672,-1.177,1.008,-1.765],[\"c\",-1.64,-1.64,-1.834,-2.188,-2.325,-4.48],[\"c\",3.162,0,2.708,-1.862,4.342,-4.09],[\"c\",-0.84,-0.504,-1.681,-1.008,-2.521,-1.512],[\"c\",3.833,-2.869,3.828,-2.76,2.539,-8.066],[\"c\",-0.877,-3.608,-0.278,-6.225,2.058,-9.733],[\"C\",25.57,-1.726,27.022,0.327,31.783,0.3],[\"c\",3.464,-0.021,6.667,0.022,8.97,5.944],[\"c\",-0.462,-0.248,-1.416,-0.428,-1.878,-0.126],[\"c\",0.126,0.588,0.825,2.984,0.5,3.49],[\"c\",-0.673,1.049,-0.867,0.977,-0.087,2.224],[\"c\",0.345,0.552,-0.111,2.569,-0.915,4.108],[\"c\",-0.366,0.807,-0.308,2.539,-1.714,2.186],[\"c\",-0.534,0.42,-0.248,1.744,0.203,2.164],[\"c\",2.527,0,5.04,-0.988,7.921,-0.666],[\"C\",47.872,19.969,54.917,30.783,62.022,30.848],[\"L\",62.022,30.848],[\"z\"]]}"; + // var EXPECTED_PATH_JSON = "{\"type\":\"path\",\"left\":0,\"top\":0,\"width\":93,\"height\":137,\"fill\":\"#99CCFF\",\"stroke\":null,\"strokeWidth\":1,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"selectable\":true,\"hasControls\":true,\"hasBorders\":true,\"hasRotatingPoint\":false,\"path\":[[\"M\",62.022,30.848],[\"c\",4.251,0.038,9.565,-2.206,13.424,-3.924],[\"c\",3.131,-1.396,4.47,-1.299,7.833,0.263],[\"c\",2.18,1.012,3.883,-1.551,5.824,-2.048],[\"c\",0.243,-0.062,0.537,0.464,0.374,0.652],[\"c\",-0.553,0.639,-2.932,1.753,-2.679,2.821],[\"c\",0.184,0.779,4.081,0.817,5.226,1.347],[\"c\",1.008,0.653,-3.22,0.665,-3.17,1.028],[\"c\",1.038,0.191,2.175,0.279,3.03,0.703],[\"c\",0.482,0.238,-0.707,0.815,-1.245,0.731],[\"c\",0.194,0.103,0.711,0.257,0.583,0.436],[\"c\",-0.144,0.891,-3.265,0.128,-4.132,-0.003],[\"c\",-0.688,-0.104,-3.754,-0.843,-3.892,0.039],[\"c\",-0.092,0.586,0.47,1.079,0.133,2.617],[\"c\",-0.314,1.438,-1.942,1.633,-1.831,1.024],[\"c\",0.273,-1.496,1.201,-1.914,-0.4,-3.564],[\"c\",-0.979,-1.01,-1.908,-2.344,-2.138,-3.425],[\"c\",-7.581,1.092,-9.456,6.321,-17.365,7.858],[\"c\",-2.787,0.541,-5.233,-1.016,-7.887,-2.27],[\"c\",0.168,0.259,0.457,0.272,0.169,1.184],[\"c\",-0.29,0.918,-0.479,2.081,-0.225,3.104],[\"c\",0.539,2.169,1.73,4.464,2.5,6.755],[\"c\",1.481,4.415,0.996,11.273,0.42,15.21],[\"c\",-0.105,0.715,0.497,1.432,0.129,2.608],[\"c\",-0.128,0.413,0.384,1.027,0.347,1.458],[\"c\",-0.195,2.236,1.106,2.01,3.446,4.502],[\"c\",-0.21,0.252,-0.926,0.504,-1.136,0.756],[\"c\",4.353,5.205,8.404,10.612,11.622,16.651],[\"c\",0.805,1.512,1.511,3.199,1.511,4.913],[\"c\",0,1.955,-1.154,2.843,-2.057,4.577],[\"c\",-0.741,1.423,-2.081,2.305,-3.121,3.526],[\"c\",-5.631,6.614,-9.57,11.287,-15.186,17.936],[\"c\",-0.976,3.091,1.141,4.783,1.093,6.394],[\"c\",-0.011,0.372,-0.267,0.74,-0.555,1.119],[\"c\",-0.452,0.595,-2.752,-1.225,-4.01,-2.617],[\"c\",-1.657,8.48,5.22,10.332,8.284,12.274],[\"c\",0.37,0.234,0.076,1.004,-0.05,1.424],[\"c\",-4.442,0.217,-7.603,0.246,-11.857,-1.172],[\"c\",-0.783,-0.963,-2.958,-5.188,-4.535,-3.406],[\"c\",-0.735,0.831,-1.092,1.932,-1.637,2.897],[\"c\",-0.462,0,-0.76,-0.247,-1.222,-0.247],[\"c\",-0.042,-1.553,0.19,-2.878,-0.044,-4.413],[\"c\",-0.633,-4.152,-1.551,-4.467,2.037,-7.866],[\"c\",1.782,-1.689,2.374,-2.065,4.045,-3.916],[\"c\",-0.552,-1.562,0.385,-2.303,-1.192,-3],[\"c\",-0.936,-0.041,-3.255,1.205,-3.535,2.152],[\"c\",-0.378,-0.042,-1.001,-0.701,-1.379,-0.742],[\"c\",0.896,-1.794,1.155,-1.791,0.926,-2.913],[\"c\",-0.796,-3.892,1.304,-4.478,3.593,-5.779],[\"c\",3.523,-3.523,6.666,-10.464,10.145,-14.605],[\"c\",1.05,-1.25,2.885,-2.043,4.019,-3.219],[\"c\",-1.26,-1.175,-2.805,-2.106,-3.779,-3.526],[\"c\",-2.437,-3.554,-6.445,-7.633,-9.421,-8.945],[\"c\",-0.756,0.168,-1.513,0.336,-2.269,0.504],[\"c\",-3.89,-2.843,-8.766,-8.817,-6.814,-16.892],[\"c\",1.413,-5.846,8.545,-7.913,2.791,-13.009],[\"c\",-1.299,-1.15,-7.22,-6.915,-8.904,-6.021],[\"c\",-1.257,0.667,-3.774,2.431,-3.966,4.015],[\"c\",-0.299,2.472,-4.275,17.925,-7.829,14.167],[\"C\",9.169,53.682,7.55,47.517,6.059,43.276],[\"c\",-0.873,-2.481,-4.009,-2.109,-5.077,-5],[\"c\",-0.368,-0.997,-1.229,-2.067,-0.914,-3.082],[\"c\",0.169,-0.545,0.63,-0.336,1.175,-0.504],[\"c\",0.535,-2.002,0.199,-1.216,1.704,-1.318],[\"c\",0,-1.215,0.604,-0.978,1.498,-0.978],[\"c\",0.987,-1.624,1.841,-0.106,4.696,1.74],[\"c\",1.461,0.945,1.292,2.708,0.987,4.319],[\"c\",-0.281,1.483,-0.582,2.403,-0.018,3.626],[\"c\",1.14,2.472,4.709,6.794,6.412,9.063],[\"c\",2.12,-2.974,1.531,-6.198,1.788,-10.647],[\"c\",0.1,-1.729,0.84,-3.361,1.26,-5.041],[\"c\",-1.504,-0.111,-2.596,-0.532,-3.277,-1.261],[\"c\",0.336,-0.588,0.672,-1.177,1.008,-1.765],[\"c\",-1.64,-1.64,-1.834,-2.188,-2.325,-4.48],[\"c\",3.162,0,2.708,-1.862,4.342,-4.09],[\"c\",-0.84,-0.504,-1.681,-1.008,-2.521,-1.512],[\"c\",3.833,-2.869,3.828,-2.76,2.539,-8.066],[\"c\",-0.877,-3.608,-0.278,-6.225,2.058,-9.733],[\"C\",25.57,-1.726,27.022,0.327,31.783,0.3],[\"c\",3.464,-0.021,6.667,0.022,8.97,5.944],[\"c\",-0.462,-0.248,-1.416,-0.428,-1.878,-0.126],[\"c\",0.126,0.588,0.825,2.984,0.5,3.49],[\"c\",-0.673,1.049,-0.867,0.977,-0.087,2.224],[\"c\",0.345,0.552,-0.111,2.569,-0.915,4.108],[\"c\",-0.366,0.807,-0.308,2.539,-1.714,2.186],[\"c\",-0.534,0.42,-0.248,1.744,0.203,2.164],[\"c\",2.527,0,5.04,-0.988,7.921,-0.666],[\"C\",47.872,19.969,54.917,30.783,62.022,30.848],[\"L\",62.022,30.848],[\"z\"]]}"; QUnit.module('fabric.Parser'); @@ -170,22 +170,22 @@ 'fontStyle': 'italic', 'fontFamily': 'Arial,Helvetica,sans-serif' }; - + deepEqual(styleObj, expectedObject); //testing different unit element.setAttribute('style', 'font: italic 1.5em Arial,Helvetica,sans-serif'); - var styleObj = fabric.parseStyleAttribute(element); + styleObj = fabric.parseStyleAttribute(element); if (styleObj.font) { fabric.parseFontDeclaration(styleObj.font, styleObj); } - var expectedObject = { + expectedObject = { 'font': 'italic 1.5em Arial,Helvetica,sans-serif', 'fontSize': 24, 'fontStyle': 'italic', 'fontFamily': 'Arial,Helvetica,sans-serif' }; - + deepEqual(styleObj, expectedObject); }); @@ -241,51 +241,53 @@ }); test('parseTransformAttribute', function() { + var parsedValue; + ok(fabric.parseTransformAttribute); var element = fabric.document.createElement('path'); //'translate(-10,-20) scale(2) rotate(45) translate(5,10)' element.setAttribute('transform', 'translate(5,10)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [1,0,0,1,5,10]); element.setAttribute('transform', 'translate(-10,-20)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [1,0,0,1,-10,-20]); var ANGLE_DEG = 90; var ANGLE = ANGLE_DEG * Math.PI / 180; element.setAttribute('transform', 'rotate(' + ANGLE_DEG + ')'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [Math.cos(ANGLE), Math.sin(ANGLE), -Math.sin(ANGLE), Math.cos(ANGLE), 0, 0]); element.setAttribute('transform', 'scale(3.5)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [3.5,0,0,3.5,0,0]); element.setAttribute('transform', 'scale(2 13)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [2,0,0,13,0,0]); element.setAttribute('transform', 'skewX(2)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [1,0,0.03492076949174773,1,0,0]); element.setAttribute('transform', 'skewY(234.111)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [1,1.3820043381762832,0,1,0,0]); element.setAttribute('transform', 'matrix(1,2,3,4,5,6)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [1,2,3,4,5,6]); element.setAttribute('transform', 'translate(21,31) translate(11,22)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [1,0,0,1,32,53]); element.setAttribute('transform', 'scale(2 13) translate(5,15) skewX(11.22)'); - var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); + parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [2,0,0.3967362169237356,13,10,195]); }); @@ -376,17 +378,19 @@ 'opacity should be parsed correctly from "opacity" attribute of ' + tagNames[i] + ' element'); } }); - + test('getCssRule', function() { ok(fabric.getCSSRules); var doc = fabric.document, + svgUid = 'uniqueId', styleElement = doc.createElement('style'); - styleElement.textContent = 'g polygon.cls, rect {fill:#FF0000; stroke:#000000;stroke-width:0.25px;}\ - polygon.cls {fill:none;stroke:#0000FF;}', - doc.body.appendChild(styleElement), - svgUid = 'uniqueId'; + + styleElement.textContent = 'g polygon.cls, rect {fill:#FF0000; stroke:#000000;stroke-width:0.25px;}\ + polygon.cls {fill:none;stroke:#0000FF;}'; + + doc.body.appendChild(styleElement); var expectedObject = { 'g polygon.cls': { @@ -407,7 +411,7 @@ fabric.cssRules[svgUid] = fabric.getCSSRules(doc); deepEqual(fabric.cssRules[svgUid], expectedObject); - + var elPolygon = fabric.document.createElement('polygon'), expectedStyle = { 'fill' : '', @@ -420,9 +424,9 @@ var style = fabric.parseAttributes(elPolygon, [ ]); deepEqual(style, expectedStyle); - + styleElement.textContent = '\t\n'; - expectedStyle = { } + expectedStyle = { }; svgUid = 'uniqueId2'; fabric.cssRules[svgUid] = fabric.getCSSRules(doc); deepEqual(fabric.cssRules[svgUid], expectedStyle); diff --git a/test/unit/path_group.js b/test/unit/path_group.js index aa07d8d2..5480383f 100644 --- a/test/unit/path_group.js +++ b/test/unit/path_group.js @@ -26,7 +26,7 @@ 'clipTo': null, 'backgroundColor': '', 'fillRule': 'nonzero', - 'globalCompositeOperation': 'source-over', + 'globalCompositeOperation': 'source-over', 'paths': getPathObjects() }; @@ -113,19 +113,15 @@ asyncTest('toObject', function() { getPathGroupObject(function(pathGroup) { ok(typeof pathGroup.toObject == 'function'); + var object = pathGroup.toObject(); + ok(typeof object == 'object'); + start(); }); }); asyncTest('complexity', function() { - function sum(objects) { - var i = objects.length, total = 0; - while (i--) { - total += objects[i]; - } - return total; - } getPathGroupObject(function(pathGroup) { ok(typeof pathGroup.complexity == 'function'); @@ -231,7 +227,7 @@ start(); }); }); - + asyncTest('toSVG', function() { ok(fabric.PathGroup); getPathGroupObject(function(pathGroup) { @@ -256,5 +252,5 @@ equal(pathGroup.toSVG(), REFERENCE_PATH_GROUP_SVG); start(); }); - }); + }); })(); diff --git a/test/unit/pattern.js b/test/unit/pattern.js index 361aa5cb..c47d44fd 100644 --- a/test/unit/pattern.js +++ b/test/unit/pattern.js @@ -2,7 +2,9 @@ var IMG_SRC = fabric.isLikelyNode ? (__dirname + '/../fixtures/greyfloral.png') : '../fixtures/greyfloral.png'; function createImageElement() { - return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'); + return fabric.isLikelyNode + ? new (require('canvas').Image)() + : fabric.document.createElement('img'); } function setSrc(img, src, callback) { if (fabric.isLikelyNode) { @@ -61,7 +63,6 @@ equal(object.offsetX, 0); equal(object.offsetY, 0); - var sourceExecuted; var patternWithGetSource = new fabric.Pattern({ source: function() {return fabric.document.createElement("canvas")} }); @@ -78,6 +79,8 @@ }); test('pattern serialization / deserialization (function)', function() { + var patternSourceCanvas, padding; + var pattern = new fabric.Pattern({ source: function() { patternSourceCanvas.setDimensions({ diff --git a/test/unit/util.js b/test/unit/util.js index fc9fbf80..11e29b5c 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -5,7 +5,9 @@ function K (x) { return x } function _createImageElement() { - return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'); + return fabric.isLikelyNode + ? new (require('canvas').Image)() + : fabric.document.createElement('img'); } function getAbsolutePath(path) { @@ -191,11 +193,11 @@ ok(typeof clone == 'function'); var obj = { x: 1, y: [1, 2, 3] }, - clone = clone(obj); + _clone = clone(obj); - equal(clone.x, 1); - notEqual(obj, clone); - equal(clone.y, obj.y); + equal(_clone.x, 1); + notEqual(obj, _clone); + equal(_clone.y, obj.y); }); test('Function.prototype.bind', function() { @@ -208,7 +210,7 @@ var bound = fn.bind(obj); deepEqual([obj, undefined, undefined], bound()); - deepEqual([obj, 1, undefined], bound(1)) + deepEqual([obj, 1, undefined], bound(1)); deepEqual([obj, 1, null], bound(1, null)); bound = fn.bind(obj, 1); @@ -220,7 +222,7 @@ this.y = y; } - var obj = { } + obj = { }; var YAxisPoint = Point.bind(obj, 0); var axisPoint = new YAxisPoint(5); @@ -247,7 +249,7 @@ ok(typeof fabric.util.toArray == 'function'); deepEqual(['x', 'y'], fabric.util.toArray({ 0: 'x', 1: 'y', length: 2 })); - deepEqual([1, 3], fabric.util.toArray(function(){ return arguments }(1, 3))); + deepEqual([1, 3], fabric.util.toArray((function(){ return arguments })(1, 3))); var nodelist = fabric.document.getElementsByTagName('div'), converted = fabric.util.toArray(nodelist); @@ -517,7 +519,7 @@ equal(2, array.indexOf(3, -47), "large negative value for fromIndex"); equal(10, array.indexOf(3, 4)); - equal(10, array.indexOf(3, -5)) + equal(10, array.indexOf(3, -5)); equal(2, array.indexOf(3, {}), "nonsensical value for fromIndex"); equal(2, array.indexOf(3, ""), "nonsensical value for fromIndex"); equal(-1, array.indexOf(3, 41), "fromIndex value larger than the length of the array"); @@ -628,7 +630,7 @@ deepEqual(['1!', '2!', '3!', '4!', '5!'], arr.reduce(function(memo, val) { memo.push(val + '!'); return memo }, [ ])); - var arr = 'foobar'.split(''); + arr = 'foobar'.split(''); equal('f0o1o2b3a4r5', arr.reduce(function(memo, val, index) { return memo + val + index }, '')); }); @@ -768,7 +770,7 @@ }); test('fabric.util.populateWithProperties', function() { - ok(typeof fabric.util.populateWithProperties == 'function') + ok(typeof fabric.util.populateWithProperties == 'function'); var source = { foo: 'bar',