diff --git a/.gitignore b/.gitignore index 04f2b2e0..85d293b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .project -jsdoc-toolkit \ No newline at end of file +jsdoc-toolkit diff --git a/dist/kinetic-Animation-current.min.js b/dist/kinetic-Animation-current.min.js deleted file mode 100644 index 88cef533..00000000 --- a/dist/kinetic-Animation-current.min.js +++ /dev/null @@ -1 +0,0 @@ -Kinetic.Animation=function(a){a||(a={});for(var b in a)this[b]=a[b];this.id=Kinetic.Animation.animIdCounter++},Kinetic.Animation.prototype={start:function(){this.stop(),Kinetic.Animation._addAnimation(this),Kinetic.Animation._handleAnimation()},stop:function(){Kinetic.Animation._removeAnimation(this)}},Kinetic.Animation.animations=[],Kinetic.Animation.animIdCounter=0,Kinetic.Animation.animRunning=!1,Kinetic.Animation.frame={time:0,timeDiff:0,lastTime:(new Date).getTime(),frameRate:0},Kinetic.Animation.fixedRequestAnimFrame=function(a){window.setTimeout(a,1e3/60)},Kinetic.Animation._addAnimation=function(a){this.animations.push(a)},Kinetic.Animation._removeAnimation=function(a){var b=a.id,c=this.animations;for(var d=0;d0){this._runFrames();var a=this;Kinetic.Animation.requestAnimFrame(function(){a._animationLoop()})}else this.animRunning=!1},Kinetic.Animation._handleAnimation=function(){var a=this;this.animRunning||(this.animRunning=!0,a._animationLoop())},Kinetic.Animation.requestAnimFrame=function(a){var b=Kinetic.DD&&Kinetic.DD.moving?this.fixedRequestAnimFrame:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||Kinetic.Animation.fixedRequestAnimFrame;b(a)}; diff --git a/dist/kinetic-Canvas-current.min.js b/dist/kinetic-Canvas-current.min.js deleted file mode 100644 index d007ba7c..00000000 --- a/dist/kinetic-Canvas-current.min.js +++ /dev/null @@ -1 +0,0 @@ -Kinetic.Canvas=function(a,b){this.element=document.createElement("canvas"),this.context=this.element.getContext("2d"),this.element.width=a,this.element.height=b},Kinetic.Canvas.prototype={clear:function(){var a=this.getContext(),b=this.getElement();a.clearRect(0,0,b.width,b.height)},getElement:function(){return this.element},getContext:function(){return this.context},setWidth:function(a){this.element.width=a},setHeight:function(a){this.element.height=a},getWidth:function(){return this.element.width},getHeight:function(){return this.element.height},setSize:function(a,b){this.setWidth(a),this.setHeight(b)},toDataURL:function(a,b){try{return this.element.toDataURL(a,b)}catch(c){return this.element.toDataURL()}}}; diff --git a/dist/kinetic-Circle-current.min.js b/dist/kinetic-Circle-current.min.js deleted file mode 100644 index 477e4a4d..00000000 --- a/dist/kinetic-Circle-current.min.js +++ /dev/null @@ -1 +0,0 @@ -Kinetic.Circle=function(a){this._initCircle(a)},Kinetic.Circle.prototype={_initCircle:function(a){this.setDefaultAttrs({radius:0}),this.shapeType="Circle",a.drawFunc=this.drawFunc,Kinetic.Shape.call(this,a)},drawFunc:function(a){a.beginPath(),a.arc(0,0,this.getRadius(),0,Math.PI*2,!0),a.closePath(),this.fill(a),this.stroke(a)},getWidth:function(){return this.getRadius()*2},getHeight:function(){return this.getRadius()*2},setWidth:function(a){Kinetic.Node.prototype.setWidth.call(this,a),this.setRadius(a/2)},setHeight:function(a){Kinetic.Node.prototype.setHeight.call(this,a),this.setRadius(a/2)}},Kinetic.Global.extend(Kinetic.Circle,Kinetic.Shape),Kinetic.Node.addGettersSetters(Kinetic.Circle,["radius"]); diff --git a/dist/kinetic-Collection-current.min.js b/dist/kinetic-Collection-current.min.js deleted file mode 100644 index 323234aa..00000000 --- a/dist/kinetic-Collection-current.min.js +++ /dev/null @@ -1 +0,0 @@ -Kinetic.Collection=function(){var a=[].slice.call(arguments),b=a.length,c=0;this.length=b;for(;c0)this.children[0].remove()},add:function(a){a._id=Kinetic.Global.idCounter++,a.index=this.children.length,a.parent=this,this.children.push(a);var b=a.getStage();if(!b)Kinetic.Global._addTempNode(a);else{b._addId(a),b._addName(a);var c=Kinetic.Global;c._pullNodes(b)}return this},get:function(a){var b=new Kinetic.Collection;if(a.charAt(0)==="#"){var c=this._getNodeById(a.slice(1));c&&b.push(c)}else if(a.charAt(0)==="."){var d=this._getNodesByName(a.slice(1));Kinetic.Collection.apply(b,d)}else{var e=[],f=this.getChildren();for(var g=0;g0){var f=this.attrs.points[c-1].x,g=this.attrs.points[c-1].y;this._dashedLine(a,f,g,d,e,this.attrs.dashArray)}else a.lineTo(d,e)}this.stroke(a)},setPoints:function(a){this.setAttr("points",Kinetic.Type._getPoints(a))},_dashedLine:function(a,b,c,d,e,f){var g=f.length,h=d-b,i=e-c,j=h>i,k=j?i/h:h/i;k>9999?k=9999:k<-9999&&(k=-9999);var l=Math.sqrt(h*h+i*i),m=0,n=!0;while(l>=.1&&m<1e4){var o=f[m++%g];o===0&&(o=.001),o>l&&(o=l);var p=Math.sqrt(o*o/(1+k*k));j?(b+=h<0&&i<0?p*-1:p,c+=h<0&&i<0?k*p*-1:k*p):(b+=h<0&&i<0?k*p*-1:k*p,c+=h<0&&i<0?p*-1:p),a[n?"lineTo":"moveTo"](b,c),l-=o,n=!n}a.moveTo(d,e)}},Kinetic.Global.extend(Kinetic.Line,Kinetic.Shape),Kinetic.Node.addGettersSetters(Kinetic.Line,["dashArray"]),Kinetic.Node.addGetters(Kinetic.Line,["points"]); diff --git a/dist/kinetic-Node-current.min.js b/dist/kinetic-Node-current.min.js deleted file mode 100644 index b8c03dac..00000000 --- a/dist/kinetic-Node-current.min.js +++ /dev/null @@ -1 +0,0 @@ -Kinetic.Node=function(a){this._nodeInit(a)},Kinetic.Node.prototype={_nodeInit:function(a){this.defaultNodeAttrs={visible:!0,listening:!0,name:undefined,opacity:1,x:0,y:0,scale:{x:1,y:1},rotation:0,offset:{x:0,y:0},draggable:!1},this.setDefaultAttrs(this.defaultNodeAttrs),this.eventListeners={},this.setAttrs(a);var b=this;this.on("idChange.kinetic",function(a){var c=b.getStage();c&&(c._removeId(a.oldVal),c._addId(b))}),this.on("nameChange.kinetic",function(a){var c=b.getStage();c&&(c._removeName(a.oldVal,b._id),c._addName(b))})},on:function(a,b){var c=a.split(" ");for(var d=0;d1?g[1]:"";this.eventListeners[h]||(this.eventListeners[h]=[]),this.eventListeners[h].push({name:i,handler:b})}},off:function(a){var b=a.split(" ");for(var c=0;c1)if(g)this.eventListeners[g]&&this._off(g,f[1]);else for(var d in this.eventListeners)this._off(d,f[1]);else delete this.eventListeners[g]}},remove:function(){var a=this.getParent();if(a&&this.index!==undefined&&a.children[this.index]._id==this._id){var b=a.getStage();b&&(b._removeId(this.getId()),b._removeName(this.getName(),this._id)),Kinetic.Global._removeTempNode(this),a.children.splice(this.index,1),a._setChildrenIndices();while(this.children&&this.children.length>0)this.children[0].remove()}},getAttrs:function(){return this.attrs},setDefaultAttrs:function(a){this.attrs===undefined&&(this.attrs={});if(a)for(var b in a)this.attrs[b]===undefined&&(this.attrs[b]=a[b])},setAttrs:function(a){if(a)for(var b in a){var c="set"+b.charAt(0).toUpperCase()+b.slice(1);Kinetic.Type._isFunction(this[c])?this[c](a[b]):this.setAttr(b,a[b])}},isVisible:function(){return this.attrs.visible&&this.getParent()&&!this.getParent().isVisible()?!1:this.attrs.visible},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},getZIndex:function(){return this.index},getAbsoluteZIndex:function(){function e(b){var f=[];for(var g=0;g0&&f[0].getLevel()<=a&&e(f)}var a=this.getLevel(),b=this.getStage(),c=this,d=0;return c.nodeType!=="Stage"&&e(c.getStage().getChildren()),d},getLevel:function(){var a=0,b=this.parent;while(b)a++,b=b.parent;return a},setPosition:function(){var a=Kinetic.Type._getXY([].slice.call(arguments));this.setAttr("x",a.x),this.setAttr("y",a.y)},getPosition:function(){return{x:this.attrs.x,y:this.attrs.y}},getAbsolutePosition:function(){var a=this.getAbsoluteTransform(),b=this.getOffset();return a.translate(b.x,b.y),a.getTranslation()},setAbsolutePosition:function(){var a=Kinetic.Type._getXY([].slice.call(arguments)),b=this._clearTransform();this.attrs.x=b.x,this.attrs.y=b.y,delete b.x,delete b.y;var c=this.getAbsoluteTransform();c.invert(),c.translate(a.x,a.y),a={x:this.attrs.x+c.getTranslation().x,y:this.attrs.y+c.getTranslation().y},this.setPosition(a.x,a.y),this._setTransform(b)},move:function(){var a=Kinetic.Type._getXY([].slice.call(arguments)),b=this.getX(),c=this.getY();a.x!==undefined&&(b+=a.x),a.y!==undefined&&(c+=a.y),this.setPosition(b,c)},getRotationDeg:function(){return this.getRotation()*180/Math.PI},setRotationDeg:function(a){this.setRotation(a*Math.PI/180)},rotate:function(a){this.setRotation(this.getRotation()+a)},rotateDeg:function(a){this.setRotation(this.getRotation()+a*Math.PI/180)},moveToTop:function(){var a=this.index;return this.parent.children.splice(a,1),this.parent.children.push(this),this.parent._setChildrenIndices(),!0},moveUp:function(){var a=this.index;if(a0)return this.parent.children.splice(a,1),this.parent.children.splice(a-1,0,this),this.parent._setChildrenIndices(),!0},moveToBottom:function(){var a=this.index;if(a>0)return this.parent.children.splice(a,1),this.parent.children.unshift(this),this.parent._setChildrenIndices(),!0},setZIndex:function(a){var b=this.index;this.parent.children.splice(b,1),this.parent.children.splice(a,0,this),this.parent._setChildrenIndices()},getAbsoluteOpacity:function(){var a=this.getOpacity();return this.getParent()&&(a*=this.getParent().getAbsoluteOpacity()),a},moveTo:function(a){var b=this.parent;b.children.splice(this.index,1),b._setChildrenIndices(),a.children.push(this),this.index=a.children.length-1,this.parent=a,a._setChildrenIndices()},toObject:function(){var a={},b=Kinetic.Type;a.attrs={};for(var c in this.attrs){var d=this.attrs[c];!b._isFunction(d)&&!b._isElement(d)&&(!b._isObject(d)||!b._hasMethods(d))&&(a.attrs[c]=d)}return a.nodeType=this.nodeType,a.shapeType=this.shapeType,a},toJSON:function(){return JSON.stringify(this.toObject())},getParent:function(){return this.parent},getLayer:function(){return this.getParent().getLayer()},getStage:function(){return this.getParent()?this.getParent().getStage():undefined},simulate:function(a,b){this._handleEvent(a,b||{})},fire:function(a,b){this._executeHandlers(a,b||{})},getAbsoluteTransform:function(){var a=new Kinetic.Transform,b=[],c=this.parent;b.unshift(this);while(c)b.unshift(c),c=c.parent;for(var d=0;di?h:i,o=h>i?1:h/i,p=h>i?i/h:1;a.translate(f,g),a.rotate(l),a.scale(o,p),a.arc(0,0,n,j,j+k,1-m),a.scale(1/o,1/p),a.rotate(-l),a.translate(-f,-g);break;case"z":a.closePath()}}this.fill(a),this.stroke(a)}},Kinetic.Global.extend(Kinetic.Path,Kinetic.Shape),Kinetic.Path.getLineLength=function(a,b,c,d){return Math.sqrt((c-a)*(c-a)+(d-b)*(d-b))},Kinetic.Path.getPointOnLine=function(a,b,c,d,e,f,g){f===undefined&&(f=b),g===undefined&&(g=c);var h=(e-c)/(d-b+1e-8),i=Math.sqrt(a*a/(1+h*h));d0&&k[0]===""&&k.shift();for(var l=0;l0){if(isNaN(k[0]))break;var m=null,n=[],o=g,p=h;switch(j){case"l":g+=k.shift(),h+=k.shift(),m="L",n.push(g,h);break;case"L":g=k.shift(),h=k.shift(),n.push(g,h);break;case"m":g+=k.shift(),h+=k.shift(),m="M",n.push(g,h),j="l";break;case"M":g=k.shift(),h=k.shift(),m="M",n.push(g,h),j="L";break;case"h":g+=k.shift(),m="L",n.push(g,h);break;case"H":g=k.shift(),m="L",n.push(g,h);break;case"v":h+=k.shift(),m="L",n.push(g,h);break;case"V":h=k.shift(),m="L",n.push(g,h);break;case"C":n.push(k.shift(),k.shift(),k.shift(),k.shift()),g=k.shift(),h=k.shift(),n.push(g,h);break;case"c":n.push(g+k.shift(),h+k.shift(),g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),m="C",n.push(g,h);break;case"S":var q=g,r=h,s=f[f.length-1];s.command==="C"&&(q=g+(g-s.points[2]),r=h+(h-s.points[3])),n.push(q,r,k.shift(),k.shift()),g=k.shift(),h=k.shift(),m="C",n.push(g,h);break;case"s":var q=g,r=h,s=f[f.length-1];s.command==="C"&&(q=g+(g-s.points[2]),r=h+(h-s.points[3])),n.push(q,r,g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),m="C",n.push(g,h);break;case"Q":n.push(k.shift(),k.shift()),g=k.shift(),h=k.shift(),n.push(g,h);break;case"q":n.push(g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),m="Q",n.push(g,h);break;case"T":var q=g,r=h,s=f[f.length-1];s.command==="Q"&&(q=g+(g-s.points[0]),r=h+(h-s.points[1])),g=k.shift(),h=k.shift(),m="Q",n.push(q,r,g,h);break;case"t":var q=g,r=h,s=f[f.length-1];s.command==="Q"&&(q=g+(g-s.points[0]),r=h+(h-s.points[1])),g+=k.shift(),h+=k.shift(),m="Q",n.push(q,r,g,h);break;case"A":var t=k.shift(),u=k.shift(),v=k.shift(),w=k.shift(),x=k.shift(),y=g,z=h;g=k.shift(),h=k.shift(),m="A",n=this.convertEndpointToCenterParameterization(y,z,g,h,w,x,t,u,v);break;case"a":var t=k.shift(),u=k.shift(),v=k.shift(),w=k.shift(),x=k.shift(),y=g,z=h;g+=k.shift(),h+=k.shift(),m="A",n=this.convertEndpointToCenterParameterization(y,z,g,h,w,x,t,u,v)}f.push({command:m||j,points:n,start:{x:o,y:p},pathLength:this.calcLength(o,p,m||j,n)})}(j==="z"||j==="Z")&&f.push({command:"z",points:[],start:undefined,pathLength:0})}return f},Kinetic.Path.calcLength=function(a,b,c,d){var e,f,g,h=Kinetic.Path;switch(c){case"L":return h.getLineLength(a,b,d[0],d[1]);case"C":e=0,f=h.getPointOnCubicBezier(0,a,b,d[0],d[1],d[2],d[3],d[4],d[5]);for(t=.01;t<=1;t+=.01)g=h.getPointOnCubicBezier(t,a,b,d[0],d[1],d[2],d[3],d[4],d[5]),e+=h.getLineLength(f.x,f.y,g.x,g.y),f=g;return e;case"Q":e=0,f=h.getPointOnQuadraticBezier(0,a,b,d[0],d[1],d[2],d[3]);for(t=.01;t<=1;t+=.01)g=h.getPointOnQuadraticBezier(t,a,b,d[0],d[1],d[2],d[3]),e+=h.getLineLength(f.x,f.y,g.x,g.y),f=g;return e;case"A":e=0;var i=d[4],j=d[5],k=d[4]+j,l=Math.PI/180;Math.abs(i-k)k;t-=l)g=h.getPointOnEllipticalArc(d[0],d[1],d[2],d[3],t,0),e+=h.getLineLength(f.x,f.y,g.x,g.y),f=g;else for(t=i+l;t1&&(g*=Math.sqrt(m),h*=Math.sqrt(m));var n=Math.sqrt((g*g*h*h-g*g*l*l-h*h*k*k)/(g*g*l*l+h*h*k*k));e==f&&(n*=-1),isNaN(n)&&(n=0);var o=n*g*l/h,p=n*-h*k/g,q=(a+c)/2+Math.cos(j)*o-Math.sin(j)*p,r=(b+d)/2+Math.sin(j)*o+Math.cos(j)*p,s=function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1])},t=function(a,b){return(a[0]*b[0]+a[1]*b[1])/(s(a)*s(b))},u=function(a,b){return(a[0]*b[1]=1&&(y=0),f===0&&y>0&&(y-=2*Math.PI),f==1&&y<0&&(y+=2*Math.PI),[q,r,g,h,v,y,j,f]},Kinetic.Node.addGettersSetters(Kinetic.Path,["data"]); diff --git a/dist/kinetic-Polygon-current.min.js b/dist/kinetic-Polygon-current.min.js deleted file mode 100644 index 5a463f5c..00000000 --- a/dist/kinetic-Polygon-current.min.js +++ /dev/null @@ -1 +0,0 @@ -Kinetic.Polygon=function(a){this._initPolygon(a)},Kinetic.Polygon.prototype={_initPolygon:function(a){this.setDefaultAttrs({points:[]}),this.shapeType="Polygon",a.drawFunc=this.drawFunc,Kinetic.Shape.call(this,a)},drawFunc:function(a){a.beginPath(),a.moveTo(this.attrs.points[0].x,this.attrs.points[0].y);for(var b=1;b0},remove:function(){Kinetic.Node.prototype.remove.call(this),delete Kinetic.Global.shapes[this.colorKey]},draw:function(a){if(this.attrs.drawFunc&&Kinetic.Node.prototype._shouldDraw.call(this,a)){var b=this.getStage(),c=a.getContext(),d=[],e=this.parent;d.unshift(this);while(e)d.unshift(e),e=e.parent;c.save();for(var f=0;f=0;d--){var e=c[d];if(e.isVisible()&&e.isListening()){var f=e.bufferCanvas.context.getImageData(Math.round(a.x),Math.round(a.y),1,1).data;if(f[3]===255){var g=Kinetic.Type._rgbToHex(f[0],f[1],f[2]);return b=Kinetic.Global.shapes[g],{shape:b,pixel:f}}if(f[0]>0||f[1]>0||f[2]>0||f[3]>0)return{pixel:f}}}return null},_getNodeById:function(a){return this.ids[a]||null},_getNodesByName:function(a){return this.names[a]||[]},_resizeDOM:function(){if(this.content){var a=this.attrs.width,b=this.attrs.height;this.content.style.width=a+"px",this.content.style.height=b+"px",this.bufferCanvas.setSize(a,b);var c=this.children;for(var d=0;d0&&d&&(this.attrs.height==="auto"||e*(c+1)this.attrs.width-this.attrs.padding*2){if(f==0)break;var i=h.lastIndexOf(" "),j=h.lastIndexOf("-"),k=Math.max(i,j);if(k>=0){g=a.splice(0,1+k).join("");break}g=a.splice(0,f).join("");break}f++,f===a.length&&(g=a.splice(0,f).join(""))}this.textWidth=Math.max(this.textWidth,this._getTextSize(g).width),g!==undefined&&(b.push(g),d=!0),c++}this.textArr=b}},Kinetic.Global.extend(Kinetic.Text,Kinetic.Shape),Kinetic.Node.addGettersSetters(Kinetic.Text,["fontFamily","fontSize","fontStyle","textFill","textStroke","textStrokeWidth","padding","align","lineHeight"]),Kinetic.Node.addGetters(Kinetic.Text,["text"]); diff --git a/dist/kinetic-TextPath-current.min.js b/dist/kinetic-TextPath-current.min.js deleted file mode 100644 index d6eb3765..00000000 --- a/dist/kinetic-TextPath-current.min.js +++ /dev/null @@ -1 +0,0 @@ -Kinetic.TextPath=function(a){this._initTextPath(a)},Kinetic.TextPath.prototype={_initTextPath:function(a){this.setDefaultAttrs({fontFamily:"Calibri",fontSize:12,fontStyle:"normal",detectionType:"path",text:""}),this.dummyCanvas=document.createElement("canvas"),this.shapeType="TextPath",this.dataArray=[];var b=this;a.drawFunc=this.drawFunc,Kinetic.Shape.call(this,a),this.dataArray=Kinetic.Path.parsePathData(this.attrs.data),this.on("dataChange",function(){b.dataArray=Kinetic.Path.parsePathData(this.attrs.data)});var c=["text","textStroke","textStrokeWidth"];for(var d=0;d0)return g=c,b[c];b[c].command=="M"&&(d={x:b[c].points[0],y:b[c].points[1]})}return{}},j=function(b,c){var g=a._getTextSize(b).width,j=0,k=0,l=!1;e=undefined;while(Math.abs(g-j)/g>.01&&k<25){k++;var m=j;while(f===undefined)f=i(),f&&m+f.pathLengthg?e=Kinetic.Path.getPointOnLine(g,d.x,d.y,f.points[0],f.points[1],d.x,d.y):f=undefined;break;case"A":var o=f.points[4],p=f.points[5],q=f.points[4]+p;h===0?h=o+1e-8:g>j?h+=Math.PI/180*p/Math.abs(p):h-=Math.PI/360*p/Math.abs(p),Math.abs(h)>Math.abs(q)&&(h=q,n=!0),e=Kinetic.Path.getPointOnEllipticalArc(f.points[0],f.points[1],f.points[2],f.points[3],h,f.points[6]);break;case"C":h===0?g>f.pathLength?h=1e-8:h=g/f.pathLength:g>j?h+=(g-j)/f.pathLength:h-=(j-g)/f.pathLength,h>1&&(h=1,n=!0),e=Kinetic.Path.getPointOnCubicBezier(h,f.start.x,f.start.y,f.points[0],f.points[1],f.points[2],f.points[3],f.points[4],f.points[5]);break;case"Q":h===0?h=g/f.pathLength:g>j?h+=(g-j)/f.pathLength:h-=(j-g)/f.pathLength,h>1&&(h=1,n=!0),e=Kinetic.Path.getPointOnQuadraticBezier(h,f.start.x,f.start.y,f.points[0],f.points[1],f.points[2],f.points[3])}e!==undefined&&(j=Kinetic.Path.getLineLength(d.x,d.y,e.x,e.y)),n&&(n=!1,f=undefined)}};for(var k=0;k=c.tweens.length&&c.onFinished()}}},Kinetic.Transition.prototype={start:function(){for(var a=0;athis.getDuration()?this.looping?(this.rewind(a-this._duration),this.update(),this.broadcastMessage("onLooped",{target:this,type:"onLooped"})):(this._time=this._duration,this.update(),this.stop(),this.broadcastMessage("onFinished",{target:this,type:"onFinished"})):a<0?(this.rewind(),this.update()):(this._time=a,this.update())},getTime:function(){return this._time},setDuration:function(a){this._duration=a===null||a<=0?1e5:a},getDuration:function(){return this._duration},setPosition:function(a){this.prevPos=this._pos,this.propFunc(a),this._pos=a,this.broadcastMessage("onChanged",{target:this,type:"onChanged"})},getPosition:function(a){return a===undefined&&(a=this._time),this.func(a,this.begin,this._change,this._duration)},setFinish:function(a){this._change=a-this.begin},getFinish:function(){return this.begin+this._change},start:function(){this.rewind(),this.startEnterFrame(),this.broadcastMessage("onStarted",{target:this,type:"onStarted"})},rewind:function(a){this.stop(),this._time=a===undefined?0:a,this.fixTime(),this.update()},fforward:function(){this._time=this._duration,this.fixTime(),this.update()},update:function(){this.setPosition(this.getPosition(this._time))},startEnterFrame:function(){this.stopEnterFrame(),this.isPlaying=!0,this.onEnterFrame()},onEnterFrame:function(){this.isPlaying&&this.nextFrame()},nextFrame:function(){this.setTime((this.getTimer()-this._startTime)/1e3)},stop:function(){this.stopEnterFrame(),this.broadcastMessage("onStopped",{target:this,type:"onStopped"})},stopEnterFrame:function(){this.isPlaying=!1},continueTo:function(a,b){this.begin=this._pos,this.setFinish(a),this._duration!==undefined&&this.setDuration(b),this.start()},resume:function(){this.fixTime(),this.startEnterFrame(),this.broadcastMessage("onResumed",{target:this,type:"onResumed"})},yoyo:function(){this.continueTo(this.begin,this._time)},addListener:function(a){return this.removeListener(a),this._listeners.push(a)},removeListener:function(a){var b=this._listeners,c=b.length;while(c--)if(b[c]==a)return b.splice(c,1),!0;return!1},broadcastMessage:function(){var a=[];for(var b=0;b0},_getXY:function(a){if(this._isNumber(a))return{x:a,y:a};if(this._isArray(a)){if(a.length===1){var b=a[0];if(this._isNumber(b))return{x:b,y:b};if(this._isArray(b))return{x:b[0],y:b[1]};if(this._isObject(b))return b}else if(a.length>=2)return{x:a[0],y:a[1]}}else if(this._isObject(a))return a;return{x:0,y:0}},_getSize:function(a){if(this._isNumber(a))return{width:a,height:a};if(this._isArray(a))if(a.length===1){var b=a[0];if(this._isNumber(b))return{width:b,height:b};if(this._isArray(b)){if(b.length>=4)return{width:b[2],height:b[3]};if(b.length>=2)return{width:b[0],height:b[1]}}else if(this._isObject(b))return b}else{if(a.length>=4)return{width:a[2],height:a[3]};if(a.length>=2)return{width:a[0],height:a[1]}}else if(this._isObject(a))return a;return{width:0,height:0}},_getPoints:function(a){if(a===undefined)return[];if(this._isObject(a[0]))return a;var b=[];for(var c=0;c>16&255,g:b>>8&255,b:b&255}},_getRandomColorKey:function(){var a=Math.round(Math.random()*255),b=Math.round(Math.random()*255),c=Math.round(Math.random()*255);return this._rgbToHex(a,b,c)},_merge:function(a,b){var c=this._clone(b);for(var d in a)this._isObject(a[d])?c[d]=this._merge(a[d],c[d]):c[d]=a[d];return c},_clone:function(a){var b={};for(var c in a)this._isObject(a[c])?b[c]=this._clone(a[c]):b[c]=a[c];return b}}; diff --git a/dist/kinetic-current.js b/dist/kinetic-current.js deleted file mode 100644 index 2e960283..00000000 --- a/dist/kinetic-current.js +++ /dev/null @@ -1,6596 +0,0 @@ -/** - * KineticJS JavaScript Library current - * http://www.kineticjs.com/ - * Copyright 2012, Eric Rowell - * Licensed under the MIT or GPL Version 2 licenses. - * Date: Nov 03 2012 - * - * Copyright (C) 2011 - 2012 by Eric Rowell - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -var Kinetic = {}; -Kinetic.Filters = {}; -Kinetic.Plugins = {}; -Kinetic.Global = { - BUFFER_WHITELIST: ['fill', 'stroke', 'textFill', 'textStroke'], - BUFFER_BLACKLIST: ['shadow'], - stages: [], - idCounter: 0, - tempNodes: {}, - //shapes hash. rgb keys and shape values - shapes: {}, - warn: function(str) { - if(console && console.warn) { - console.warn('Kinetic warning: ' + str); - } - }, - extend: function(c1, c2) { - for(var key in c2.prototype) { - if(!( key in c1.prototype)) { - c1.prototype[key] = c2.prototype[key]; - } - } - }, - _pullNodes: function(stage) { - var tempNodes = this.tempNodes; - for(var key in tempNodes) { - var node = tempNodes[key]; - if(node.getStage() !== undefined && node.getStage()._id === stage._id) { - stage._addId(node); - stage._addName(node); - this._removeTempNode(node); - } - } - }, - _addTempNode: function(node) { - this.tempNodes[node._id] = node; - }, - _removeTempNode: function(node) { - delete this.tempNodes[node._id]; - } -}; - -Kinetic.Filters.Grayscale = function(imageData) { - var data = imageData.data; - for(var i = 0; i < data.length; i += 4) { - var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; - // red - data[i] = brightness; - // green - data[i + 1] = brightness; - // blue - data[i + 2] = brightness; - // i+3 is alpha (the fourth element) - } -}; - -/* - * utilities that handle data type detection, conversion, and manipulation - */ -Kinetic.Type = { - /* - * cherry-picked utilities from underscore.js - */ - _isElement: function(obj) { - return !!(obj && obj.nodeType == 1); - }, - _isFunction: function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }, - _isObject: function(obj) { - return (!!obj && obj.constructor == Object); - }, - _isArray: function(obj) { - return Object.prototype.toString.call(obj) == '[object Array]'; - }, - _isNumber: function(obj) { - return Object.prototype.toString.call(obj) == '[object Number]'; - }, - _isString: function(obj) { - return Object.prototype.toString.call(obj) == '[object String]'; - }, - /* - * other utils - */ - _hasMethods: function(obj) { - var names = []; - for(var key in obj) { - if(this._isFunction(obj[key])) - names.push(key); - } - return names.length > 0; - }, - /* - * The argument can be: - * - an integer (will be applied to both x and y) - * - an array of one integer (will be applied to both x and y) - * - an array of two integers (contains x and y) - * - an array of four integers (contains x, y, width, and height) - * - an object with x and y properties - * - an array of one element which is an array of integers - * - an array of one element of an object - */ - _getXY: function(arg) { - if(this._isNumber(arg)) { - return { - x: arg, - y: arg - }; - } - else if(this._isArray(arg)) { - // if arg is an array of one element - if(arg.length === 1) { - var val = arg[0]; - // if arg is an array of one element which is a number - if(this._isNumber(val)) { - return { - x: val, - y: val - }; - } - // if arg is an array of one element which is an array - else if(this._isArray(val)) { - return { - x: val[0], - y: val[1] - }; - } - // if arg is an array of one element which is an object - else if(this._isObject(val)) { - return val; - } - } - // if arg is an array of two or more elements - else if(arg.length >= 2) { - return { - x: arg[0], - y: arg[1] - }; - } - } - // if arg is an object return the object - else if(this._isObject(arg)) { - return arg; - } - - // default - return { - x: 0, - y: 0 - }; - }, - /* - * The argument can be: - * - an integer (will be applied to both width and height) - * - an array of one integer (will be applied to both width and height) - * - an array of two integers (contains width and height) - * - an array of four integers (contains x, y, width, and height) - * - an object with width and height properties - * - an array of one element which is an array of integers - * - an array of one element of an object - */ - _getSize: function(arg) { - if(this._isNumber(arg)) { - return { - width: arg, - height: arg - }; - } - else if(this._isArray(arg)) { - // if arg is an array of one element - if(arg.length === 1) { - var val = arg[0]; - // if arg is an array of one element which is a number - if(this._isNumber(val)) { - return { - width: val, - height: val - }; - } - // if arg is an array of one element which is an array - else if(this._isArray(val)) { - /* - * if arg is an array of one element which is an - * array of four elements - */ - if(val.length >= 4) { - return { - width: val[2], - height: val[3] - }; - } - /* - * if arg is an array of one element which is an - * array of two elements - */ - else if(val.length >= 2) { - return { - width: val[0], - height: val[1] - }; - } - } - // if arg is an array of one element which is an object - else if(this._isObject(val)) { - return val; - } - } - // if arg is an array of four elements - else if(arg.length >= 4) { - return { - width: arg[2], - height: arg[3] - }; - } - // if arg is an array of two elements - else if(arg.length >= 2) { - return { - width: arg[0], - height: arg[1] - }; - } - } - // if arg is an object return the object - else if(this._isObject(arg)) { - return arg; - } - - // default - return { - width: 0, - height: 0 - }; - }, - /* - * arg will be an array of numbers or - * an array of point objects - */ - _getPoints: function(arg) { - if(arg === undefined) { - return []; - } - - // an array of objects - if(this._isObject(arg[0])) { - return arg; - } - // an array of integers - else { - /* - * convert array of numbers into an array - * of objects containing x, y - */ - var arr = []; - for(var n = 0; n < arg.length; n += 2) { - arr.push({ - x: arg[n], - y: arg[n + 1] - }); - } - - return arr; - } - }, - /* - * arg can be an image object or image data - */ - _getImage: function(arg, callback) { - // if arg is null or undefined - if(!arg) { - callback(null); - } - - // if arg is already an image object - else if(this._isElement(arg)) { - callback(arg); - } - - // if arg is a string, then it's a data url - else if(this._isString(arg)) { - var imageObj = new Image(); - imageObj.onload = function() { - callback(imageObj); - } - imageObj.src = arg; - } - - //if arg is an object that contains the data property, it's an image object - else if(arg.data) { - var canvas = document.createElement('canvas'); - canvas.width = arg.width; - canvas.height = arg.height; - var context = canvas.getContext('2d'); - context.putImageData(arg, 0, 0); - var dataUrl = canvas.toDataURL(); - var imageObj = new Image(); - imageObj.onload = function() { - callback(imageObj); - } - imageObj.src = dataUrl; - } - else { - callback(null); - } - }, - _rgbToHex: function(r, g, b) { - return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - }, - _hexToRgb: function(hex) { - var bigint = parseInt(hex, 16); - return { - r: (bigint >> 16) & 255, - g: (bigint >> 8) & 255, - b: bigint & 255 - }; - }, - _getRandomColorKey: function() { - var r = Math.round(Math.random() * 255); - var g = Math.round(Math.random() * 255); - var b = Math.round(Math.random() * 255); - return this._rgbToHex(r, g, b); - }, - // o1 takes precedence over o2 - _merge: function(o1, o2) { - var retObj = this._clone(o2); - for(var key in o1) { - if(this._isObject(o1[key])) { - retObj[key] = this._merge(o1[key], retObj[key]); - } - else { - retObj[key] = o1[key]; - } - } - return retObj; - }, - // deep clone - _clone: function(obj) { - var retObj = {}; - for(var key in obj) { - if(this._isObject(obj[key])) { - retObj[key] = this._clone(obj[key]); - } - else { - retObj[key] = obj[key]; - } - } - return retObj; - } -}; - -/** - * Canvas wrapper constructor - * @constructor - * @param {Number} width - * @param {Number} height - */ -Kinetic.Canvas = function(width, height) { - this.element = document.createElement('canvas'); - this.context = this.element.getContext('2d'); - - // set dimensions - this.element.width = width; - this.element.height = height; -}; - -Kinetic.Canvas.prototype = { - /** - * clear canvas - * @name clear - * @methodOf Kinetic.Canvas.prototype - */ - clear: function() { - var context = this.getContext(); - var el = this.getElement(); - context.clearRect(0, 0, el.width, el.height); - }, - /** - * get element - * @name getElement - * @methodOf Kinetic.Canvas.prototype - */ - getElement: function() { - return this.element; - }, - /** - * get context - * @name getContext - * @methodOf Kinetic.Canvas.prototype - */ - getContext: function() { - return this.context; - }, - /** - * set width - * @name setWidth - * @methodOf Kinetic.Canvas.prototype - */ - setWidth: function(width) { - this.element.width = width; - }, - /** - * set height - * @name setHeight - * @methodOf Kinetic.Canvas.prototype - */ - setHeight: function(height) { - this.element.height = height; - }, - /** - * get width - * @name getWidth - * @methodOf Kinetic.Canvas.prototype - */ - getWidth: function() { - return this.element.width; - }, - /** - * get height - * @name getHeight - * @methodOf Kinetic.Canvas.prototype - */ - getHeight: function() { - return this.element.height; - }, - /** - * set size - * @name setSize - * @methodOf Kinetic.Canvas.prototype - */ - setSize: function(width, height) { - this.setWidth(width); - this.setHeight(height); - }, - /** - * toDataURL - */ - toDataURL: function(mimeType, quality) { - try { - // If this call fails (due to browser bug, like in Firefox 3.6), - // then revert to previous no-parameter image/png behavior - return this.element.toDataURL(mimeType, quality); - } - catch(e) { - return this.element.toDataURL(); - } - } -}; - -/* -* The Tween class was ported from an Adobe Flash Tween library -* to JavaScript by Xaric. In the context of KineticJS, a Tween is -* an animation of a single Node property. A Transition is a set of -* multiple tweens -*/ -Kinetic.Tween = function(obj, propFunc, func, begin, finish, duration) { - this._listeners = []; - this.addListener(this); - this.obj = obj; - this.propFunc = propFunc; - this.begin = begin; - this._pos = begin; - this.setDuration(duration); - this.isPlaying = false; - this._change = 0; - this.prevTime = 0; - this.prevPos = 0; - this.looping = false; - this._time = 0; - this._position = 0; - this._startTime = 0; - this._finish = 0; - this.name = ''; - this.func = func; - this.setFinish(finish); -}; -/* - * Tween methods - */ -Kinetic.Tween.prototype = { - setTime: function(t) { - this.prevTime = this._time; - if(t > this.getDuration()) { - if(this.looping) { - this.rewind(t - this._duration); - this.update(); - this.broadcastMessage('onLooped', { - target: this, - type: 'onLooped' - }); - } - else { - this._time = this._duration; - this.update(); - this.stop(); - this.broadcastMessage('onFinished', { - target: this, - type: 'onFinished' - }); - } - } - else if(t < 0) { - this.rewind(); - this.update(); - } - else { - this._time = t; - this.update(); - } - }, - getTime: function() { - return this._time; - }, - setDuration: function(d) { - this._duration = (d === null || d <= 0) ? 100000 : d; - }, - getDuration: function() { - return this._duration; - }, - setPosition: function(p) { - this.prevPos = this._pos; - this.propFunc(p); - this._pos = p; - this.broadcastMessage('onChanged', { - target: this, - type: 'onChanged' - }); - }, - getPosition: function(t) { - if(t === undefined) { - t = this._time; - } - return this.func(t, this.begin, this._change, this._duration); - }, - setFinish: function(f) { - this._change = f - this.begin; - }, - getFinish: function() { - return this.begin + this._change; - }, - start: function() { - this.rewind(); - this.startEnterFrame(); - this.broadcastMessage('onStarted', { - target: this, - type: 'onStarted' - }); - }, - rewind: function(t) { - this.stop(); - this._time = (t === undefined) ? 0 : t; - this.fixTime(); - this.update(); - }, - fforward: function() { - this._time = this._duration; - this.fixTime(); - this.update(); - }, - update: function() { - this.setPosition(this.getPosition(this._time)); - }, - startEnterFrame: function() { - this.stopEnterFrame(); - this.isPlaying = true; - this.onEnterFrame(); - }, - onEnterFrame: function() { - if(this.isPlaying) { - this.nextFrame(); - } - }, - nextFrame: function() { - this.setTime((this.getTimer() - this._startTime) / 1000); - }, - stop: function() { - this.stopEnterFrame(); - this.broadcastMessage('onStopped', { - target: this, - type: 'onStopped' - }); - }, - stopEnterFrame: function() { - this.isPlaying = false; - }, - continueTo: function(finish, duration) { - this.begin = this._pos; - this.setFinish(finish); - if(this._duration !== undefined) { - this.setDuration(duration); - } - this.start(); - }, - resume: function() { - this.fixTime(); - this.startEnterFrame(); - this.broadcastMessage('onResumed', { - target: this, - type: 'onResumed' - }); - }, - yoyo: function() { - this.continueTo(this.begin, this._time); - }, - addListener: function(o) { - this.removeListener(o); - return this._listeners.push(o); - }, - removeListener: function(o) { - var a = this._listeners; - var i = a.length; - while(i--) { - if(a[i] == o) { - a.splice(i, 1); - return true; - } - } - return false; - }, - broadcastMessage: function() { - var arr = []; - for(var i = 0; i < arguments.length; i++) { - arr.push(arguments[i]); - } - var e = arr.shift(); - var a = this._listeners; - var l = a.length; - for(var i = 0; i < l; i++) { - if(a[i][e]) { - a[i][e].apply(a[i], arr); - } - } - }, - fixTime: function() { - this._startTime = this.getTimer() - this._time * 1000; - }, - getTimer: function() { - return new Date().getTime() - this._time; - } -}; - -Kinetic.Tweens = { - 'back-ease-in': function(t, b, c, d, a, p) { - var s = 1.70158; - return c * (t /= d) * t * ((s + 1) * t - s) + b; - }, - 'back-ease-out': function(t, b, c, d, a, p) { - var s = 1.70158; - return c * (( t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; - }, - 'back-ease-in-out': function(t, b, c, d, a, p) { - var s = 1.70158; - if((t /= d / 2) < 1) { - return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; - } - return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; - }, - 'elastic-ease-in': function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if(t === 0) { - return b; - } - if((t /= d) == 1) { - return b + c; - } - if(!p) { - p = d * 0.3; - } - if(!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; - }, - 'elastic-ease-out': function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if(t === 0) { - return b; - } - if((t /= d) == 1) { - return b + c; - } - if(!p) { - p = d * 0.3; - } - if(!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b); - }, - 'elastic-ease-in-out': function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if(t === 0) { - return b; - } - if((t /= d / 2) == 2) { - return b + c; - } - if(!p) { - p = d * (0.3 * 1.5); - } - if(!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - if(t < 1) { - return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; - } - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; - }, - 'bounce-ease-out': function(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; - } - }, - 'bounce-ease-in': function(t, b, c, d) { - return c - Kinetic.Tweens['bounce-ease-out'](d - t, 0, c, d) + b; - }, - 'bounce-ease-in-out': function(t, b, c, d) { - if(t < d / 2) { - return Kinetic.Tweens['bounce-ease-in'](t * 2, 0, c, d) * 0.5 + b; - } - else { - return Kinetic.Tweens['bounce-ease-out'](t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - }, - // duplicate - /* - strongEaseInOut: function(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - }, - */ - 'ease-in': function(t, b, c, d) { - return c * (t /= d) * t + b; - }, - 'ease-out': function(t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; - }, - 'ease-in-out': function(t, b, c, d) { - if((t /= d / 2) < 1) { - return c / 2 * t * t + b; - } - return -c / 2 * ((--t) * (t - 2) - 1) + b; - }, - 'strong-ease-in': function(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - }, - 'strong-ease-out': function(t, b, c, d) { - return c * (( t = t / d - 1) * t * t * t * t + 1) + b; - }, - 'strong-ease-in-out': function(t, b, c, d) { - if((t /= d / 2) < 1) { - return c / 2 * t * t * t * t * t + b; - } - return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; - }, - 'linear': function(t, b, c, d) { - return c * t / d + b; - } -}; - -/* - * Last updated November 2011 - * By Simon Sarris - * www.simonsarris.com - * sarris@acm.org - * - * Free to use and distribute at will - * So long as you are nice to people, etc - */ - -/* - * The usage of this class was inspired by some of the work done by a forked - * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform - * class. - */ - -Kinetic.Transform = function() { - this.m = [1, 0, 0, 1, 0, 0]; -} - -Kinetic.Transform.prototype = { - /** - * Apply translation - * @param {Number} x - * @param {Number} y - */ - translate: function(x, y) { - this.m[4] += this.m[0] * x + this.m[2] * y; - this.m[5] += this.m[1] * x + this.m[3] * y; - }, - /** - * Apply scale - * @param {Number} sx - * @param {Number} sy - */ - scale: function(sx, sy) { - this.m[0] *= sx; - this.m[1] *= sx; - this.m[2] *= sy; - this.m[3] *= sy; - }, - /** - * Apply rotation - * @param {Number} rad Angle in radians - */ - rotate: function(rad) { - var c = Math.cos(rad); - var s = Math.sin(rad); - var m11 = this.m[0] * c + this.m[2] * s; - var m12 = this.m[1] * c + this.m[3] * s; - var m21 = this.m[0] * -s + this.m[2] * c; - var m22 = this.m[1] * -s + this.m[3] * c; - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - }, - /** - * Returns the translation - * @returns {Object} 2D point(x, y) - */ - getTranslation: function() { - return { - x: this.m[4], - y: this.m[5] - }; - }, - /** - * Transform multiplication - * @param {Kinetic.Transform} matrix - */ - multiply: function(matrix) { - var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1]; - var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1]; - - var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3]; - var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3]; - - var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4]; - var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5]; - - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - this.m[4] = dx; - this.m[5] = dy; - }, - /** - * Invert the matrix - */ - invert: function() { - var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]); - var m0 = this.m[3] * d; - var m1 = -this.m[1] * d; - var m2 = -this.m[2] * d; - var m3 = this.m[0] * d; - var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]); - var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]); - this.m[0] = m0; - this.m[1] = m1; - this.m[2] = m2; - this.m[3] = m3; - this.m[4] = m4; - this.m[5] = m5; - }, - /** - * return matrix - */ - getMatrix: function() { - return this.m; - } -}; - -/** - * Collection constructor. Collection extends - * Array. This class is used in conjunction with get() - * @constructor - */ -Kinetic.Collection = function() { - var args = [].slice.call( arguments ), - length = args.length, i = 0; - - this.length = length; - for (; i < length; i++ ) { - this[ i ] = args[ i ]; - } - return this; -} -Kinetic.Collection.prototype = new Array(); -/** - * apply a method to all nodes in the array - * @name apply - * @methodOf Kinetic.Collection.prototype - * @param {String} method - * @param val - */ - Kinetic.Collection.prototype.apply = function(method) { - args = [].slice.call(arguments); - args.shift(); - for (var n=0; n 0) { - this._runFrames(); - var that = this; - Kinetic.Animation.requestAnimFrame(function() { - that._animationLoop(); - }); - } - else { - this.animRunning = false; - } -}; -Kinetic.Animation._handleAnimation = function() { - var that = this; - if(!this.animRunning) { - this.animRunning = true; - that._animationLoop(); - } -}; -Kinetic.Animation.requestAnimFrame = function(callback) { - var raf = Kinetic.DD && Kinetic.DD.moving ? this.fixedRequestAnimFrame : window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || Kinetic.Animation.fixedRequestAnimFrame; - - raf(callback); -}; - -/** - * Node constructor.  Nodes are entities that can be transformed, layered, - * and have events bound to them. They are the building blocks of a KineticJS - * application - * @constructor - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position - */ -Kinetic.Node = function(config) { - this._nodeInit(config); -}; - -Kinetic.Node.prototype = { - _nodeInit: function(config) { - this.defaultNodeAttrs = { - visible: true, - listening: true, - name: undefined, - opacity: 1, - x: 0, - y: 0, - scale: { - x: 1, - y: 1 - }, - rotation: 0, - offset: { - x: 0, - y: 0 - }, - draggable: false - }; - - this.setDefaultAttrs(this.defaultNodeAttrs); - this.eventListeners = {}; - this.setAttrs(config); - - // bind events - var that = this; - this.on('idChange.kinetic', function(evt) { - var stage = that.getStage(); - if(stage) { - stage._removeId(evt.oldVal); - stage._addId(that); - } - }); - this.on('nameChange.kinetic', function(evt) { - var stage = that.getStage(); - if(stage) { - stage._removeName(evt.oldVal, that._id); - stage._addName(that); - } - }); - }, - /** - * bind events to the node. KineticJS supports mouseover, mousemove, - * mouseout, mousedown, mouseup, click, dblclick, touchstart, touchmove, - * touchend, tap, dbltap, dragstart, dragmove, and dragend. Pass in a string - * of event types delimmited by a space to bind multiple events at once - * such as 'mousedown mouseup mousemove'. include a namespace to bind an - * event by name such as 'click.foobar'. - * @name on - * @methodOf Kinetic.Node.prototype - * @param {String} typesStr - * @param {Function} handler - */ - on: function(typesStr, handler) { - var types = typesStr.split(' '); - /* - * loop through types and attach event listeners to - * each one. eg. 'click mouseover.namespace mouseout' - * will create three event bindings - */ - for(var n = 0; n < types.length; n++) { - var type = types[n]; - var event = type; - var parts = event.split('.'); - var baseEvent = parts[0]; - var name = parts.length > 1 ? parts[1] : ''; - - if(!this.eventListeners[baseEvent]) { - this.eventListeners[baseEvent] = []; - } - - this.eventListeners[baseEvent].push({ - name: name, - handler: handler - }); - } - }, - /** - * remove event bindings from the node. Pass in a string of - * event types delimmited by a space to remove multiple event - * bindings at once such as 'mousedown mouseup mousemove'. - * include a namespace to remove an event binding by name - * such as 'click.foobar'. If you only give a name like '.foobar', - * all events in that namespace will be removed. - * @name off - * @methodOf Kinetic.Node.prototype - * @param {String} typesStr - */ - off: function(typesStr) { - var types = typesStr.split(' '); - - for(var n = 0; n < types.length; n++) { - var type = types[n]; - //var event = (type.indexOf('touch') === -1) ? 'on' + type : type; - var event = type; - var parts = event.split('.'); - var baseEvent = parts[0]; - - if(parts.length > 1) { - if(baseEvent) { - if(this.eventListeners[baseEvent]) { - this._off(baseEvent, parts[1]); - } - } - else { - for(var type in this.eventListeners) { - this._off(type, parts[1]); - } - } - } - else { - delete this.eventListeners[baseEvent]; - } - } - }, - /** - * remove child from container - * @name remove - * @methodOf Kinetic.Container.prototype - * @param {Node} child - */ - remove: function() { - var parent = this.getParent(); - if(parent && this.index !== undefined && parent.children[this.index]._id == this._id) { - var stage = parent.getStage(); - /* - * remove event listeners and references to the node - * from the ids and names hashes - */ - if(stage) { - stage._removeId(this.getId()); - stage._removeName(this.getName(), this._id); - } - - Kinetic.Global._removeTempNode(this); - parent.children.splice(this.index, 1); - parent._setChildrenIndices(); - - // remove children - while(this.children && this.children.length > 0) { - this.children[0].remove(); - } - } - }, - /** - * get attrs - * @name getAttrs - * @methodOf Kinetic.Node.prototype - */ - getAttrs: function() { - return this.attrs; - }, - /** - * set default attrs. This method should only be used if - * you're creating a custom node - * @name setDefaultAttrs - * @methodOf Kinetic.Node.prototype - * @param {Object} confic - */ - setDefaultAttrs: function(config) { - // create attrs object if undefined - if(this.attrs === undefined) { - this.attrs = {}; - } - - if(config) { - for(var key in config) { - /* - * only set the attr if it's undefined in case - * a developer writes a custom class that extends - * a Kinetic Class such that their default property - * isn't overwritten by the Kinetic Class default - * property - */ - if(this.attrs[key] === undefined) { - this.attrs[key] = config[key]; - } - } - } - }, - /** - * set attrs - * @name setAttrs - * @methodOf Kinetic.Node.prototype - * @param {Object} config - */ - setAttrs: function(config) { - if(config) { - for(var key in config) { - var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1); - // use setter if available - if(Kinetic.Type._isFunction(this[method])) { - this[method](config[key]); - } - // otherwise set directly - else { - this.setAttr(key, config[key]); - } - } - } - }, - /** - * determine if shape is visible or not. Shape is visible only - * if it's visible and all of its ancestors are visible. If an ancestor - * is invisible, this means that the shape is also invisible - * @name isVisible - * @methodOf Kinetic.Node.prototype - */ - isVisible: function() { - if(this.attrs.visible && this.getParent() && !this.getParent().isVisible()) { - return false; - } - return this.attrs.visible; - }, - /** - * show node - * @name show - * @methodOf Kinetic.Node.prototype - */ - show: function() { - this.setVisible(true); - }, - /** - * hide node. Hidden nodes are no longer detectable - * @name hide - * @methodOf Kinetic.Node.prototype - */ - hide: function() { - this.setVisible(false); - }, - /** - * get zIndex - * @name getZIndex - * @methodOf Kinetic.Node.prototype - */ - getZIndex: function() { - return this.index; - }, - /** - * get absolute z-index which takes into account sibling - * and parent indices - * @name getAbsoluteZIndex - * @methodOf Kinetic.Node.prototype - */ - getAbsoluteZIndex: function() { - var level = this.getLevel(); - var stage = this.getStage(); - var that = this; - var index = 0; - function addChildren(children) { - var nodes = []; - for(var n = 0; n < children.length; n++) { - var child = children[n]; - index++; - - if(child.nodeType !== 'Shape') { - nodes = nodes.concat(child.getChildren()); - } - - if(child._id === that._id) { - n = children.length; - } - } - - if(nodes.length > 0 && nodes[0].getLevel() <= level) { - addChildren(nodes); - } - } - if(that.nodeType !== 'Stage') { - addChildren(that.getStage().getChildren()); - } - - return index; - }, - /** - * get node level in node tree - * @name getLevel - * @methodOf Kinetic.Node.prototype - */ - getLevel: function() { - var level = 0; - var parent = this.parent; - while(parent) { - level++; - parent = parent.parent; - } - return level; - }, - /** - * set node position - * @name setPosition - * @methodOf Kinetic.Node.prototype - * @param {Number} x - * @param {Number} y - */ - setPosition: function() { - var pos = Kinetic.Type._getXY([].slice.call(arguments)); - this.setAttr('x', pos.x); - this.setAttr('y', pos.y); - }, - /** - * get node position relative to container - * @name getPosition - * @methodOf Kinetic.Node.prototype - */ - getPosition: function() { - return { - x: this.attrs.x, - y: this.attrs.y - }; - }, - /** - * get absolute position - * @name getAbsolutePosition - * @methodOf Kinetic.Node.prototype - */ - getAbsolutePosition: function() { - var trans = this.getAbsoluteTransform(); - var o = this.getOffset(); - trans.translate(o.x, o.y); - return trans.getTranslation(); - }, - /** - * set absolute position - * @name setAbsolutePosition - * @methodOf Kinetic.Node.prototype - * @param {Object} pos object containing an x and - * y property - */ - setAbsolutePosition: function() { - var pos = Kinetic.Type._getXY([].slice.call(arguments)); - var trans = this._clearTransform(); - // don't clear translation - this.attrs.x = trans.x; - this.attrs.y = trans.y; - delete trans.x; - delete trans.y; - - // unravel transform - var it = this.getAbsoluteTransform(); - - it.invert(); - it.translate(pos.x, pos.y); - pos = { - x: this.attrs.x + it.getTranslation().x, - y: this.attrs.y + it.getTranslation().y - }; - - this.setPosition(pos.x, pos.y); - this._setTransform(trans); - }, - /** - * move node by an amount - * @name move - * @methodOf Kinetic.Node.prototype - * @param {Number} x - * @param {Number} y - */ - move: function() { - var pos = Kinetic.Type._getXY([].slice.call(arguments)); - - var x = this.getX(); - var y = this.getY(); - - if(pos.x !== undefined) { - x += pos.x; - } - - if(pos.y !== undefined) { - y += pos.y; - } - - this.setPosition(x, y); - }, - /** - * get rotation in degrees - * @name getRotationDeg - * @methodOf Kinetic.Node.prototype - */ - getRotationDeg: function() { - return this.getRotation() * 180 / Math.PI; - }, - /** - * set rotation in degrees - * @name setRotationDeg - * @methodOf Kinetic.Node.prototype - * @param {Number} rotDeg - */ - setRotationDeg: function(rotDeg) { - this.setRotation(rotDeg * Math.PI / 180); - }, - /** - * rotate node by an amount in radians - * @name rotate - * @methodOf Kinetic.Node.prototype - * @param {Number} theta - */ - rotate: function(theta) { - this.setRotation(this.getRotation() + theta); - }, - /** - * rotate node by an amount in degrees - * @name rotateDeg - * @methodOf Kinetic.Node.prototype - * @param {Number} deg - */ - rotateDeg: function(deg) { - this.setRotation(this.getRotation() + (deg * Math.PI / 180)); - }, - /** - * move node to the top of its siblings - * @name moveToTop - * @methodOf Kinetic.Node.prototype - */ - moveToTop: function() { - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.push(this); - this.parent._setChildrenIndices(); - return true; - }, - /** - * move node up - * @name moveUp - * @methodOf Kinetic.Node.prototype - */ - moveUp: function() { - var index = this.index; - if(index < this.parent.getChildren().length - 1) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index + 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - }, - /** - * move node down - * @name moveDown - * @methodOf Kinetic.Node.prototype - */ - moveDown: function() { - var index = this.index; - if(index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index - 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - }, - /** - * move node to the bottom of its siblings - * @name moveToBottom - * @methodOf Kinetic.Node.prototype - */ - moveToBottom: function() { - var index = this.index; - if(index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.unshift(this); - this.parent._setChildrenIndices(); - return true; - } - }, - /** - * set zIndex - * @name setZIndex - * @methodOf Kinetic.Node.prototype - * @param {Integer} zIndex - */ - setZIndex: function(zIndex) { - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.splice(zIndex, 0, this); - this.parent._setChildrenIndices(); - }, - /** - * get absolute opacity - * @name getAbsoluteOpacity - * @methodOf Kinetic.Node.prototype - */ - getAbsoluteOpacity: function() { - var absOpacity = this.getOpacity(); - if(this.getParent()) { - absOpacity *= this.getParent().getAbsoluteOpacity(); - } - return absOpacity; - }, - /** - * move node to another container - * @name moveTo - * @methodOf Kinetic.Node.prototype - * @param {Container} newContainer - */ - moveTo: function(newContainer) { - var parent = this.parent; - // remove from parent's children - parent.children.splice(this.index, 1); - parent._setChildrenIndices(); - - // add to new parent - newContainer.children.push(this); - this.index = newContainer.children.length - 1; - this.parent = newContainer; - newContainer._setChildrenIndices(); - }, - /** - * convert Node into an object for serialization - * @name toObject - * @methodOf Kinetic.Node.prototype - */ - toObject: function() { - var obj = {}; - var type = Kinetic.Type; - - obj.attrs = {}; - - // serialize only attributes that are not function, image, DOM, or objects with methods - for(var key in this.attrs) { - var val = this.attrs[key]; - if(!type._isFunction(val) && !type._isElement(val) && !(type._isObject(val) && type._hasMethods(val))) { - obj.attrs[key] = val; - } - } - - obj.nodeType = this.nodeType; - obj.shapeType = this.shapeType; - - return obj; - }, - toJSON: function() { - return JSON.stringify(this.toObject()); - }, - /** - * get parent container - * @name getParent - * @methodOf Kinetic.Node.prototype - */ - getParent: function() { - return this.parent; - }, - /** - * get layer that contains the node - * @name getLayer - * @methodOf Kinetic.Node.prototype - */ - getLayer: function() { - return this.getParent().getLayer(); - }, - /** - * get stage that contains the node - * @name getStage - * @methodOf Kinetic.Node.prototype - */ - getStage: function() { - if(this.getParent()) { - return this.getParent().getStage(); - } - else { - return undefined; - } - }, - /** - * simulate event with event bubbling - * @name simulate - * @methodOf Kinetic.Node.prototype - * @param {String} eventType - * @param {EventObject} evt event object - */ - simulate: function(eventType, evt) { - this._handleEvent(eventType, evt || {}); - }, - /** - * synthetically fire an event.  The event object will not bubble up the Node tree.  You can also pass in custom properties - * @name fire - * @methodOf Kinetic.Node.prototype - * @param {String} eventType - * @param {Object} obj optional object which can be used to pass parameters - */ - fire: function(eventType, obj) { - this._executeHandlers(eventType, obj || {}); - }, - /** - * get absolute transform of the node which takes into - * account its parent transforms - * @name getAbsoluteTransform - * @methodOf Kinetic.Node.prototype - */ - getAbsoluteTransform: function() { - // absolute transform - var am = new Kinetic.Transform(); - - var family = []; - var parent = this.parent; - - family.unshift(this); - while(parent) { - family.unshift(parent); - parent = parent.parent; - } - - for(var n = 0; n < family.length; n++) { - var node = family[n]; - var m = node.getTransform(); - am.multiply(m); - } - - return am; - }, - /** - * get transform of the node - * @name getTransform - * @methodOf Kinetic.Node.prototype - */ - getTransform: function() { - var m = new Kinetic.Transform(); - - if(this.attrs.x !== 0 || this.attrs.y !== 0) { - m.translate(this.attrs.x, this.attrs.y); - } - if(this.attrs.rotation !== 0) { - m.rotate(this.attrs.rotation); - } - if(this.attrs.scale.x !== 1 || this.attrs.scale.y !== 1) { - m.scale(this.attrs.scale.x, this.attrs.scale.y); - } - if(this.attrs.offset && (this.attrs.offset.x !== 0 || this.attrs.offset.y !== 0)) { - m.translate(-1 * this.attrs.offset.x, -1 * this.attrs.offset.y); - } - - return m; - }, - /** - * clone node - * @name clone - * @methodOf Kinetic.Node.prototype - * @param {Object} attrs override attrs - */ - clone: function(obj) { - // instantiate new node - var classType = this.shapeType || this.nodeType; - var node = new Kinetic[classType](this.attrs); - - /* - * copy over user listeners - */ - for(var key in this.eventListeners) { - var allListeners = this.eventListeners[key]; - for(var n = 0; n < allListeners.length; n++) { - var listener = allListeners[n]; - /* - * don't include kinetic namespaced listeners because - * these are generated by the constructors - */ - if(listener.name.indexOf('kinetic') < 0) { - // if listeners array doesn't exist, then create it - if(!node.eventListeners[key]) { - node.eventListeners[key] = []; - } - node.eventListeners[key].push(listener); - } - } - } - - // apply attr overrides - node.setAttrs(obj); - return node; - }, - /** - * Creates a composite data URL. If MIME type is not - * specified, then "image/png" will result. For "image/jpeg", specify a quality - * level as quality (range 0.0 - 1.0) - * @name toDataURL - * @methodOf Kinetic.Node.prototype - * @param {Object} config - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toDataURL: function(config) { - var mimeType = config && config.mimeType ? config.mimeType : null; - var quality = config && config.quality ? config.quality : null; - var canvas; - - //if width and height are defined, create new canvas to draw on, else reuse stage buffer canvas - if(config && config.width && config.height) { - canvas = new Kinetic.Canvas(config.width, config.height); - } - else { - canvas = this.getStage().bufferCanvas; - canvas.clear(); - } - - this.draw(canvas); - return canvas.toDataURL(mimeType, quality); - }, - /** - * converts node into an image. Since the toImage - * method is asynchronous, a callback is required - * @name toImage - * @methodOf Kinetic.Node.prototype - * @param {Object} config - * @param {Function} callback since the toImage() method is asynchonrous, the - * resulting image object is passed into the callback function - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toImage: function(config) { - Kinetic.Type._getImage(this.toDataURL(config), function(img) { - config.callback(img); - }); - }, - /** - * set offset. A node's offset defines the position and rotation point - * @name setOffset - * @methodOf Kinetic.Node.prototype - * @param {Number} x - * @param {Number} y - */ - setOffset: function() { - var pos = Kinetic.Type._getXY([].slice.call(arguments)); - if(pos.x === undefined) { - pos.x = this.getOffset().x; - } - if(pos.y === undefined) { - pos.y = this.getOffset().y; - } - this.setAttr('offset', pos); - }, - /** - * set scale. - * @name setScale - * @param {Number} x - * @param {Number} y - * @methodOf Kinetic.Node.prototype - */ - setScale: function() { - var pos = Kinetic.Type._getXY([].slice.call(arguments)); - - if(pos.x === undefined) { - pos.x = this.getScale().x; - } - if(pos.y === undefined) { - pos.y = this.getScale().y; - } - this.setAttr('scale', pos); - - }, - /** - * set size - * @name setSize - * @methodOf Kinetic.Node.prototype - * @param {Number} width - * @param {Number} height - */ - setSize: function() { - // set stage dimensions - var size = Kinetic.Type._getSize(Array.prototype.slice.call(arguments)); - this.setWidth(size.width); - this.setHeight(size.height); - }, - /** - * get size - * @name getSize - * @methodOf Kinetic.Node.prototype - */ - getSize: function() { - return { - width: this.getWidth(), - height: this.getHeight() - }; - }, - /** - * get width - * @name getWidth - * @methodOf Kinetic.Node.prototype - */ - getWidth: function() { - return this.attrs.width || 0; - }, - /** - * get height - * @name getHeight - * @methodOf Kinetic.Node.prototype - */ - getHeight: function() { - return this.attrs.height || 0; - }, - _get: function(selector) { - return this.nodeType === selector ? [this] : []; - }, - _off: function(type, name) { - for(var i = 0; i < this.eventListeners[type].length; i++) { - if(this.eventListeners[type][i].name === name) { - this.eventListeners[type].splice(i, 1); - if(this.eventListeners[type].length === 0) { - delete this.eventListeners[type]; - break; - } - i--; - } - } - }, - _clearTransform: function() { - var trans = { - x: this.attrs.x, - y: this.attrs.y, - rotation: this.attrs.rotation, - scale: { - x: this.attrs.scale.x, - y: this.attrs.scale.y - }, - offset: { - x: this.attrs.offset.x, - y: this.attrs.offset.y - } - }; - - this.attrs.x = 0; - this.attrs.y = 0; - this.attrs.rotation = 0; - this.attrs.scale = { - x: 1, - y: 1 - }; - this.attrs.offset = { - x: 0, - y: 0 - }; - - return trans; - }, - _setTransform: function(trans) { - for(var key in trans) { - this.attrs[key] = trans[key]; - } - }, - _fireBeforeChangeEvent: function(attr, oldVal, newVal) { - this._handleEvent('before' + attr.toUpperCase() + 'Change', { - oldVal: oldVal, - newVal: newVal - }); - }, - _fireChangeEvent: function(attr, oldVal, newVal) { - this._handleEvent(attr + 'Change', { - oldVal: oldVal, - newVal: newVal - }); - }, - setAttr: function(key, val) { - if(val !== undefined) { - var oldVal = this.attrs[key]; - this._fireBeforeChangeEvent(key, oldVal, val); - this.attrs[key] = val; - this._fireChangeEvent(key, oldVal, val); - } - }, - /** - * handle node event - */ - _handleEvent: function(eventType, evt, compareShape) { - if(this.nodeType === 'Shape') { - evt.shape = this; - } - var stage = this.getStage(); - var el = this.eventListeners; - var okayToRun = true; - - if(eventType === 'mouseenter' && compareShape && this._id === compareShape._id) { - okayToRun = false; - } - else if(eventType === 'mouseleave' && compareShape && this._id === compareShape._id) { - okayToRun = false; - } - - if(okayToRun) { - if(el[eventType]) { - this.fire(eventType, evt); - } - - // simulate event bubbling - if(!evt.cancelBubble && this.parent) { - if(compareShape && compareShape.parent) { - this._handleEvent.call(this.parent, eventType, evt, compareShape.parent); - } - else { - this._handleEvent.call(this.parent, eventType, evt); - } - } - } - }, - _executeHandlers: function(eventType, evt) { - var events = this.eventListeners[eventType]; - for(var i = 0; i < events.length; i++) { - events[i].handler.apply(this, [evt]); - } - }, - _shouldDraw: function(canvas) { - return (this.isVisible() && (!canvas || canvas.name !== 'buffer' || this.getListening())); - } -}; - -// add getter and setter methods -Kinetic.Node.addSetters = function(constructor, arr) { - for(var n = 0; n < arr.length; n++) { - var attr = arr[n]; - this._addSetter(constructor, attr); - } -}; -Kinetic.Node.addGetters = function(constructor, arr) { - for(var n = 0; n < arr.length; n++) { - var attr = arr[n]; - this._addGetter(constructor, attr); - } -}; -Kinetic.Node.addGettersSetters = function(constructor, arr) { - this.addSetters(constructor, arr); - this.addGetters(constructor, arr); -}; -Kinetic.Node._addSetter = function(constructor, attr) { - var that = this; - var method = 'set' + attr.charAt(0).toUpperCase() + attr.slice(1); - constructor.prototype[method] = function(val) { - this.setAttr(attr, val); - }; -}; -Kinetic.Node._addGetter = function(constructor, attr) { - var that = this; - var method = 'get' + attr.charAt(0).toUpperCase() + attr.slice(1); - constructor.prototype[method] = function(arg) { - return this.attrs[attr]; - }; -}; -/** - * create node with JSON string. De-serializtion does not generate custom - * shape drawing functions, images, or event handlers (this would make the - * serialized object huge). If your app uses custom shapes, images, and - * event handlers (it probably does), then you need to select the appropriate - * shapes after loading the stage and set these properties via on(), setDrawFunc(), - * and setImage() - * @name create - * @methodOf Kinetic.Node - * @param {String} JSON string - */ -Kinetic.Node.create = function(json, container) { - return this._createNode(JSON.parse(json), container); -}; -Kinetic.Node._createNode = function(obj, container) { - var type; - - // determine type - if(obj.nodeType === 'Shape') { - // add custom shape - if(obj.shapeType === undefined) { - type = 'Shape'; - } - // add standard shape - else { - type = obj.shapeType; - } - } - else { - type = obj.nodeType; - } - - // if container was passed in, add it to attrs - if(container) { - obj.attrs.container = container; - } - - var no = new Kinetic[type](obj.attrs); - if(obj.children) { - for(var n = 0; n < obj.children.length; n++) { - no.add(this._createNode(obj.children[n])); - } - } - - return no; -}; -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Node, ['x', 'y', 'rotation', 'opacity', 'name', 'id', 'listening', 'visible']); -Kinetic.Node.addGetters(Kinetic.Node, ['scale', 'offset']); -Kinetic.Node.addSetters(Kinetic.Node, ['width', 'height']); - -// mappings -/** - * determine if listening to events or not. Alias of getListening() - * @name isListening - * @methodOf Kinetic.Node.prototype - */ -Kinetic.Node.prototype.isListening = Kinetic.Node.prototype.getListening; - -// collection mappings -(function() { - var collectionMappings = ['on', 'off']; - for(var n = 0; n < collectionMappings.length; n++) { - // induce scope - (function(i) { - var method = collectionMappings[i]; - Kinetic.Collection.prototype[method] = function() { - var args = [].slice.call(arguments); - args.unshift(method); - this.apply.apply(this, args); - }; - })(n); - } -})(); - -/** - * set node x position - * @name setX - * @methodOf Kinetic.Node.prototype - * @param {Number} x - */ - -/** - * set node y position - * @name setY - * @methodOf Kinetic.Node.prototype - * @param {Number} y - */ - -/** - * set node rotation in radians - * @name setRotation - * @methodOf Kinetic.Node.prototype - * @param {Number} theta - */ - -/** - * set opacity. Opacity values range from 0 to 1. - * A node with an opacity of 0 is fully transparent, and a node - * with an opacity of 1 is fully opaque - * @name setOpacity - * @methodOf Kinetic.Node.prototype - * @param {Object} opacity - */ - -/** - * set name - * @name setName - * @methodOf Kinetic.Node.prototype - * @param {String} name - */ - -/** - * set id - * @name setId - * @methodOf Kinetic.Node.prototype - * @param {String} id - */ - -/** - * listen or don't listen to events - * @name setListening - * @methodOf Kinetic.Node.prototype - * @param {Boolean} listening - */ - -/** - * set visible - * @name setVisible - * @methodOf Kinetic.Node.prototype - * @param {Boolean} visible - */ - -/** - * get node x position - * @name getX - * @methodOf Kinetic.Node.prototype - */ - -/** - * get node y position - * @name getY - * @methodOf Kinetic.Node.prototype - */ - -/** - * get rotation in radians - * @name getRotation - * @methodOf Kinetic.Node.prototype - */ - -/** - * get opacity. - * @name getOpacity - * @methodOf Kinetic.Node.prototype - */ - -/** - * get name - * @name getName - * @methodOf Kinetic.Node.prototype - */ - -/** - * get id - * @name getId - * @methodOf Kinetic.Node.prototype - */ - -/** - * get scale - * @name getScale - * @methodOf Kinetic.Node.prototype - */ - -/** - * get offset - * @name getOffset - * @methodOf Kinetic.Node.prototype - */ - -/** - * determine if listening to events or not - * @name getListening - * @methodOf Kinetic.Node.prototype - */ - -/** - * determine if visible or not - * @name getVisible - * @methodOf Kinetic.Node.prototype - */ -Kinetic.DD = { - anim: new Kinetic.Animation(), - moving: false, - offset: { - x: 0, - y: 0 - } -}; - -Kinetic.DD._startDrag = function(evt) { - var dd = Kinetic.DD; - var node = dd.node; - - if(node) { - var pos = node.getStage().getUserPosition(); - var dbf = node.attrs.dragBoundFunc; - - var newNodePos = { - x: pos.x - dd.offset.x, - y: pos.y - dd.offset.y - }; - - if(dbf !== undefined) { - newNodePos = dbf.call(node, newNodePos, evt); - } - - node.setAbsolutePosition(newNodePos); - - if(!dd.moving) { - dd.moving = true; - // execute dragstart events if defined - dd.node._handleEvent('dragstart', evt); - } - - // execute user defined ondragmove if defined - dd.node._handleEvent('dragmove', evt); - } -}; -Kinetic.DD._endDrag = function(evt) { - var dd = Kinetic.DD; - var node = dd.node; - if(node) { - if(node.nodeType === 'Stage') { - node.draw(); - } - else { - node.getLayer().draw(); - } - - // handle dragend - if(dd.moving) { - dd.moving = false; - node._handleEvent('dragend', evt); - } - } - dd.node = null; - dd.anim.stop(); -}; -/** - * set draggable - * @name setDraggable - * @methodOf Kinetic.Node.prototype - * @param {String} draggable - */ -Kinetic.Node.prototype.setDraggable = function(draggable) { - this.setAttr('draggable', draggable); - this._dragChange(); -}; -/** - * get draggable - * @name getDraggable - * @methodOf Kinetic.Node.prototype - */ -Kinetic.Node.prototype.getDraggable = function() { - return this.attrs.draggable; -}; -/** - * determine if node is currently in drag and drop mode - * @name isDragging - * @methodOf Kinetic.Node.prototype - */ -Kinetic.Node.prototype.isDragging = function() { - var dd = Kinetic.DD; - return dd.node && dd.node._id === this._id && dd.moving; -}; - -Kinetic.Node.prototype._listenDrag = function() { - this._dragCleanup(); - var that = this; - this.on('mousedown.kinetic touchstart.kinetic', function(evt) { - that._initDrag(); - }); -}; -Kinetic.Node.prototype._initDrag = function() { - var dd = Kinetic.DD; - var stage = this.getStage(); - var pos = stage.getUserPosition(); - - if(pos) { - var m = this.getTransform().getTranslation(); - var am = this.getAbsoluteTransform().getTranslation(); - var ap = this.getAbsolutePosition(); - dd.node = this; - dd.offset.x = pos.x - ap.x; - dd.offset.y = pos.y - ap.y; - - /* - * if dragging and dropping the stage, - * draw all of the layers - */ - if(this.nodeType === 'Stage') { - dd.anim.node = this; - } - else { - dd.anim.node = this.getLayer(); - } - dd.anim.start(); - } -}; -Kinetic.Node.prototype._dragChange = function() { - if(this.attrs.draggable) { - this._listenDrag(); - } - else { - // remove event listeners - this._dragCleanup(); - - /* - * force drag and drop to end - * if this node is currently in - * drag and drop mode - */ - var stage = this.getStage(); - var dd = Kinetic.DD; - if(stage && dd.node && dd.node._id === this._id) { - dd._endDrag(); - } - } -}; -Kinetic.Node.prototype._dragCleanup = function() { - this.off('mousedown.kinetic'); - this.off('touchstart.kinetic'); -}; -/** - * get draggable. Alias of getDraggable() - * @name isDraggable - * @methodOf Kinetic.Node.prototype - */ -Kinetic.Node.prototype.isDraggable = Kinetic.Node.prototype.getDraggable; - -Kinetic.Node.addGettersSetters(Kinetic.Node, ['dragBoundFunc']); - -/** - * set drag bound function. This is used to override the default - * drag and drop position - * @name setDragBoundFunc - * @methodOf Kinetic.Node.prototype - * @param {Function} dragBoundFunc - */ - -/** - * get dragBoundFunc - * @name getDragBoundFunc - * @methodOf Kinetic.Node.prototype - */ -/** - * Transition constructor. The transitionTo() Node method - * returns a reference to the transition object which you can use - * to stop, resume, or restart the transition - * @constructor - */ -Kinetic.Transition = function(node, config) { - this.node = node; - this.config = config; - this.tweens = []; - var that = this; - - // add tween for each property - function addTween(c, attrs, obj, rootObj) { - for(var key in c) { - if(key !== 'duration' && key !== 'easing' && key !== 'callback') { - // if val is an object then traverse - if(Kinetic.Type._isObject(c[key])) { - obj[key] = {}; - addTween(c[key], attrs[key], obj[key], rootObj); - } - else { - that._add(that._getTween(attrs, key, c[key], obj, rootObj)); - } - } - } - } - var obj = {}; - addTween(config, node.attrs, obj, obj); - - var finishedTweens = 0; - for(var n = 0; n < this.tweens.length; n++) { - var tween = this.tweens[n]; - tween.onFinished = function() { - finishedTweens++; - if(finishedTweens >= that.tweens.length) { - that.onFinished(); - } - }; - } -}; -/* - * Transition methods - */ -Kinetic.Transition.prototype = { - /** - * start transition - * @name start - * @methodOf Kinetic.Transition.prototype - */ - start: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].start(); - } - }, - /** - * stop transition - * @name stop - * @methodOf Kinetic.Transition.prototype - */ - stop: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].stop(); - } - }, - /** - * resume transition - * @name resume - * @methodOf Kinetic.Transition.prototype - */ - resume: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].resume(); - } - }, - _onEnterFrame: function() { - for(var n = 0; n < this.tweens.length; n++) { - this.tweens[n].onEnterFrame(); - } - }, - _add: function(tween) { - this.tweens.push(tween); - }, - _getTween: function(attrs, prop, val, obj, rootObj) { - var config = this.config; - var node = this.node; - var easing = config.easing; - if(easing === undefined) { - easing = 'linear'; - } - - var tween = new Kinetic.Tween(node, function(i) { - obj[prop] = i; - node.setAttrs(rootObj); - }, Kinetic.Tweens[easing], attrs[prop], val, config.duration); - - return tween; - } -}; - -/** - * transition node to another state. Any property that can accept a real - * number can be transitioned, including x, y, rotation, opacity, strokeWidth, - * radius, scale.x, scale.y, offset.x, offset.y, etc. - * @name transitionTo - * @methodOf Kinetic.Node.prototype - * @param {Object} config - * @config {Number} duration duration that the transition runs in seconds - * @config {String} [easing] easing function. can be linear, ease-in, ease-out, ease-in-out, - * back-ease-in, back-ease-out, back-ease-in-out, elastic-ease-in, elastic-ease-out, - * elastic-ease-in-out, bounce-ease-out, bounce-ease-in, bounce-ease-in-out, - * strong-ease-in, strong-ease-out, or strong-ease-in-out - * linear is the default - * @config {Function} [callback] callback function to be executed when - * transition completes - */ -Kinetic.Node.prototype.transitionTo = function(config) { - if(!this.transAnim) { - this.transAnim = new Kinetic.Animation(); - } - /* - * create new transition - */ - var node = this.nodeType === 'Stage' ? this : this.getLayer(); - var that = this; - var trans = new Kinetic.Transition(this, config); - - this.transAnim.func = function() { - trans._onEnterFrame(); - }; - this.transAnim.node = node; - - // subscribe to onFinished for first tween - trans.onFinished = function() { - // remove animation - that.transAnim.stop(); - - // callback - if(config.callback) { - config.callback(); - } - }; - // auto start - trans.start(); - this.transAnim.start(); - return trans; -}; - -/** - * Container constructor.  Containers are used to contain nodes or other containers - * @constructor - * @augments Kinetic.Node - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.alpha] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {String} [config.dragConstraint] can be vertical, horizontal, or none. The default - * is none - * @param {Object} [config.dragBounds] - * @param {Number} [config.dragBounds.top] - * @param {Number} [config.dragBounds.right] - * @param {Number} [config.dragBounds.bottom] - * @param {Number} [config.dragBounds.left] - */ -Kinetic.Container = function(config) { - this._containerInit(config); -}; - -Kinetic.Container.prototype = { - _containerInit: function(config) { - this.children = []; - Kinetic.Node.call(this, config); - }, - /** - * get children - * @name getChildren - * @methodOf Kinetic.Container.prototype - */ - getChildren: function() { - return this.children; - }, - /** - * remove all children - * @name removeChildren - * @methodOf Kinetic.Container.prototype - */ - removeChildren: function() { - while(this.children.length > 0) { - this.children[0].remove(); - } - }, - /** - * add node to container - * @name add - * @methodOf Kinetic.Container.prototype - * @param {Node} child - */ - add: function(child) { - child._id = Kinetic.Global.idCounter++; - child.index = this.children.length; - child.parent = this; - - this.children.push(child); - var stage = child.getStage(); - - if(!stage) { - Kinetic.Global._addTempNode(child); - } - else { - stage._addId(child); - stage._addName(child); - - /* - * pull in other nodes that are now linked - * to a stage - */ - var go = Kinetic.Global; - go._pullNodes(stage); - } - - // chainable - return this; - }, - /** - * return an array of nodes that match the selector. Use '#' for id selections - * and '.' for name selections - * ex: - * var node = stage.get('#foo'); // selects node with id foo - * var nodes = layer.get('.bar'); // selects nodes with name bar inside layer - * @name get - * @methodOf Kinetic.Container.prototype - * @param {String} selector - */ - get: function(selector) { - var collection = new Kinetic.Collection(); - // ID selector - if(selector.charAt(0) === '#') { - var node = this._getNodeById(selector.slice(1)); - if(node) - collection.push(node); - } - // name selector - else if(selector.charAt(0) === '.') { - var nodeList = this._getNodesByName(selector.slice(1)); - Kinetic.Collection.apply(collection, nodeList); - } - // unrecognized selector, pass to children - else { - var retArr = []; - var children = this.getChildren(); - for(var n = 0; n < children.length; n++) { - retArr = retArr.concat(children[n]._get(selector)); - } - Kinetic.Collection.apply(collection, retArr); - } - return collection; - }, - _getNodeById: function(key) { - var stage = this.getStage(); - if(stage.ids[key] !== undefined && this.isAncestorOf(stage.ids[key])) { - return stage.ids[key]; - } - return null; - }, - _getNodesByName: function(key) { - var arr = this.getStage().names[key] || []; - return this._getDescendants(arr); - }, - _get: function(selector) { - var retArr = Kinetic.Node.prototype._get.call(this, selector); - var children = this.getChildren(); - for(var n = 0; n < children.length; n++) { - retArr = retArr.concat(children[n]._get(selector)); - } - return retArr; - }, - // extenders - toObject: function() { - var obj = Kinetic.Node.prototype.toObject.call(this); - - obj.children = []; - - var children = this.getChildren(); - for(var n = 0; n < children.length; n++) { - var child = children[n]; - obj.children.push(child.toObject()); - } - - return obj; - }, - _getDescendants: function(arr) { - var retArr = []; - for(var n = 0; n < arr.length; n++) { - var node = arr[n]; - if(this.isAncestorOf(node)) { - retArr.push(node); - } - } - - return retArr; - }, - /** - * determine if node is an ancestor - * of descendant - * @name isAncestorOf - * @methodOf Kinetic.Container.prototype - * @param {Kinetic.Node} node - */ - isAncestorOf: function(node) { - var parent = node.getParent(); - while(parent) { - if(parent._id === this._id) { - return true; - } - parent = parent.getParent(); - } - - return false; - }, - /** - * clone node - * @name clone - * @methodOf Kinetic.Container.prototype - * @param {Object} attrs override attrs - */ - clone: function(obj) { - // call super method - var node = Kinetic.Node.prototype.clone.call(this, obj) - - // perform deep clone on containers - for(var key in this.children) { - node.add(this.children[key].clone()); - } - return node; - }, - /** - * get shapes that intersect a point - * @name getIntersections - * @methodOf Kinetic.Container.prototype - * @param {Object} point - */ - getIntersections: function() { - var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments)); - var arr = []; - var shapes = this.get('Shape'); - - for(var n = 0; n < shapes.length; n++) { - var shape = shapes[n]; - if(shape.isVisible() && shape.intersects(pos)) { - arr.push(shape); - } - } - - return arr; - }, - /** - * set children indices - */ - _setChildrenIndices: function() { - for(var n = 0; n < this.children.length; n++) { - this.children[n].index = n; - } - }, - draw: function(canvas) { - if(Kinetic.Node.prototype._shouldDraw.call(this, canvas)) { - var children = this.children; - var length = children.length; - for(var n = 0; n < length; n++) { - children[n].draw(canvas); - } - } - } -}; -Kinetic.Global.extend(Kinetic.Container, Kinetic.Node); - -/** - * Stage constructor. A stage is used to contain multiple layers - * @constructor - * @augments Kinetic.Container - * @param {Object} config - * @param {String|DomElement} config.container Container id or DOM element - * @param {Number} config.width - * @param {Number} config.height - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position - */ -Kinetic.Stage = function(config) { - this._initStage(config); -}; - -Kinetic.Stage.prototype = { - _initStage: function(config) { - this.setDefaultAttrs({ - width: 400, - height: 200 - }); - - // call super constructor - Kinetic.Container.call(this, config); - - this._setStageDefaultProperties(); - this._id = Kinetic.Global.idCounter++; - this._buildDOM(); - this._bindContentEvents(); - - var go = Kinetic.Global; - go.stages.push(this); - this._addId(this); - this._addName(this); - - }, - setContainer: function(container) { - /* - * if container is a string, assume it's an id for - * a DOM element - */ - if( typeof container === 'string') { - container = document.getElementById(container); - } - this.setAttr('container', container); - }, - /** - * draw layers - * @name draw - * @methodOf Kinetic.Stage.prototype - */ - - /** - * set height - * @name setHeight - * @methodOf Kinetic.Stage.prototype - * @param {Number} height - */ - setHeight: function(height) { - Kinetic.Node.prototype.setHeight.call(this, height); - this._resizeDOM(); - }, - /** - * set width - * @name setWidth - * @methodOf Kinetic.Stage.prototype - * @param {Number} width - */ - setWidth: function(width) { - Kinetic.Node.prototype.setWidth.call(this, width); - this._resizeDOM(); - }, - /** - * clear all layers - * @name clear - * @methodOf Kinetic.Stage.prototype - */ - clear: function() { - var layers = this.children; - for(var n = 0; n < layers.length; n++) { - layers[n].clear(); - } - }, - /** - * reset stage to default state - * @name reset - * @methodOf Kinetic.Stage.prototype - */ - reset: function() { - // remove children - this.removeChildren(); - - // defaults - this._setStageDefaultProperties(); - this.setAttrs(this.defaultNodeAttrs); - }, - /** - * get mouse position for desktop apps - * @name getMousePosition - * @methodOf Kinetic.Stage.prototype - * @param {Event} evt - */ - getMousePosition: function(evt) { - return this.mousePos; - }, - /** - * get touch position for mobile apps - * @name getTouchPosition - * @methodOf Kinetic.Stage.prototype - * @param {Event} evt - */ - getTouchPosition: function(evt) { - return this.touchPos; - }, - /** - * get user position (mouse position or touch position) - * @name getUserPosition - * @methodOf Kinetic.Stage.prototype - * @param {Event} evt - */ - getUserPosition: function(evt) { - return this.getTouchPosition() || this.getMousePosition(); - }, - /** - * get stage - * @name getStage - * @methodOf Kinetic.Stage.prototype - */ - getStage: function() { - return this; - }, - /** - * get stage DOM node, which is a div element - * with the class name "kineticjs-content" - * @name getDOM - * @methodOf Kinetic.Stage.prototype - */ - getDOM: function() { - return this.content; - }, - /** - * Creates a composite data URL and requires a callback because the stage - * toDataURL method is asynchronous. If MIME type is not - * specified, then "image/png" will result. For "image/jpeg", specify a quality - * level as quality (range 0.0 - 1.0). Note that this method works - * differently from toDataURL() for other nodes because it generates an absolute dataURL - * based on what's draw onto the canvases for each layer, rather than drawing - * the current state of each node - * @name toDataURL - * @methodOf Kinetic.Stage.prototype - * @param {Object} config - * @param {Function} config.callback since the stage toDataURL() method is asynchronous, - * the data url string will be passed into the callback - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toDataURL: function(config) { - var mimeType = config && config.mimeType ? config.mimeType : null; - var quality = config && config.quality ? config.quality : null; - /* - * need to create a canvas element rather than using the buffer canvas - * because this method is asynchonous which means that other parts of the - * code could modify the buffer canvas before it's finished - */ - var width = config && config.width ? config.width : this.attrs.width; - var height = config && config.height ? config.height : this.attrs.height; - var canvas = new Kinetic.Canvas(width, height); - var context = canvas.getContext(); - var layers = this.children; - - function drawLayer(n) { - var layer = layers[n]; - var layerUrl = layer.toDataURL(); - var imageObj = new Image(); - imageObj.onload = function() { - context.drawImage(imageObj, 0, 0); - - if(n < layers.length - 1) { - drawLayer(n + 1); - } - else { - config.callback(canvas.toDataURL(mimeType, quality)); - } - }; - imageObj.src = layerUrl; - } - drawLayer(0); - }, - /** - * converts stage into an image. Since the stage toImage() method - * is asynchronous, a callback function is required - * @name toImage - * @methodOf Kinetic.Stage.prototype - * @param {Object} config - * @param {Function} callback since the toImage() method is asynchonrous, the - * resulting image object is passed into the callback function - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toImage: function(config) { - this.toDataURL({ - callback: function(dataUrl) { - Kinetic.Type._getImage(dataUrl, function(img) { - config.callback(img); - }); - } - }); - }, - /** - * get intersection object that contains shape and pixel data - * @name getIntersection - * @methodOf Kinetic.Stage.prototype - * @param {Object} pos point object - */ - getIntersection: function(pos) { - var shape; - var layers = this.getChildren(); - - /* - * traverse through layers from top to bottom and look - * for hit detection - */ - for(var n = layers.length - 1; n >= 0; n--) { - var layer = layers[n]; - if(layer.isVisible() && layer.isListening()) { - var p = layer.bufferCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data; - // this indicates that a buffer pixel may have been found - if(p[3] === 255) { - var colorKey = Kinetic.Type._rgbToHex(p[0], p[1], p[2]); - shape = Kinetic.Global.shapes[colorKey]; - return { - shape: shape, - pixel: p - }; - } - // if no shape mapped to that pixel, return pixel array - else if(p[0] > 0 || p[1] > 0 || p[2] > 0 || p[3] > 0) { - return { - pixel: p - }; - } - } - } - - return null; - }, - _getNodeById: function(key) { - return this.ids[key] || null; - }, - _getNodesByName: function(key) { - return this.names[key] || []; - }, - _resizeDOM: function() { - if(this.content) { - var width = this.attrs.width; - var height = this.attrs.height; - - // set content dimensions - this.content.style.width = width + 'px'; - this.content.style.height = height + 'px'; - - this.bufferCanvas.setSize(width, height); - // set user defined layer dimensions - var layers = this.children; - for(var n = 0; n < layers.length; n++) { - var layer = layers[n]; - layer.getCanvas().setSize(width, height); - layer.bufferCanvas.setSize(width, height); - layer.draw(); - } - } - }, - /** - * add layer to stage - * @param {Layer} layer - */ - add: function(layer) { - Kinetic.Container.prototype.add.call(this, layer); - layer.canvas.setSize(this.attrs.width, this.attrs.height); - layer.bufferCanvas.setSize(this.attrs.width, this.attrs.height); - - // draw layer and append canvas to container - layer.draw(); - this.content.appendChild(layer.canvas.element); - - // chainable - return this; - }, - _setUserPosition: function(evt) { - if(!evt) { - evt = window.event; - } - this._setMousePosition(evt); - this._setTouchPosition(evt); - }, - /** - * begin listening for events by adding event handlers - * to the container - */ - _bindContentEvents: function() { - var go = Kinetic.Global; - var that = this; - var events = ['mousedown', 'mousemove', 'mouseup', 'mouseout', 'touchstart', 'touchmove', 'touchend']; - - for(var n = 0; n < events.length; n++) { - var pubEvent = events[n]; - // induce scope - ( function() { - var event = pubEvent; - that.content.addEventListener(event, function(evt) { - that['_' + event](evt); - }, false); - }()); - } - }, - _mouseout: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - // if there's a current target shape, run mouseout handlers - var targetShape = this.targetShape; - if(targetShape && (!dd || !dd.moving)) { - targetShape._handleEvent('mouseout', evt); - targetShape._handleEvent('mouseleave', evt); - this.targetShape = null; - } - this.mousePos = undefined; - - // end drag and drop - if(dd) { - dd._endDrag(evt); - } - }, - _mousemove: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - var obj = this.getIntersection(this.getUserPosition()); - - if(obj) { - var shape = obj.shape; - if(shape) { - if((!dd || !dd.moving) && obj.pixel[3] === 255 && (!this.targetShape || this.targetShape._id !== shape._id)) { - if(this.targetShape) { - this.targetShape._handleEvent('mouseout', evt, shape); - this.targetShape._handleEvent('mouseleave', evt, shape); - } - shape._handleEvent('mouseover', evt, this.targetShape); - shape._handleEvent('mouseenter', evt, this.targetShape); - this.targetShape = shape; - } - else { - shape._handleEvent('mousemove', evt); - } - } - } - /* - * if no shape was detected, clear target shape and try - * to run mouseout from previous target shape - */ - else if(this.targetShape && (!dd || !dd.moving)) { - this.targetShape._handleEvent('mouseout', evt); - this.targetShape._handleEvent('mouseleave', evt); - this.targetShape = null; - } - - // start drag and drop - if(dd) { - dd._startDrag(evt); - } - }, - _mousedown: function(evt) { - this._setUserPosition(evt); - var obj = this.getIntersection(this.getUserPosition()); - if(obj && obj.shape) { - var shape = obj.shape; - this.clickStart = true; - shape._handleEvent('mousedown', evt); - } - - //init stage drag and drop - if(Kinetic.DD && this.attrs.draggable) { - this._initDrag(); - } - }, - _mouseup: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - var obj = this.getIntersection(this.getUserPosition()); - var that = this; - if(obj && obj.shape) { - var shape = obj.shape; - shape._handleEvent('mouseup', evt); - - // detect if click or double click occurred - if(this.clickStart) { - /* - * if dragging and dropping, don't fire click or dbl click - * event - */ - if(!dd || !dd.moving || !dd.node) { - shape._handleEvent('click', evt); - - if(this.inDoubleClickWindow) { - shape._handleEvent('dblclick', evt); - } - this.inDoubleClickWindow = true; - setTimeout(function() { - that.inDoubleClickWindow = false; - }, this.dblClickWindow); - } - } - } - this.clickStart = false; - - // end drag and drop - if(dd) { - dd._endDrag(evt); - } - }, - _touchstart: function(evt) { - this._setUserPosition(evt); - evt.preventDefault(); - var obj = this.getIntersection(this.getUserPosition()); - - if(obj && obj.shape) { - var shape = obj.shape; - this.tapStart = true; - shape._handleEvent('touchstart', evt); - } - - /* - * init stage drag and drop - */ - if(Kinetic.DD && this.attrs.draggable) { - this._initDrag(); - } - }, - _touchend: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - var obj = this.getIntersection(this.getUserPosition()); - var that = this; - if(obj && obj.shape) { - var shape = obj.shape; - shape._handleEvent('touchend', evt); - - // detect if tap or double tap occurred - if(this.tapStart) { - /* - * if dragging and dropping, don't fire tap or dbltap - * event - */ - if(!dd || !dd.moving || !dd.node) { - shape._handleEvent('tap', evt); - - if(this.inDoubleClickWindow) { - shape._handleEvent('dbltap', evt); - } - this.inDoubleClickWindow = true; - setTimeout(function() { - that.inDoubleClickWindow = false; - }, this.dblClickWindow); - } - } - } - - this.tapStart = false; - - // end drag and drop - if(dd) { - dd._endDrag(evt); - } - }, - _touchmove: function(evt) { - this._setUserPosition(evt); - var dd = Kinetic.DD; - evt.preventDefault(); - var obj = this.getIntersection(this.getUserPosition()); - if(obj && obj.shape) { - var shape = obj.shape; - shape._handleEvent('touchmove', evt); - } - - // start drag and drop - if(dd) { - dd._startDrag(evt); - } - }, - /** - * set mouse positon for desktop apps - * @param {Event} evt - */ - _setMousePosition: function(evt) { - var mouseX = evt.clientX - this._getContentPosition().left; - var mouseY = evt.clientY - this._getContentPosition().top; - this.mousePos = { - x: mouseX, - y: mouseY - }; - }, - /** - * set touch position for mobile apps - * @param {Event} evt - */ - _setTouchPosition: function(evt) { - if(evt.touches !== undefined && evt.touches.length === 1) { - // one finger - var touch = evt.touches[0]; - // Get the information for finger #1 - var touchX = touch.clientX - this._getContentPosition().left; - var touchY = touch.clientY - this._getContentPosition().top; - - this.touchPos = { - x: touchX, - y: touchY - }; - } - }, - /** - * get container position - */ - _getContentPosition: function() { - var rect = this.content.getBoundingClientRect(); - return { - top: rect.top, - left: rect.left - }; - }, - /** - * build dom - */ - _buildDOM: function() { - // content - this.content = document.createElement('div'); - this.content.style.position = 'relative'; - this.content.style.display = 'inline-block'; - this.content.className = 'kineticjs-content'; - this.attrs.container.appendChild(this.content); - - this.bufferCanvas = new Kinetic.Canvas({ - width: this.attrs.width, - height: this.attrs.height - }); - - this._resizeDOM(); - }, - _addId: function(node) { - if(node.attrs.id !== undefined) { - this.ids[node.attrs.id] = node; - } - }, - _removeId: function(id) { - if(id !== undefined) { - delete this.ids[id]; - } - }, - _addName: function(node) { - var name = node.attrs.name; - if(name !== undefined) { - if(this.names[name] === undefined) { - this.names[name] = []; - } - this.names[name].push(node); - } - }, - _removeName: function(name, _id) { - if(name !== undefined) { - var nodes = this.names[name]; - if(nodes !== undefined) { - for(var n = 0; n < nodes.length; n++) { - var no = nodes[n]; - if(no._id === _id) { - nodes.splice(n, 1); - } - } - if(nodes.length === 0) { - delete this.names[name]; - } - } - } - }, - /** - * bind event listener to container DOM element - * @param {String} typesStr - * @param {function} handler - */ - _onContent: function(typesStr, handler) { - var types = typesStr.split(' '); - for(var n = 0; n < types.length; n++) { - var baseEvent = types[n]; - this.content.addEventListener(baseEvent, handler, false); - } - }, - /** - * set defaults - */ - _setStageDefaultProperties: function() { - this.nodeType = 'Stage'; - this.dblClickWindow = 400; - this.targetShape = null; - this.mousePos = undefined; - this.clickStart = false; - this.touchPos = undefined; - this.tapStart = false; - - /* - * ids and names hash needs to be stored at the stage level to prevent - * id and name collisions between multiple stages in the document - */ - this.ids = {}; - this.names = {}; - } -}; -Kinetic.Global.extend(Kinetic.Stage, Kinetic.Container); - -// add getters and setters -Kinetic.Node.addGetters(Kinetic.Stage, ['container']); - -/** - * get container DOM element - * @name getContainer - * @methodOf Kinetic.Stage.prototype - */ -/** - * Layer constructor. Layers are tied to their own canvas element and are used - * to contain groups or shapes - * @constructor - * @augments Kinetic.Container - * @param {Object} config - * @param {Boolean} [config.clearBeforeDraw] set this property to true if you'd like to disable - * canvas clearing before each new layer draw - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position - */ -Kinetic.Layer = function(config) { - this._initLayer(config); -}; - -Kinetic.Layer.prototype = { - _initLayer: function(config) { - this.setDefaultAttrs({ - clearBeforeDraw: true - }); - - this.nodeType = 'Layer'; - this.beforeDrawFunc = undefined; - this.afterDrawFunc = undefined; - this.canvas = new Kinetic.Canvas(); - this.canvas.getElement().style.position = 'absolute'; - this.bufferCanvas = new Kinetic.Canvas(); - this.bufferCanvas.name = 'buffer'; - - // call super constructor - Kinetic.Container.call(this, config); - }, - /** - * draw children nodes. this includes any groups - * or shapes - * @name draw - * @methodOf Kinetic.Layer.prototype - */ - draw: function(canvas) { - // before draw handler - if(this.beforeDrawFunc !== undefined) { - this.beforeDrawFunc.call(this); - } - - var canvases = []; - if(canvas) { - canvases.push(canvas); - } - else { - canvases.push(this.getCanvas()); - canvases.push(this.bufferCanvas); - } - - var length = canvases.length; - for(var n = 0; n < length; n++) { - var canvas = canvases[n]; - if(Kinetic.Node.prototype._shouldDraw.call(this, canvas)) { - if(this.attrs.clearBeforeDraw) { - canvas.clear(); - } - Kinetic.Container.prototype.draw.call(this, canvas); - } - } - - // after draw handler - if(this.afterDrawFunc !== undefined) { - this.afterDrawFunc.call(this); - } - }, - /** - * draw children nodes on buffer. this includes any groups - * or shapes - * @name drawBuffer - * @methodOf Kinetic.Layer.prototype - */ - drawBuffer: function() { - this.draw(this.bufferCanvas); - }, - /** - * draw children nodes on scene. this includes any groups - * or shapes - * @name drawScene - * @methodOf Kinetic.Layer.prototype - */ - drawScene: function() { - this.draw(this.getCanvas()); - }, - /** - * set before draw handler - * @name beforeDraw - * @methodOf Kinetic.Layer.prototype - * @param {Function} handler - */ - beforeDraw: function(func) { - this.beforeDrawFunc = func; - }, - /** - * set after draw handler - * @name afterDraw - * @methodOf Kinetic.Layer.prototype - * @param {Function} handler - */ - afterDraw: function(func) { - this.afterDrawFunc = func; - }, - /** - * get layer canvas - * @name getCanvas - * @methodOf Kinetic.Layer.prototype - */ - getCanvas: function() { - return this.canvas; - }, - /** - * get layer canvas context - * @name getContext - * @methodOf Kinetic.Layer.prototype - */ - getContext: function() { - return this.canvas.context; - }, - /** - * clear canvas tied to the layer - * @name clear - * @methodOf Kinetic.Layer.prototype - */ - clear: function() { - this.getCanvas().clear(); - }, - // extenders - setVisible: function(visible) { - Kinetic.Node.prototype.setVisible.call(this, visible); - if(visible) { - this.canvas.element.style.display = 'block'; - this.bufferCanvas.element.style.display = 'block'; - } - else { - this.canvas.element.style.display = 'none'; - this.bufferCanvas.element.style.display = 'none'; - } - }, - setZIndex: function(index) { - Kinetic.Node.prototype.setZIndex.call(this, index); - var stage = this.getStage(); - if(stage) { - stage.content.removeChild(this.canvas.element); - - if(index < stage.getChildren().length - 1) { - stage.content.insertBefore(this.canvas.element, stage.getChildren()[index + 1].canvas.element); - } - else { - stage.content.appendChild(this.canvas.element); - } - } - }, - moveToTop: function() { - Kinetic.Node.prototype.moveToTop.call(this); - var stage = this.getStage(); - if(stage) { - stage.content.removeChild(this.canvas.element); - stage.content.appendChild(this.canvas.element); - } - }, - moveUp: function() { - if(Kinetic.Node.prototype.moveUp.call(this)) { - var stage = this.getStage(); - if(stage) { - stage.content.removeChild(this.canvas.element); - - if(this.index < stage.getChildren().length - 1) { - stage.content.insertBefore(this.canvas.element, stage.getChildren()[this.index + 1].canvas.element); - } - else { - stage.content.appendChild(this.canvas.element); - } - } - } - }, - moveDown: function() { - if(Kinetic.Node.prototype.moveDown.call(this)) { - var stage = this.getStage(); - if(stage) { - var children = stage.getChildren(); - stage.content.removeChild(this.canvas.element); - stage.content.insertBefore(this.canvas.element, children[this.index + 1].canvas.element); - } - } - }, - moveToBottom: function() { - if(Kinetic.Node.prototype.moveToBottom.call(this)) { - var stage = this.getStage(); - if(stage) { - var children = stage.getChildren(); - stage.content.removeChild(this.canvas.element); - stage.content.insertBefore(this.canvas.element, children[1].canvas.element); - } - } - }, - getLayer: function() { - return this; - }, - /** - * Creates a composite data URL. If MIME type is not - * specified, then "image/png" will result. For "image/jpeg", specify a quality - * level as quality (range 0.0 - 1.0). Note that this method works - * differently from toDataURL() for other nodes because it generates an absolute dataURL - * based on what's currently drawn on the layer, rather than drawing - * the current state of each child node - * @name toDataURL - * @methodOf Kinetic.Layer.prototype - * @param {Object} config - * @param {String} [config.mimeType] mime type. can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.width] data url image width - * @param {Number} [config.height] data url image height - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toDataURL: function(config) { - var canvas; - var mimeType = config && config.mimeType ? config.mimeType : null; - var quality = config && config.quality ? config.quality : null; - - /* - * if layer is hidden, return blank canvas - * else if width and height are defined, create blank canvas and draw onto it - * else return canvas as is - */ - if(!this.isVisible()) { - var stage = this.getStage(); - canvas = new Kinetic.Canvas(stage.getWidth(), stage.getHeight()); - } - else if(config && config.width && config.height) { - canvas = new Kinetic.Canvas(config.width, config.height); - this.draw(canvas); - } - else { - canvas = this.getCanvas(); - } - return canvas.toDataURL(mimeType, quality); - }, - /** - * remove layer from stage - */ - remove: function() { - Kinetic.Node.prototype.remove.call(this); - /* - * remove canvas DOM from the document if - * it exists - */ - try { - this.getStage().content.removeChild(this.canvas.element); - } catch(e) { - Kinetic.Global.warn('unable to remove layer scene canvas element from the document'); - } - } -}; -Kinetic.Global.extend(Kinetic.Layer, Kinetic.Container); - -// add getters and setters -Kinetic.Node.addGettersSetters(Kinetic.Layer, ['clearBeforeDraw']); - -/** - * set flag which determines if the layer is cleared or not - * before drawing - * @name setClearBeforeDraw - * @methodOf Kinetic.Layer.prototype - * @param {Boolean} clearBeforeDraw - */ - -/** - * get flag which determines if the layer is cleared or not - * before drawing - * @name getClearBeforeDraw - * @methodOf Kinetic.Layer.prototype - */ -/** - * Group constructor. Groups are used to contain shapes or other groups. - * @constructor - * @augments Kinetic.Container - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {Function} [config.dragBoundFunc] dragBoundFunc(pos, evt) should return new position - */ -Kinetic.Group = function(config) { - this._initGroup(config); -}; - -Kinetic.Group.prototype = { - _initGroup: function(config) { - this.nodeType = 'Group'; - - // call super constructor - Kinetic.Container.call(this, config); - } -}; -Kinetic.Global.extend(Kinetic.Group, Kinetic.Container); - -/** - * Shape constructor. Shapes are primitive objects such as rectangles, - * circles, text, lines, etc. - * @constructor - * @augments Kinetic.Node - * @param {Object} config - * @config {String|Object} [config.fill] can be a string color, a linear gradient object, a radial - * gradient object, or a pattern object. - * @config {Image} [config.fill.image] image object if filling the shape with a pattern - * @config {Object} [config.fill.offset] pattern offset if filling the shape with a pattern - * @config {Number} [config.fill.offset.x] - * @config {Number} [config.fill.offset.y] - * @config {Object} [config.fill.start] start point if using a linear gradient or - * radial gradient fill - * @config {Number} [config.fill.start.x] - * @config {Number} [config.fill.start.y] - * @config {Number} [config.fill.start.radius] start radius if using a radial gradient fill - * @config {Object} [config.fill.end] end point if using a linear gradient or - * radial gradient fill - * @config {Number} [config.fill.end.x] - * @config {Number} [config.fill.end.y] - * @config {Number} [config.fill.end.radius] end radius if using a radial gradient fill - * @config {String} [config.stroke] stroke color - * @config {Number} [config.strokeWidth] stroke width - * @config {String} [config.lineJoin] line join can be miter, round, or bevel. The default - * is miter - * @config {Object} [config.shadow] shadow object - * @config {String} [config.shadow.color] - * @config {Number} [config.shadow.blur] - * @config {Obect} [config.shadow.blur.offset] - * @config {Number} [config.shadow.blur.offset.x] - * @config {Number} [config.shadow.blur.offset.y] - * @config {Number} [config.shadow.opacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] - * @param {Number} [config.scale.x] - * @param {Number} [config.scale.y] - * @param {Number} [config.rotation] rotation in radians - * @param {Number} [config.rotationDeg] rotation in degrees - * @param {Object} [config.offset] offsets default position point and rotation point - * @param {Number} [config.offset.x] - * @param {Number} [config.offset.y] - * @param {Boolean} [config.draggable] - * @param {String} [config.dragConstraint] can be vertical, horizontal, or none. The default - * is none - * @param {Object} [config.dragBounds] - * @param {Number} [config.dragBounds.top] - * @param {Number} [config.dragBounds.right] - * @param {Number} [config.dragBounds.bottom] - * @param {Number} [config.dragBounds.left] - */ -Kinetic.Shape = function(config) { - this._initShape(config); -}; - -Kinetic.Shape.prototype = { - _initShape: function(config) { - this.nodeType = 'Shape'; - this.appliedShadow = false; - - // set colorKey - var shapes = Kinetic.Global.shapes; - var key; - while(true) { - key = Kinetic.Type._getRandomColorKey(); - if(key && !( key in shapes)) { - break; - } - } - this.colorKey = key; - shapes[key] = this; - - // call super constructor - Kinetic.Node.call(this, config); - }, - /** - * get canvas context tied to the layer - * @name getContext - * @methodOf Kinetic.Shape.prototype - */ - getContext: function() { - return this.getLayer().getContext(); - }, - /** - * get canvas tied to the layer - * @name getCanvas - * @methodOf Kinetic.Shape.prototype - */ - getCanvas: function() { - return this.getLayer().getCanvas(); - }, - /** - * helper method to stroke the shape and apply - * shadows if needed - * @name stroke - * @methodOf Kinetic.Shape.prototype - */ - stroke: function(context) { - var strokeWidth = this.getStrokeWidth(); - var stroke = this.getStroke(); - if(stroke || strokeWidth) { - var go = Kinetic.Global; - var appliedShadow = false; - - context.save(); - if(this.attrs.shadow && !this.appliedShadow) { - appliedShadow = this._applyShadow(context); - } - - context.lineWidth = strokeWidth || 2; - context.strokeStyle = stroke || 'black'; - context.stroke(context); - context.restore(); - - if(appliedShadow) { - this.stroke(context); - } - } - }, - _getFillType: function(fill) { - if(!fill) { - return undefined; - } - else if(Kinetic.Type._isString(fill)) { - return 'COLOR'; - } - else if(fill.image) { - return 'PATTERN'; - } - else if(fill.start && fill.end && !fill.start.radius && !fill.end.radius) { - return 'LINEAR_GRADIENT'; - } - else if(fill.start && fill.end && Kinetic.Type._isNumber(fill.start.radius) && Kinetic.Type._isNumber(fill.end.radius)) { - return 'RADIAL_GRADIENT'; - } - else { - return 'UNKNOWN'; - } - }, - /** - * helper method to fill the shape with a color, linear gradient, - * radial gradient, or pattern, and also apply shadows if needed - * @name fill - * @methodOf Kinetic.Shape.prototype - * */ - fill: function(context) { - var appliedShadow = false; - var fill = this.getFill(); - var fillType = this._getFillType(fill); - if(fill) { - context.save(); - if(this.attrs.shadow && !this.appliedShadow) { - appliedShadow = this._applyShadow(context); - } - - var s = fill.start; - var e = fill.end; - - // color fill - switch(fillType) { - case 'COLOR': - context.fillStyle = fill; - context.fill(context); - break; - case 'PATTERN': - var repeat = !fill.repeat ? 'repeat' : fill.repeat; - if(fill.scale) { - context.scale(fill.scale.x, fill.scale.y); - } - if(fill.offset) { - context.translate(fill.offset.x, fill.offset.y); - } - - context.fillStyle = context.createPattern(fill.image, repeat); - context.fill(context); - break; - case 'LINEAR_GRADIENT': - var grd = context.createLinearGradient(s.x, s.y, e.x, e.y); - var colorStops = fill.colorStops; - - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - context.fillStyle = grd; - context.fill(context); - - break; - case 'RADIAL_GRADIENT': - var grd = context.createRadialGradient(s.x, s.y, s.radius, e.x, e.y, e.radius); - var colorStops = fill.colorStops; - - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - context.fillStyle = grd; - context.fill(context); - break; - default: - context.fillStyle = 'black'; - context.fill(context); - break; - } - context.restore(); - } - - if(appliedShadow) { - this.fill(context); - } - }, - /** - * helper method to fill text and appy shadows if needed - * @param {String} text - * @name fillText - * @methodOf Kinetic.Shape.prototype - */ - fillText: function(context, text) { - var appliedShadow = false; - if(this.attrs.textFill) { - context.save(); - if(this.attrs.shadow && !this.appliedShadow) { - appliedShadow = this._applyShadow(context); - } - context.fillStyle = this.attrs.textFill; - context.fillText(text, 0, 0); - context.restore(); - } - if(appliedShadow) { - this.fillText(context, text, 0, 0); - } - }, - /** - * helper method to stroke text and apply shadows - * if needed - * @name strokeText - * @methodOf Kinetic.Shape.prototype - * @param {String} text - */ - strokeText: function(context, text) { - var appliedShadow = false; - - if(this.attrs.textStroke || this.attrs.textStrokeWidth) { - context.save(); - if(this.attrs.shadow && !this.appliedShadow) { - appliedShadow = this._applyShadow(context); - } - // defaults - var textStroke = this.attrs.textStroke ? this.attrs.textStroke : 'black'; - var textStrokeWidth = this.attrs.textStrokeWidth ? this.attrs.textStrokeWidth : 2; - context.lineWidth = textStrokeWidth; - context.strokeStyle = textStroke; - context.strokeText(text, 0, 0); - context.restore(); - } - - if(appliedShadow) { - this.strokeText(context, text, 0, 0); - } - }, - /** - * helper method to draw an image and apply - * a shadow if neede - * @name drawImage - * @methodOf Kinetic.Shape.prototype - */ - drawImage: function() { - var appliedShadow = false; - var context = arguments[0]; - context.save(); - var a = Array.prototype.slice.call(arguments); - - if(a.length === 6 || a.length === 10) { - if(this.attrs.shadow && !this.appliedShadow) { - appliedShadow = this._applyShadow(context); - } - - if(a.length === 6) { - context.drawImage(a[1], a[2], a[3], a[4], a[5]); - } - else { - context.drawImage(a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); - } - } - - context.restore(); - - if(appliedShadow) { - this.drawImage.apply(this, a); - } - }, - applyOpacity: function(context) { - var absOpacity = this.getAbsoluteOpacity(); - if(absOpacity !== 1) { - context.globalAlpha = absOpacity; - } - }, - /** - * helper method to set the line join of a shape - * based on the applyLineJoin property - * @name lineJoin - * @methodOf Kinetic.Shape.prototype - */ - applyLineJoin: function(context) { - if(this.attrs.lineJoin) { - context.lineJoin = this.attrs.lineJoin; - } - }, - /** - * helper method to set the line cap of a path - * based on the lineCap property - * @name applyLineCap - * @methodOf Kinetic.Shape.prototype - */ - applyLineCap: function(context) { - if(this.attrs.lineCap) { - context.lineCap = this.attrs.lineCap; - } - }, - /** - * set shadow object - * @name setShadow - * @methodOf Kinetic.Shape.prototype - * @param {Object} config - * @param {String} config.color - * @param {Number} config.blur - * @param {Array|Object|Number} config.offset - * @param {Number} config.opacity - */ - setShadow: function(config) { - if(config.offset !== undefined) { - config.offset = Kinetic.Type._getXY(config.offset); - } - this.setAttr('shadow', Kinetic.Type._merge(config, this.getShadow())); - }, - /** - * set fill which can be a color, linear gradient object, - * radial gradient object, or pattern object - * @name setFill - * @methodOf Kinetic.Shape.prototype - * @param {String|Object} fill - */ - setFill: function(fill) { - var oldFill = this.getFill(); - var fillType = this._getFillType(fill); - var oldFillType = this._getFillType(oldFill); - var newOrOldFillIsColor = fillType === 'COLOR' || oldFillType === 'COLOR'; - var changedFillType = fillType === oldFillType || fillType === 'UNKNOWN'; - - // if fill.offset is defined, normalize the xy value - if(fill.offset !== undefined) { - fill.offset = Kinetic.Type._getXY(fill.offset); - } - - /* - * merge fill objects if neither the new or old fill - * is type is COLOR, and if if the fill type has not changed. Otherwise, - * overwrite the fill entirely - */ - if(!newOrOldFillIsColor && changedFillType) { - fill = Kinetic.Type._merge(fill, oldFill); - } - - this.setAttr('fill', fill); - }, - /** - * set width and height - * @name setSize - * @methodOf Kinetic.Shape.prototype - */ - setSize: function() { - var size = Kinetic.Type._getSize(Array.prototype.slice.call(arguments)); - this.setWidth(size.width); - this.setHeight(size.height); - }, - /** - * return shape size - * @name getSize - * @methodOf Kinetic.Shape.prototype - */ - getSize: function() { - return { - width: this.getWidth(), - height: this.getHeight() - }; - }, - _get: function(selector) { - return this.nodeType === selector || this.shapeType === selector ? [this] : []; - }, - /** - * apply shadow. return true if shadow was applied - * and false if it was not - */ - _applyShadow: function(context) { - var s = this.attrs.shadow; - if(s) { - var aa = this.getAbsoluteOpacity(); - // defaults - var color = s.color ? s.color : 'black'; - var blur = s.blur ? s.blur : 5; - var offset = s.offset ? s.offset : { - x: 0, - y: 0 - }; - - if(s.opacity) { - context.globalAlpha = s.opacity * aa; - } - context.shadowColor = color; - context.shadowBlur = blur; - context.shadowOffsetX = offset.x; - context.shadowOffsetY = offset.y; - this.appliedShadow = true; - return true; - } - - return false; - }, - /** - * determines if point is in the shape - * @param {Object|Array} point point can be an object containing - * an x and y property, or it can be an array with two elements - * in which the first element is the x component and the second - * element is the y component - */ - intersects: function() { - var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments)); - var stage = this.getStage(); - var bufferCanvas = stage.bufferCanvas; - bufferCanvas.clear(); - this.draw(bufferCanvas); - var p = bufferCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data; - return p[3] > 0; - }, - remove: function() { - Kinetic.Node.prototype.remove.call(this); - delete Kinetic.Global.shapes[this.colorKey]; - }, - draw: function(canvas) { - if(this.attrs.drawFunc && Kinetic.Node.prototype._shouldDraw.call(this, canvas)) { - var stage = this.getStage(); - var context = canvas.getContext(); - var family = []; - var parent = this.parent; - - family.unshift(this); - while(parent) { - family.unshift(parent); - parent = parent.parent; - } - - context.save(); - for(var n = 0; n < family.length; n++) { - var node = family[n]; - var t = node.getTransform(); - var m = t.getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - - this.applyOpacity(context); - this.applyLineJoin(context); - this.applyLineCap(context); - - // draw the shape - this.appliedShadow = false; - - var wl = Kinetic.Global.BUFFER_WHITELIST; - var bl = Kinetic.Global.BUFFER_BLACKLIST; - var attrs = {}; - - if(canvas.name === 'buffer') { - for(var n = 0; n < wl.length; n++) { - var key = wl[n]; - attrs[key] = this.attrs[key]; - if(this.attrs[key] || (key === 'fill' && !this.attrs.stroke && !('image' in this.attrs))) { - this.attrs[key] = '#' + this.colorKey; - } - } - - for(var n = 0; n < bl.length; n++) { - var key = bl[n]; - attrs[key] = this.attrs[key]; - this.attrs[key] = ''; - } - - // image is a special case - if('image' in this.attrs) { - attrs.image = this.attrs.image; - - if(this.imageBuffer) { - this.attrs.image = this.imageBuffer; - } - else { - this.attrs.image = null; - this.attrs.fill = '#' + this.colorKey; - } - } - - context.globalAlpha = 1; - } - - this.attrs.drawFunc.call(this, canvas.getContext()); - - if(canvas.name === 'buffer') { - var bothLists = wl.concat(bl); - for(var n = 0; n < bothLists.length; n++) { - var key = bothLists[n]; - this.attrs[key] = attrs[key]; - } - - // image is a special case - this.attrs.image = attrs.image; - } - - context.restore(); - } - } -}; -Kinetic.Global.extend(Kinetic.Shape, Kinetic.Node); - -// add getters and setters -Kinetic.Node.addGettersSetters(Kinetic.Shape, ['stroke', 'lineJoin', 'strokeWidth', 'drawFunc', 'cornerRadius']); -Kinetic.Node.addGetters(Kinetic.Shape, ['shadow', 'fill']); - -/** - * set stroke color - * @name setStroke - * @methodOf Kinetic.Shape.prototype - * @param {String} stroke - */ - -/** - * set line join - * @name setLineJoin - * @methodOf Kinetic.Shape.prototype - * @param {String} lineJoin. Can be miter, round, or bevel. The - * default is miter - */ - -/** - * set stroke width - * @name setStrokeWidth - * @methodOf Kinetic.Shape.prototype - * @param {Number} strokeWidth - */ - -/** - * set draw function - * @name setDrawFunc - * @methodOf Kinetic.Shape.prototype - * @param {Function} drawFunc drawing function - */ - -/** - * set corner radius - * @name setCornerRadius - * @methodOf Kinetic.Shape.prototype - * @param {Number} corner radius - */ - -/** - * set line cap. Can be butt, round, or square - * @name setLineCap - * @methodOf Kinetic.Shape.prototype - * @param {String} lineCap - */ - -/** - * get stroke color - * @name getStroke - * @methodOf Kinetic.Shape.prototype - */ - -/** - * get line join - * @name getLineJoin - * @methodOf Kinetic.Shape.prototype - */ - -/** - * get stroke width - * @name getStrokeWidth - * @methodOf Kinetic.Shape.prototype - */ - -/** - * get corner radius - * @name getCornerRadius - * @methodOf Kinetic.Shape.prototype - */ - -/** - * get draw function - * @name getDrawFunc - * @methodOf Kinetic.Shape.prototype - */ - -/** - * get shadow object - * @name getShadow - * @methodOf Kinetic.Shape.prototype - */ - -/** - * get fill - * @name getFill - * @methodOf Kinetic.Shape.prototype - */ - -/** - * get line cap - * @name getLineCap - * @methodOf Kinetic.Shape.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Rect -/////////////////////////////////////////////////////////////////////// -/** - * Rect constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Rect = function(config) { - this._initRect(config); -} -Kinetic.Rect.prototype = { - _initRect: function(config) { - this.setDefaultAttrs({ - width: 0, - height: 0, - cornerRadius: 0 - }); - this.shapeType = "Rect"; - config.drawFunc = this.drawFunc; - - Kinetic.Shape.call(this, config); - }, - drawFunc: function(context) { - context.beginPath(); - if(this.attrs.cornerRadius === 0) { - // simple rect - don't bother doing all that complicated maths stuff. - context.rect(0, 0, this.attrs.width, this.attrs.height); - } - else { - // arcTo would be nicer, but browser support is patchy (Opera) - context.moveTo(this.attrs.cornerRadius, 0); - context.lineTo(this.attrs.width - this.attrs.cornerRadius, 0); - context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false); - context.lineTo(this.attrs.width, this.attrs.height - this.attrs.cornerRadius); - context.arc(this.attrs.width - this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false); - context.lineTo(this.attrs.cornerRadius, this.attrs.height); - context.arc(this.attrs.cornerRadius, this.attrs.height - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false); - context.lineTo(0, this.attrs.cornerRadius); - context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false); - } - context.closePath(); - - this.fill(context); - this.stroke(context); - } -}; - -Kinetic.Global.extend(Kinetic.Rect, Kinetic.Shape); - -/////////////////////////////////////////////////////////////////////// -// Circle -/////////////////////////////////////////////////////////////////////// -/** - * Circle constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Circle = function(config) { - this._initCircle(config); -}; - -Kinetic.Circle.prototype = { - _initCircle: function(config) { - this.setDefaultAttrs({ - radius: 0 - }); - - this.shapeType = "Circle"; - config.drawFunc = this.drawFunc; - - // call super constructor - Kinetic.Shape.call(this, config); - }, - drawFunc: function(context) { - context.beginPath(); - context.arc(0, 0, this.getRadius(), 0, Math.PI * 2, true); - context.closePath(); - this.fill(context); - this.stroke(context); - }, - getWidth: function() { - return this.getRadius() * 2; - }, - getHeight: function() { - return this.getRadius() * 2; - }, - setWidth: function(width) { - Kinetic.Node.prototype.setWidth.call(this, width); - this.setRadius(width / 2); - }, - setHeight: function(height) { - Kinetic.Node.prototype.setHeight.call(this, height); - this.setRadius(height / 2); - } -}; -Kinetic.Global.extend(Kinetic.Circle, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Circle, ['radius']); - -/** - * set radius - * @name setRadius - * @methodOf Kinetic.Circle.prototype - * @param {Number} radius - */ - -/** - * get radius - * @name getRadius - * @methodOf Kinetic.Circle.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Ellipse -/////////////////////////////////////////////////////////////////////// -/** - * Ellipse constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Ellipse = function(config) { - this._initEllipse(config); -}; - -Kinetic.Ellipse.prototype = { - _initEllipse: function(config) { - this.setDefaultAttrs({ - radius: { - x: 0, - y: 0 - } - }); - - this.shapeType = "Ellipse"; - config.drawFunc = this.drawFunc; - - // call super constructor - Kinetic.Shape.call(this, config); - }, - drawFunc: function(context) { - var r = this.getRadius(); - context.beginPath(); - context.save(); - if(r.x !== r.y) { - context.scale(1, r.y / r.x); - } - context.arc(0, 0, r.x, 0, Math.PI * 2, true); - context.restore(); - context.closePath(); - this.fill(context); - this.stroke(context); - }, - /** - * set radius - * @name setRadius - * @methodOf Kinetic.Ellipse.prototype - * @param {Object|Array} radius - * radius can be a number, in which the ellipse becomes a circle, - * it can be an object with an x and y component, or it - * can be an array in which the first element is the x component - * and the second element is the y component. The x component - * defines the horizontal radius and the y component - * defines the vertical radius - */ - setRadius: function() { - var pos = Kinetic.Type._getXY([].slice.call(arguments)); - this.setAttr('radius', Kinetic.Type._merge(pos, this.getRadius())); - }, - getWidth: function() { - return this.getRadius().x * 2; - }, - getHeight: function() { - return this.getRadius().y * 2; - }, - setWidth: function(width) { - Kinetic.Node.prototype.setWidth.call(this, width); - this.setRadius({ - x: width / 2 - }); - }, - setHeight: function(height) { - Kinetic.Node.prototype.setHeight.call(this, height); - this.setRadius({ - y: height / 2 - }); - } -}; -Kinetic.Global.extend(Kinetic.Ellipse, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGetters(Kinetic.Ellipse, ['radius']); - -/** - * get radius - * @name getRadius - * @methodOf Kinetic.Ellipse.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Image -/////////////////////////////////////////////////////////////////////// -/** - * Image constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - * @param {ImageObject} config.image - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Object} [config.crop] - */ -Kinetic.Image = function(config) { - this._initImage(config); -}; - -Kinetic.Image.prototype = { - _initImage: function(config) { - this.shapeType = "Image"; - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - - var that = this; - this.on('imageChange', function(evt) { - that._syncSize(); - }); - - this._syncSize(); - }, - drawFunc: function(context) { - var width = this.getWidth(); - var height = this.getHeight(); - - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - this.fill(context); - this.stroke(context); - - if(this.attrs.image) { - // if cropping - if(this.attrs.crop && this.attrs.crop.width && this.attrs.crop.height) { - var cropX = this.attrs.crop.x ? this.attrs.crop.x : 0; - var cropY = this.attrs.crop.y ? this.attrs.crop.y : 0; - var cropWidth = this.attrs.crop.width; - var cropHeight = this.attrs.crop.height; - this.drawImage(context, this.attrs.image, cropX, cropY, cropWidth, cropHeight, 0, 0, width, height); - } - // no cropping - else { - this.drawImage(context, this.attrs.image, 0, 0, width, height); - } - } - }, - /** - * apply filter - * @name applyFilter - * @methodOf Kinetic.Image.prototype - * @param {Object} config - * @param {Function} config.filter filter function - * @param {Function} [config.callback] callback function to be called once - * filter has been applied - */ - applyFilter: function(config) { - var canvas = new Kinetic.Canvas(this.attrs.image.width, this.attrs.image.height); - var context = canvas.getContext(); - context.drawImage(this.attrs.image, 0, 0); - try { - var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight()); - config.filter(imageData, config); - var that = this; - Kinetic.Type._getImage(imageData, function(imageObj) { - that.setImage(imageObj); - - if(config.callback) { - config.callback(); - } - }); - } catch(e) { - Kinetic.Global.warn('Unable to apply filter.'); - } - }, - /** - * set crop - * @name setCrop - * @methodOf Kinetic.Image.prototype - * @param {Object|Array} config - * @param {Number} config.x - * @param {Number} config.y - * @param {Number} config.width - * @param {Number} config.height - */ - setCrop: function() { - var config = [].slice.call(arguments); - var pos = Kinetic.Type._getXY(config); - var size = Kinetic.Type._getSize(config); - var both = Kinetic.Type._merge(pos, size); - this.setAttr('crop', Kinetic.Type._merge(both, this.getCrop())); - }, - /** - * create image buffer which enables more accurate hit detection mapping of the image - * by avoiding event detections for transparent pixels - * @name createImageBuffer - * @methodOf Kinetic.Image.prototype - * @param {Function} [callback] callback function to be called once - * the buffer image has been created and set - */ - createImageBuffer: function(callback) { - var canvas = new Kinetic.Canvas(this.attrs.width, this.attrs.height); - var context = canvas.getContext(); - context.drawImage(this.attrs.image, 0, 0); - try { - var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight()); - var data = imageData.data; - var rgbColorKey = Kinetic.Type._hexToRgb(this.colorKey); - // replace non transparent pixels with color key - for(var i = 0, n = data.length; i < n; i += 4) { - data[i] = rgbColorKey.r; - data[i + 1] = rgbColorKey.g; - data[i + 2] = rgbColorKey.b; - // i+3 is alpha (the fourth element) - } - - var that = this; - Kinetic.Type._getImage(imageData, function(imageObj) { - that.imageBuffer = imageObj; - if(callback) { - callback(); - } - }); - } catch(e) { - Kinetic.Global.warn('Unable to create image buffer.'); - } - }, - /** - * clear buffer image - * @name clearImageBuffer - * @methodOf Kinetic.Image.prototype - */ - clearImageBuffer: function() { - delete this.imageBuffer; - }, - _syncSize: function() { - if(this.attrs.image) { - if(!this.attrs.width) { - this.setWidth(this.attrs.image.width); - } - if(!this.attrs.height) { - this.setHeight(this.attrs.image.height); - } - } - } -}; -Kinetic.Global.extend(Kinetic.Image, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Image, ['image']); -Kinetic.Node.addGetters(Kinetic.Image, ['crop']); - -/** - * set image - * @name setImage - * @methodOf Kinetic.Image.prototype - * @param {ImageObject} image - */ - -/** - * get crop - * @name getCrop - * @methodOf Kinetic.Image.prototype - */ - -/** - * get image - * @name getImage - * @methodOf Kinetic.Image.prototype - */ - -/////////////////////////////////////////////////////////////////////// -// Polygon -/////////////////////////////////////////////////////////////////////// -/** - * Polygon constructor.  Polygons are defined by an array of points - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Polygon = function(config) { - this._initPolygon(config); -}; - -Kinetic.Polygon.prototype = { - _initPolygon: function(config) { - this.setDefaultAttrs({ - points: [] - }); - - this.shapeType = "Polygon"; - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - }, - drawFunc: function(context) { - context.beginPath(); - context.moveTo(this.attrs.points[0].x, this.attrs.points[0].y); - for(var n = 1; n < this.attrs.points.length; n++) { - context.lineTo(this.attrs.points[n].x, this.attrs.points[n].y); - } - context.closePath(); - this.fill(context); - this.stroke(context); - }, - /** - * set points array - * @name setPoints - * @methodOf Kinetic.Line.prototype - * @param {Array} can be an array of point objects or an array - * of Numbers. e.g. [{x:1,y:2},{x:3,y:4}] or [1,2,3,4] - */ - setPoints: function(val) { - this.setAttr('points', Kinetic.Type._getPoints(val)); - } -}; -Kinetic.Global.extend(Kinetic.Polygon, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGetters(Kinetic.Polygon, ['points']); - -/** - * get points array - * @name getPoints - * @methodOf Kinetic.Polygon.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Text -/////////////////////////////////////////////////////////////////////// -/** - * Text constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Text = function(config) { - this._initText(config); -}; - -Kinetic.Text.prototype = { - _initText: function(config) { - this.setDefaultAttrs({ - fontFamily: 'Calibri', - text: '', - fontSize: 12, - align: 'left', - verticalAlign: 'top', - fontStyle: 'normal', - padding: 0, - width: 'auto', - height: 'auto', - detectionType: 'path', - cornerRadius: 0, - lineHeight: 1.2 - }); - - this.dummyCanvas = document.createElement('canvas'); - this.shapeType = "Text"; - - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - - // update text data for certain attr changes - var attrs = ['fontFamily', 'fontSize', 'fontStyle', 'padding', 'align', 'lineHeight', 'text', 'width', 'height']; - var that = this; - for(var n = 0; n < attrs.length; n++) { - var attr = attrs[n]; - this.on(attr + 'Change.kinetic', that._setTextData); - } - that._setTextData(); - }, - drawFunc: function(context) { - // draw rect - context.beginPath(); - var boxWidth = this.getWidth(); - var boxHeight = this.getHeight(); - - if(this.attrs.cornerRadius === 0) { - // simple rect - don't bother doing all that complicated maths stuff. - context.rect(0, 0, boxWidth, boxHeight); - } - else { - // arcTo would be nicer, but browser support is patchy (Opera) - context.moveTo(this.attrs.cornerRadius, 0); - context.lineTo(boxWidth - this.attrs.cornerRadius, 0); - context.arc(boxWidth - this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI * 3 / 2, 0, false); - context.lineTo(boxWidth, boxHeight - this.attrs.cornerRadius); - context.arc(boxWidth - this.attrs.cornerRadius, boxHeight - this.attrs.cornerRadius, this.attrs.cornerRadius, 0, Math.PI / 2, false); - context.lineTo(this.attrs.cornerRadius, boxHeight); - context.arc(this.attrs.cornerRadius, boxHeight - this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI / 2, Math.PI, false); - context.lineTo(0, this.attrs.cornerRadius); - context.arc(this.attrs.cornerRadius, this.attrs.cornerRadius, this.attrs.cornerRadius, Math.PI, Math.PI * 3 / 2, false); - } - context.closePath(); - - this.fill(context); - this.stroke(context); - /* - * draw text - */ - var p = this.attrs.padding; - var lineHeightPx = this.attrs.lineHeight * this.getTextHeight(); - var textArr = this.textArr; - - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - context.textBaseline = 'middle'; - context.textAlign = 'left'; - context.save(); - context.translate(p, 0); - context.translate(0, p + this.getTextHeight() / 2); - - // draw text lines - var appliedShadow = this.appliedShadow; - for(var n = 0; n < textArr.length; n++) { - var text = textArr[n]; - /* - * need to reset appliedShadow flag so that shadows - * are appropriately applied to each line of text - */ - this.appliedShadow = appliedShadow; - - // horizontal alignment - context.save(); - if(this.attrs.align === 'right') { - context.translate(this.getWidth() - this._getTextSize(text).width - p * 2, 0); - } - else if(this.attrs.align === 'center') { - context.translate((this.getWidth() - this._getTextSize(text).width - p * 2) / 2, 0); - } - - this.fillText(context, text); - this.strokeText(context, text); - context.restore(); - - context.translate(0, lineHeightPx); - } - context.restore(); - }, - /** - * set text - * @name setText - * @methodOf Kinetic.Text.prototype - * @param {String} text - */ - setText: function(text) { - var str = Kinetic.Type._isString(text) ? text : text.toString(); - this.setAttr('text', str); - }, - /** - * get width - * @name getWidth - * @methodOf Kinetic.Text.prototype - */ - getWidth: function() { - return this.attrs.width === 'auto' ? this.getTextWidth() + this.attrs.padding * 2 : this.attrs.width; - }, - /** - * get height - * @name getHeight - * @methodOf Kinetic.Text.prototype - */ - getHeight: function() { - return this.attrs.height === 'auto' ? (this.getTextHeight() * this.textArr.length * this.attrs.lineHeight) + this.attrs.padding * 2 : this.attrs.height; - }, - /** - * get text width - * @name getTextWidth - * @methodOf Kinetic.Text.prototype - */ - getTextWidth: function() { - return this.textWidth; - }, - /** - * get text height - * @name getTextHeight - * @methodOf Kinetic.Text.prototype - */ - getTextHeight: function() { - return this.textHeight; - }, - _getTextSize: function(text) { - var dummyCanvas = this.dummyCanvas; - var context = dummyCanvas.getContext('2d'); - - context.save(); - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - var metrics = context.measureText(text); - context.restore(); - return { - width: metrics.width, - height: parseInt(this.attrs.fontSize, 10) - }; - }, - /** - * set text data. wrap logic and width and height setting occurs - * here - */ - _setTextData: function() { - var charArr = this.attrs.text.split(''); - var arr = []; - var row = 0; - var addLine = true; - this.textWidth = 0; - this.textHeight = this._getTextSize(this.attrs.text).height; - var lineHeightPx = this.attrs.lineHeight * this.textHeight; - while(charArr.length > 0 && addLine && (this.attrs.height === 'auto' || lineHeightPx * (row + 1) < this.attrs.height - this.attrs.padding * 2)) { - var index = 0; - var line = undefined; - addLine = false; - - while(index < charArr.length) { - if(charArr.indexOf('\n') === index) { - // remove newline char - charArr.splice(index, 1); - line = charArr.splice(0, index).join(''); - break; - } - - // if line exceeds inner box width - var lineArr = charArr.slice(0, index); - if(this.attrs.width !== 'auto' && this._getTextSize(lineArr.join('')).width > this.attrs.width - this.attrs.padding * 2) { - /* - * if a single character is too large to fit inside - * the text box width, then break out of the loop - * and stop processing - */ - if(index == 0) { - break; - } - var lastSpace = lineArr.lastIndexOf(' '); - var lastDash = lineArr.lastIndexOf('-'); - var wrapIndex = Math.max(lastSpace, lastDash); - if(wrapIndex >= 0) { - line = charArr.splice(0, 1 + wrapIndex).join(''); - break; - } - /* - * if not able to word wrap based on space or dash, - * go ahead and wrap in the middle of a word if needed - */ - line = charArr.splice(0, index).join(''); - break; - } - index++; - - // if the end is reached - if(index === charArr.length) { - line = charArr.splice(0, index).join(''); - } - } - this.textWidth = Math.max(this.textWidth, this._getTextSize(line).width); - if(line !== undefined) { - arr.push(line); - addLine = true; - } - row++; - } - this.textArr = arr; - } -}; -Kinetic.Global.extend(Kinetic.Text, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'lineHeight']); -Kinetic.Node.addGetters(Kinetic.Text, ['text']); -/** - * set font family - * @name setFontFamily - * @methodOf Kinetic.Text.prototype - * @param {String} fontFamily - */ - -/** - * set font size - * @name setFontSize - * @methodOf Kinetic.Text.prototype - * @param {int} fontSize - */ - -/** - * set font style. Can be "normal", "italic", or "bold". "normal" is the default. - * @name setFontStyle - * @methodOf Kinetic.Text.prototype - * @param {String} fontStyle - */ - -/** - * set text fill color - * @name setTextFill - * @methodOf Kinetic.Text.prototype - * @param {String} textFill - */ - -/** - * set text stroke color - * @name setFontStroke - * @methodOf Kinetic.Text.prototype - * @param {String} textStroke - */ - -/** - * set text stroke width - * @name setTextStrokeWidth - * @methodOf Kinetic.Text.prototype - * @param {int} textStrokeWidth - */ - -/** - * set padding - * @name setPadding - * @methodOf Kinetic.Text.prototype - * @param {int} padding - */ - -/** - * set horizontal align of text - * @name setAlign - * @methodOf Kinetic.Text.prototype - * @param {String} align align can be 'left', 'center', or 'right' - */ - -/** - * set line height - * @name setLineHeight - * @methodOf Kinetic.Text.prototype - * @param {Number} lineHeight default is 1.2 - */ - -/** - * get font family - * @name getFontFamily - * @methodOf Kinetic.Text.prototype - */ - -/** - * get font size - * @name getFontSize - * @methodOf Kinetic.Text.prototype - */ - -/** - * get font style - * @name getFontStyle - * @methodOf Kinetic.Text.prototype - */ - -/** - * get text fill color - * @name getTextFill - * @methodOf Kinetic.Text.prototype - */ - -/** - * get text stroke color - * @name getTextStroke - * @methodOf Kinetic.Text.prototype - */ - -/** - * get text stroke width - * @name getTextStrokeWidth - * @methodOf Kinetic.Text.prototype - */ - -/** - * get padding - * @name getPadding - * @methodOf Kinetic.Text.prototype - */ - -/** - * get horizontal align - * @name getAlign - * @methodOf Kinetic.Text.prototype - */ - -/** - * get line height - * @name getLineHeight - * @methodOf Kinetic.Text.prototype - */ - -/** - * get text - * @name getText - * @methodOf Kinetic.Text.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Line -/////////////////////////////////////////////////////////////////////// -/** - * Line constructor.  Lines are defined by an array of points - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Line = function(config) { - this._initLine(config); -}; - -Kinetic.Line.prototype = { - _initLine: function(config) { - this.setDefaultAttrs({ - points: [], - lineCap: 'butt', - dashArray: [], - detectionType: 'pixel' - }); - - this.shapeType = "Line"; - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - }, - drawFunc: function(context) { - var lastPos = {}; - context.beginPath(); - - context.moveTo(this.attrs.points[0].x, this.attrs.points[0].y); - - for(var n = 1; n < this.attrs.points.length; n++) { - var x = this.attrs.points[n].x; - var y = this.attrs.points[n].y; - if(this.attrs.dashArray.length > 0) { - // draw dashed line - var lastX = this.attrs.points[n - 1].x; - var lastY = this.attrs.points[n - 1].y; - this._dashedLine(context, lastX, lastY, x, y, this.attrs.dashArray); - } - else { - // draw normal line - context.lineTo(x, y); - } - } - - this.stroke(context); - }, - /** - * set points array - * @name setPoints - * @methodOf Kinetic.Line.prototype - * @param {Array} can be an array of point objects or an array - * of Numbers. e.g. [{x:1,y:2},{x:3,y:4}] or [1,2,3,4] - */ - setPoints: function(val) { - this.setAttr('points', Kinetic.Type._getPoints(val)); - }, - /** - * draw dashed line. Written by Phrogz - */ - _dashedLine: function(context, x, y, x2, y2, dashArray) { - var dashCount = dashArray.length; - - var dx = (x2 - x), dy = (y2 - y); - var xSlope = dx > dy; - var slope = (xSlope) ? dy / dx : dx / dy; - - /* - * gaurd against slopes of infinity - */ - if(slope > 9999) { - slope = 9999; - } - else if(slope < -9999) { - slope = -9999; - } - - var distRemaining = Math.sqrt(dx * dx + dy * dy); - var dashIndex = 0, draw = true; - while(distRemaining >= 0.1 && dashIndex < 10000) { - var dashLength = dashArray[dashIndex++ % dashCount]; - if(dashLength === 0) { - dashLength = 0.001; - } - if(dashLength > distRemaining) { - dashLength = distRemaining; - } - var step = Math.sqrt(dashLength * dashLength / (1 + slope * slope)); - if(xSlope) { - x += dx < 0 && dy < 0 ? step * -1 : step; - y += dx < 0 && dy < 0 ? slope * step * -1 : slope * step; - } - else { - x += dx < 0 && dy < 0 ? slope * step * -1 : slope * step; - y += dx < 0 && dy < 0 ? step * -1 : step; - } - context[draw ? 'lineTo' : 'moveTo'](x, y); - distRemaining -= dashLength; - draw = !draw; - } - - context.moveTo(x2, y2); - } -}; -Kinetic.Global.extend(Kinetic.Line, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Line, ['dashArray']); -Kinetic.Node.addGetters(Kinetic.Line, ['points']); - -/** - * set dash array. - * @name setDashArray - * @methodOf Kinetic.Line.prototype - * @param {Array} dashArray - * examples:
- * [10, 5] dashes are 10px long and 5 pixels apart - * [10, 20, 0, 20] if using a round lineCap, the line will - * be made up of alternating dashed lines that are 10px long - * and 20px apart, and dots that have a radius of 5 and are 20px - * apart - */ - -/** - * get dash array - * @name getDashArray - * @methodOf Kinetic.Line.prototype - */ - -/** - * get points array - * @name getPoints - * @methodOf Kinetic.Line.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Sprite -/////////////////////////////////////////////////////////////////////// -/** - * Sprite constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Sprite = function(config) { - this._initSprite(config); -}; - -Kinetic.Sprite.prototype = { - _initSprite: function(config) { - this.setDefaultAttrs({ - index: 0, - frameRate: 17 - }); - - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - this.anim = new Kinetic.Animation(); - var that = this; - this.on('animationChange.kinetic', function() { - // reset index when animation changes - that.setIndex(0); - }); - }, - drawFunc: function(context) { - var anim = this.attrs.animation; - var index = this.attrs.index; - var f = this.attrs.animations[anim][index]; - - context.beginPath(); - context.rect(0, 0, f.width, f.height); - context.closePath(); - this.fill(context); - this.stroke(context); - - if(this.attrs.image) { - - context.beginPath(); - context.rect(0, 0, f.width, f.height); - context.closePath(); - - this.drawImage(context, this.attrs.image, f.x, f.y, f.width, f.height, 0, 0, f.width, f.height); - } - }, - /** - * start sprite animation - * @name start - * @methodOf Kinetic.Sprite.prototype - */ - start: function() { - var that = this; - var layer = this.getLayer(); - - /* - * animation object has no executable function because - * the updates are done with a fixed FPS with the setInterval - * below. The anim object only needs the layer reference for - * redraw - */ - this.anim.node = layer; - - this.interval = setInterval(function() { - var index = that.attrs.index; - that._updateIndex(); - if(that.afterFrameFunc && index === that.afterFrameIndex) { - that.afterFrameFunc(); - delete that.afterFrameFunc; - delete that.afterFrameIndex; - } - }, 1000 / this.attrs.frameRate); - - this.anim.start(); - }, - /** - * stop sprite animation - * @name stop - * @methodOf Kinetic.Sprite.prototype - */ - stop: function() { - this.anim.stop(); - clearInterval(this.interval); - }, - /** - * set after frame event handler - * @name afterFrame - * @methodOf Kinetic.Sprite.prototype - * @param {Integer} index frame index - * @param {Function} func function to be executed after frame has been drawn - */ - afterFrame: function(index, func) { - this.afterFrameIndex = index; - this.afterFrameFunc = func; - }, - _updateIndex: function() { - var i = this.attrs.index; - var a = this.attrs.animation; - if(i < this.attrs.animations[a].length - 1) { - this.attrs.index++; - } - else { - this.attrs.index = 0; - } - } -}; -Kinetic.Global.extend(Kinetic.Sprite, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Sprite, ['animation', 'animations', 'index']); - -/** - * set animation key - * @name setAnimation - * @methodOf Kinetic.Sprite.prototype - * @param {String} anim animation key - */ - -/** - * set animations object - * @name setAnimations - * @methodOf Kinetic.Sprite.prototype - * @param {Object} animations - */ - -/** - * set animation frame index - * @name setIndex - * @methodOf Kinetic.Sprite.prototype - * @param {Integer} index frame index - */ - -/** - * get animation key - * @name getAnimation - * @methodOf Kinetic.Sprite.prototype - */ - -/** - * get animations object - * @name getAnimations - * @methodOf Kinetic.Sprite.prototype - */ - -/** - * get animation frame index - * @name getIndex - * @methodOf Kinetic.Sprite.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Star -/////////////////////////////////////////////////////////////////////// -/** - * Star constructor - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Star = function(config) { - this._initStar(config); -}; - -Kinetic.Star.prototype = { - _initStar: function(config) { - this.setDefaultAttrs({ - numPoints: 0, - innerRadius: 0, - outerRadius: 0 - }); - - this.shapeType = "Star"; - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - }, - drawFunc: function(context) { - context.beginPath(); - context.moveTo(0, 0 - this.attrs.outerRadius); - - for(var n = 1; n < this.attrs.numPoints * 2; n++) { - var radius = n % 2 === 0 ? this.attrs.outerRadius : this.attrs.innerRadius; - var x = radius * Math.sin(n * Math.PI / this.attrs.numPoints); - var y = -1 * radius * Math.cos(n * Math.PI / this.attrs.numPoints); - context.lineTo(x, y); - } - context.closePath(); - - this.fill(context); - this.stroke(context); - } -}; -Kinetic.Global.extend(Kinetic.Star, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Star, ['numPoints', 'innerRadius', 'outerRadius']); - -/** - * set number of points - * @name setNumPoints - * @methodOf Kinetic.Star.prototype - * @param {Integer} points - */ - -/** - * set outer radius - * @name setOuterRadius - * @methodOf Kinetic.Star.prototype - * @param {Number} radius - */ - -/** - * set inner radius - * @name setInnerRadius - * @methodOf Kinetic.Star.prototype - * @param {Number} radius - */ - -/** - * get number of points - * @name getNumPoints - * @methodOf Kinetic.Star.prototype - */ - -/** - * get outer radius - * @name getOuterRadius - * @methodOf Kinetic.Star.prototype - */ - -/** - * get inner radius - * @name getInnerRadius - * @methodOf Kinetic.Star.prototype - */ -/////////////////////////////////////////////////////////////////////// -// RegularPolygon -/////////////////////////////////////////////////////////////////////// -/** - * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc. - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.RegularPolygon = function(config) { - this._initRegularPolygon(config); -}; - -Kinetic.RegularPolygon.prototype = { - _initRegularPolygon: function(config) { - this.setDefaultAttrs({ - radius: 0, - sides: 0 - }); - - this.shapeType = "RegularPolygon"; - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - }, - drawFunc: function(context) { - context.beginPath(); - context.moveTo(0, 0 - this.attrs.radius); - - for(var n = 1; n < this.attrs.sides; n++) { - var x = this.attrs.radius * Math.sin(n * 2 * Math.PI / this.attrs.sides); - var y = -1 * this.attrs.radius * Math.cos(n * 2 * Math.PI / this.attrs.sides); - context.lineTo(x, y); - } - context.closePath(); - this.fill(context); - this.stroke(context); - } -}; -Kinetic.Global.extend(Kinetic.RegularPolygon, Kinetic.Shape); - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.RegularPolygon, ['radius', 'sides']); - -/** - * set radius - * @name setRadius - * @methodOf Kinetic.RegularPolygon.prototype - * @param {Number} radius - */ - -/** - * set number of sides - * @name setSides - * @methodOf Kinetic.RegularPolygon.prototype - * @param {int} sides - */ -/** - * get radius - * @name getRadius - * @methodOf Kinetic.RegularPolygon.prototype - */ - -/** - * get number of sides - * @name getSides - * @methodOf Kinetic.RegularPolygon.prototype - */ -/////////////////////////////////////////////////////////////////////// -// SVG Path -/////////////////////////////////////////////////////////////////////// -/** - * Path constructor. - * @author Jason Follas - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.Path = function(config) { - this._initPath(config); -}; - -Kinetic.Path.prototype = { - _initPath: function(config) { - this.shapeType = "Path"; - this.dataArray = []; - var that = this; - - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - this.dataArray = Kinetic.Path.parsePathData(this.attrs.data); - this.on('dataChange', function() { - that.dataArray = Kinetic.Path.parsePathData(that.attrs.data); - }); - }, - drawFunc: function(context) { - var ca = this.dataArray; - // context position - context.beginPath(); - for(var n = 0; n < ca.length; n++) { - var c = ca[n].command; - var p = ca[n].points; - switch (c) { - case 'L': - context.lineTo(p[0], p[1]); - break; - case 'M': - context.moveTo(p[0], p[1]); - break; - case 'C': - context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); - break; - case 'Q': - context.quadraticCurveTo(p[0], p[1], p[2], p[3]); - break; - case 'A': - var cx = p[0], cy = p[1], rx = p[2], ry = p[3], theta = p[4], dTheta = p[5], psi = p[6], fs = p[7]; - - var r = (rx > ry) ? rx : ry; - var scaleX = (rx > ry) ? 1 : rx / ry; - var scaleY = (rx > ry) ? ry / rx : 1; - - context.translate(cx, cy); - context.rotate(psi); - context.scale(scaleX, scaleY); - context.arc(0, 0, r, theta, theta + dTheta, 1 - fs); - context.scale(1 / scaleX, 1 / scaleY); - context.rotate(-psi); - context.translate(-cx, -cy); - - break; - case 'z': - context.closePath(); - break; - } - } - this.fill(context); - this.stroke(context); - } -}; -Kinetic.Global.extend(Kinetic.Path, Kinetic.Shape); - -/* - * Utility methods written by jfollas to - * handle length and point measurements - */ -Kinetic.Path.getLineLength = function(x1, y1, x2, y2) { - return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); -}; -Kinetic.Path.getPointOnLine = function(dist, P1x, P1y, P2x, P2y, fromX, fromY) { - if(fromX === undefined) { - fromX = P1x; - } - if(fromY === undefined) { - fromY = P1y; - } - - var m = (P2y - P1y) / ((P2x - P1x) + 0.00000001); - var run = Math.sqrt(dist * dist / (1 + m * m)); - if(P2x < P1x) run *= -1; - var rise = m * run; - var pt; - - if((fromY - P1y) / ((fromX - P1x) + 0.00000001) === m) { - pt = { - x: fromX + run, - y: fromY + rise - }; - } - else { - var ix, iy; - - var len = this.getLineLength(P1x, P1y, P2x, P2y); - if(len < 0.00000001) { - return undefined; - } - var u = (((fromX - P1x) * (P2x - P1x)) + ((fromY - P1y) * (P2y - P1y))); - u = u / (len * len); - ix = P1x + u * (P2x - P1x); - iy = P1y + u * (P2y - P1y); - - var pRise = this.getLineLength(fromX, fromY, ix, iy); - var pRun = Math.sqrt(dist * dist - pRise * pRise); - run = Math.sqrt(pRun * pRun / (1 + m * m)); - if(P2x < P1x) run *= -1; - rise = m * run; - pt = { - x: ix + run, - y: iy + rise - }; - } - - return pt; -}; - -Kinetic.Path.getPointOnCubicBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { - function CB1(t) { - return t * t * t; - } - function CB2(t) { - return 3 * t * t * (1 - t); - } - function CB3(t) { - return 3 * t * (1 - t) * (1 - t); - } - function CB4(t) { - return (1 - t) * (1 - t) * (1 - t); - } - var x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); - var y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); - - return { - x: x, - y: y - }; -}; -Kinetic.Path.getPointOnQuadraticBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y) { - function QB1(t) { - return t * t; - } - function QB2(t) { - return 2 * t * (1 - t); - } - function QB3(t) { - return (1 - t) * (1 - t); - } - var x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); - var y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); - - return { - x: x, - y: y - }; -}; -Kinetic.Path.getPointOnEllipticalArc = function(cx, cy, rx, ry, theta, psi) { - var cosPsi = Math.cos(psi), sinPsi = Math.sin(psi); - var pt = { - x: rx * Math.cos(theta), - y: ry * Math.sin(theta) - }; - return { - x: cx + (pt.x * cosPsi - pt.y * sinPsi), - y: cy + (pt.x * sinPsi + pt.y * cosPsi) - }; -}; -/** - * get parsed data array from the data - * string. V, v, H, h, and l data are converted to - * L data for the purpose of high performance Path - * rendering - */ -Kinetic.Path.parsePathData = function(data) { - // Path Data Segment must begin with a moveTo - //m (x y)+ Relative moveTo (subsequent points are treated as lineTo) - //M (x y)+ Absolute moveTo (subsequent points are treated as lineTo) - //l (x y)+ Relative lineTo - //L (x y)+ Absolute LineTo - //h (x)+ Relative horizontal lineTo - //H (x)+ Absolute horizontal lineTo - //v (y)+ Relative vertical lineTo - //V (y)+ Absolute vertical lineTo - //z (closepath) - //Z (closepath) - //c (x1 y1 x2 y2 x y)+ Relative Bezier curve - //C (x1 y1 x2 y2 x y)+ Absolute Bezier curve - //q (x1 y1 x y)+ Relative Quadratic Bezier - //Q (x1 y1 x y)+ Absolute Quadratic Bezier - //t (x y)+ Shorthand/Smooth Relative Quadratic Bezier - //T (x y)+ Shorthand/Smooth Absolute Quadratic Bezier - //s (x2 y2 x y)+ Shorthand/Smooth Relative Bezier curve - //S (x2 y2 x y)+ Shorthand/Smooth Absolute Bezier curve - //a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Relative Elliptical Arc - //A (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Absolute Elliptical Arc - - // return early if data is not defined - if(!data) { - return []; - } - - // command string - var cs = data; - - // command chars - var cc = ['m', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A']; - // convert white spaces to commas - cs = cs.replace(new RegExp(' ', 'g'), ','); - // create pipes so that we can split the data - for(var n = 0; n < cc.length; n++) { - cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); - } - // create array - var arr = cs.split('|'); - var ca = []; - // init context point - var cpx = 0; - var cpy = 0; - for(var n = 1; n < arr.length; n++) { - var str = arr[n]; - var c = str.charAt(0); - str = str.slice(1); - // remove ,- for consistency - str = str.replace(new RegExp(',-', 'g'), '-'); - // add commas so that it's easy to split - str = str.replace(new RegExp('-', 'g'), ',-'); - str = str.replace(new RegExp('e,-', 'g'), 'e-'); - var p = str.split(','); - if(p.length > 0 && p[0] === '') { - p.shift(); - } - // convert strings to floats - for(var i = 0; i < p.length; i++) { - p[i] = parseFloat(p[i]); - } - while(p.length > 0) { - if(isNaN(p[0]))// case for a trailing comma before next command - break; - - var cmd = null; - var points = []; - var startX = cpx, startY = cpy; - - // convert l, H, h, V, and v to L - switch (c) { - - // Note: Keep the lineTo's above the moveTo's in this switch - case 'l': - cpx += p.shift(); - cpy += p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'L': - cpx = p.shift(); - cpy = p.shift(); - points.push(cpx, cpy); - break; - - // Note: lineTo handlers need to be above this point - case 'm': - cpx += p.shift(); - cpy += p.shift(); - cmd = 'M'; - points.push(cpx, cpy); - c = 'l'; - // subsequent points are treated as relative lineTo - break; - case 'M': - cpx = p.shift(); - cpy = p.shift(); - cmd = 'M'; - points.push(cpx, cpy); - c = 'L'; - // subsequent points are treated as absolute lineTo - break; - - case 'h': - cpx += p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'H': - cpx = p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'v': - cpy += p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'V': - cpy = p.shift(); - cmd = 'L'; - points.push(cpx, cpy); - break; - case 'C': - points.push(p.shift(), p.shift(), p.shift(), p.shift()); - cpx = p.shift(); - cpy = p.shift(); - points.push(cpx, cpy); - break; - case 'c': - points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift()); - cpx += p.shift(); - cpy += p.shift(); - cmd = 'C'; - points.push(cpx, cpy); - break; - case 'S': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'C') { - ctlPtx = cpx + (cpx - prevCmd.points[2]); - ctlPty = cpy + (cpy - prevCmd.points[3]); - } - points.push(ctlPtx, ctlPty, p.shift(), p.shift()); - cpx = p.shift(); - cpy = p.shift(); - cmd = 'C'; - points.push(cpx, cpy); - break; - case 's': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'C') { - ctlPtx = cpx + (cpx - prevCmd.points[2]); - ctlPty = cpy + (cpy - prevCmd.points[3]); - } - points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift()); - cpx += p.shift(); - cpy += p.shift(); - cmd = 'C'; - points.push(cpx, cpy); - break; - case 'Q': - points.push(p.shift(), p.shift()); - cpx = p.shift(); - cpy = p.shift(); - points.push(cpx, cpy); - break; - case 'q': - points.push(cpx + p.shift(), cpy + p.shift()); - cpx += p.shift(); - cpy += p.shift(); - cmd = 'Q'; - points.push(cpx, cpy); - break; - case 'T': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'Q') { - ctlPtx = cpx + (cpx - prevCmd.points[0]); - ctlPty = cpy + (cpy - prevCmd.points[1]); - } - cpx = p.shift(); - cpy = p.shift(); - cmd = 'Q'; - points.push(ctlPtx, ctlPty, cpx, cpy); - break; - case 't': - var ctlPtx = cpx, ctlPty = cpy; - var prevCmd = ca[ca.length - 1]; - if(prevCmd.command === 'Q') { - ctlPtx = cpx + (cpx - prevCmd.points[0]); - ctlPty = cpy + (cpy - prevCmd.points[1]); - } - cpx += p.shift(); - cpy += p.shift(); - cmd = 'Q'; - points.push(ctlPtx, ctlPty, cpx, cpy); - break; - case 'A': - var rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); - var x1 = cpx, y1 = cpy; - cpx = p.shift(), cpy = p.shift(); - cmd = 'A'; - points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); - break; - case 'a': - var rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); - var x1 = cpx, y1 = cpy; - cpx += p.shift(), cpy += p.shift(); - cmd = 'A'; - points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); - break; - } - - ca.push({ - command: cmd || c, - points: points, - start: { - x: startX, - y: startY - }, - pathLength: this.calcLength(startX, startY, cmd || c, points) - }); - } - - if(c === 'z' || c === 'Z') { - ca.push({ - command: 'z', - points: [], - start: undefined, - pathLength: 0 - }); - } - } - - return ca; -}; -Kinetic.Path.calcLength = function(x, y, cmd, points) { - var len, p1, p2; - var path = Kinetic.Path; - - switch (cmd) { - case 'L': - return path.getLineLength(x, y, points[0], points[1]); - case 'C': - // Approximates by breaking curve into 100 line segments - len = 0.0; - p1 = path.getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); - for( t = 0.01; t <= 1; t += 0.01) { - p2 = path.getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - return len; - case 'Q': - // Approximates by breaking curve into 100 line segments - len = 0.0; - p1 = path.getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]); - for( t = 0.01; t <= 1; t += 0.01) { - p2 = path.getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - return len; - case 'A': - // Approximates by breaking curve into line segments - len = 0.0; - var start = points[4]; - // 4 = theta - var dTheta = points[5]; - // 5 = dTheta - var end = points[4] + dTheta; - var inc = Math.PI / 180.0; - // 1 degree resolution - if(Math.abs(start - end) < inc) { - inc = Math.abs(start - end); - } - // Note: for purpose of calculating arc length, not going to worry about rotating X-axis by angle psi - p1 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); - if(dTheta < 0) {// clockwise - for( t = start - inc; t > end; t -= inc) { - p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - } - else {// counter-clockwise - for( t = start + inc; t < end; t += inc) { - p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - } - p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); - len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); - - return len; - } - - return 0; -}; -Kinetic.Path.convertEndpointToCenterParameterization = function(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) { - // Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes - var psi = psiDeg * (Math.PI / 180.0); - var xp = Math.cos(psi) * (x1 - x2) / 2.0 + Math.sin(psi) * (y1 - y2) / 2.0; - var yp = -1 * Math.sin(psi) * (x1 - x2) / 2.0 + Math.cos(psi) * (y1 - y2) / 2.0; - - var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); - - if(lambda > 1) { - rx *= Math.sqrt(lambda); - ry *= Math.sqrt(lambda); - } - - var f = Math.sqrt((((rx * rx) * (ry * ry)) - ((rx * rx) * (yp * yp)) - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp) + (ry * ry) * (xp * xp))); - - if(fa == fs) { - f *= -1; - } - if(isNaN(f)) { - f = 0; - } - - var cxp = f * rx * yp / ry; - var cyp = f * -ry * xp / rx; - - var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp; - var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp; - - var vMag = function(v) { - return Math.sqrt(v[0] * v[0] + v[1] * v[1]); - }; - var vRatio = function(u, v) { - return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); - }; - var vAngle = function(u, v) { - return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v)); - }; - var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]); - var u = [(xp - cxp) / rx, (yp - cyp) / ry]; - var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry]; - var dTheta = vAngle(u, v); - - if(vRatio(u, v) <= -1) { - dTheta = Math.PI; - } - if(vRatio(u, v) >= 1) { - dTheta = 0; - } - if(fs === 0 && dTheta > 0) { - dTheta = dTheta - 2 * Math.PI; - } - if(fs == 1 && dTheta < 0) { - dTheta = dTheta + 2 * Math.PI; - } - return [cx, cy, rx, ry, theta, dTheta, psi, fs]; -}; - -// add getters setters -Kinetic.Node.addGettersSetters(Kinetic.Path, ['data']); - -/** - * set SVG path data string. This method - * also automatically parses the data string - * into a data array. Currently supported SVG data: - * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z - * @name setData - * @methodOf Kinetic.Path.prototype - * @param {String} SVG path command string - */ - -/** - * get SVG path data string - * @name getData - * @methodOf Kinetic.Path.prototype - */ -/////////////////////////////////////////////////////////////////////// -// Text Path -/////////////////////////////////////////////////////////////////////// -/** - * Path constructor. - * @author Jason Follas - * @constructor - * @augments Kinetic.Shape - * @param {Object} config - */ -Kinetic.TextPath = function(config) { - this._initTextPath(config); -}; - -Kinetic.TextPath.prototype = { - _initTextPath: function(config) { - this.setDefaultAttrs({ - fontFamily: 'Calibri', - fontSize: 12, - fontStyle: 'normal', - detectionType: 'path', - text: '' - }); - - this.dummyCanvas = document.createElement('canvas'); - this.shapeType = "TextPath"; - this.dataArray = []; - var that = this; - - config.drawFunc = this.drawFunc; - // call super constructor - Kinetic.Shape.call(this, config); - this.dataArray = Kinetic.Path.parsePathData(this.attrs.data); - this.on('dataChange', function() { - that.dataArray = Kinetic.Path.parsePathData(this.attrs.data); - }); - // update text data for certain attr changes - var attrs = ['text', 'textStroke', 'textStrokeWidth']; - for(var n = 0; n < attrs.length; n++) { - var attr = attrs[n]; - this.on(attr + 'Change', that._setTextData); - } - that._setTextData(); - }, - drawFunc: function(context) { - var charArr = this.charArr; - - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - context.textBaseline = 'middle'; - context.textAlign = 'left'; - context.save(); - - var glyphInfo = this.glyphInfo; - - var appliedShadow = this.appliedShadow; - for(var i = 0; i < glyphInfo.length; i++) { - /* - * need to reset appliedShadow flag so that shadows - * are appropriately applied to each line of text - */ - this.appliedShadow = appliedShadow; - - context.save(); - - var p0 = glyphInfo[i].p0; - var p1 = glyphInfo[i].p1; - var ht = parseFloat(this.attrs.fontSize); - - context.translate(p0.x, p0.y); - - context.rotate(glyphInfo[i].rotation); - - this.fillText(context, glyphInfo[i].text); - this.strokeText(context, glyphInfo[i].text); - - context.restore(); - - //// To assist with debugging visually, uncomment following - // context.beginPath(); - // if (i % 2) - // context.strokeStyle = 'cyan'; - // else - // context.strokeStyle = 'green'; - - // context.moveTo(p0.x, p0.y); - // context.lineTo(p1.x, p1.y); - // context.stroke(); - } - - context.restore(); - }, - /** - * get text width in pixels - * @name getTextWidth - * @methodOf Kinetic.TextPath.prototype - */ - getTextWidth: function() { - return this.textWidth; - }, - /** - * get text height in pixels - * @name getTextHeight - * @methodOf Kinetic.TextPath.prototype - */ - getTextHeight: function() { - return this.textHeight; - }, - /** - * set text - * @name setText - * @methodOf Kinetic.TextPath.prototype - * @param {String} text - */ - setText: function(text) { - Kinetic.Text.prototype.setText.call(this, text); - }, - _getTextSize: function(text) { - var dummyCanvas = this.dummyCanvas; - var context = dummyCanvas.getContext('2d'); - - context.save(); - - context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily; - var metrics = context.measureText(text); - - context.restore(); - - return { - width: metrics.width, - height: parseInt(this.attrs.fontSize, 10) - }; - }, - /** - * set text data. - */ - _setTextData: function() { - - var that = this; - var size = this._getTextSize(this.attrs.text); - this.textWidth = size.width; - this.textHeight = size.height; - - this.glyphInfo = []; - - var charArr = this.attrs.text.split(''); - - var p0, p1, pathCmd; - - var pIndex = -1; - var currentT = 0; - - var getNextPathSegment = function() { - currentT = 0; - var pathData = that.dataArray; - - for(var i = pIndex + 1; i < pathData.length; i++) { - if(pathData[i].pathLength > 0) { - pIndex = i; - - return pathData[i]; - } - else if(pathData[i].command == 'M') { - p0 = { - x: pathData[i].points[0], - y: pathData[i].points[1] - }; - } - } - - return {}; - }; - var findSegmentToFitCharacter = function(c, before) { - - var glyphWidth = that._getTextSize(c).width; - - var currLen = 0; - var attempts = 0; - var needNextSegment = false; - p1 = undefined; - while(Math.abs(glyphWidth - currLen) / glyphWidth > 0.01 && attempts < 25) { - attempts++; - var cumulativePathLength = currLen; - while(pathCmd === undefined) { - pathCmd = getNextPathSegment(); - - if(pathCmd && cumulativePathLength + pathCmd.pathLength < glyphWidth) { - cumulativePathLength += pathCmd.pathLength; - pathCmd = undefined; - } - } - - if(pathCmd === {} || p0 === undefined) - return undefined; - - var needNewSegment = false; - - switch (pathCmd.command) { - case 'L': - if(Kinetic.Path.getLineLength(p0.x, p0.y, pathCmd.points[0], pathCmd.points[1]) > glyphWidth) { - p1 = Kinetic.Path.getPointOnLine(glyphWidth, p0.x, p0.y, pathCmd.points[0], pathCmd.points[1], p0.x, p0.y); - } - else - pathCmd = undefined; - break; - case 'A': - - var start = pathCmd.points[4]; - // 4 = theta - var dTheta = pathCmd.points[5]; - // 5 = dTheta - var end = pathCmd.points[4] + dTheta; - - if(currentT === 0) - currentT = start + 0.00000001; - // Just in case start is 0 - else if(glyphWidth > currLen) - currentT += (Math.PI / 180.0) * dTheta / Math.abs(dTheta); - else - currentT -= Math.PI / 360.0 * dTheta / Math.abs(dTheta); - - if(Math.abs(currentT) > Math.abs(end)) { - currentT = end; - needNewSegment = true; - } - p1 = Kinetic.Path.getPointOnEllipticalArc(pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], currentT, pathCmd.points[6]); - break; - case 'C': - if(currentT === 0) { - if(glyphWidth > pathCmd.pathLength) - currentT = 0.00000001; - else - currentT = glyphWidth / pathCmd.pathLength; - } - else if(glyphWidth > currLen) - currentT += (glyphWidth - currLen) / pathCmd.pathLength; - else - currentT -= (currLen - glyphWidth) / pathCmd.pathLength; - - if(currentT > 1.0) { - currentT = 1.0; - needNewSegment = true; - } - p1 = Kinetic.Path.getPointOnCubicBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], pathCmd.points[4], pathCmd.points[5]); - break; - case 'Q': - if(currentT === 0) - currentT = glyphWidth / pathCmd.pathLength; - else if(glyphWidth > currLen) - currentT += (glyphWidth - currLen) / pathCmd.pathLength; - else - currentT -= (currLen - glyphWidth) / pathCmd.pathLength; - - if(currentT > 1.0) { - currentT = 1.0; - needNewSegment = true; - } - p1 = Kinetic.Path.getPointOnQuadraticBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3]); - break; - - } - - if(p1 !== undefined) { - currLen = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); - } - - if(needNewSegment) { - needNewSegment = false; - pathCmd = undefined; - } - } - }; - for(var i = 0; i < charArr.length; i++) { - - // Find p1 such that line segment between p0 and p1 is approx. width of glyph - findSegmentToFitCharacter(charArr[i]); - - if(p0 === undefined || p1 === undefined) - break; - - var width = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); - - // Note: Since glyphs are rendered one at a time, any kerning pair data built into the font will not be used. - // Can foresee having a rough pair table built in that the developer can override as needed. - - var kern = 0; - // placeholder for future implementation - - var midpoint = Kinetic.Path.getPointOnLine(kern + width / 2.0, p0.x, p0.y, p1.x, p1.y); - - var rotation = Math.atan2((p1.y - p0.y), (p1.x - p0.x)); - this.glyphInfo.push({ - transposeX: midpoint.x, - transposeY: midpoint.y, - text: charArr[i], - rotation: rotation, - p0: p0, - p1: p1 - }); - p0 = p1; - } - } -}; -Kinetic.Global.extend(Kinetic.TextPath, Kinetic.Shape); - -// add setters and getters -Kinetic.Node.addGettersSetters(Kinetic.TextPath, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth']); -Kinetic.Node.addGetters(Kinetic.TextPath, ['text']); - -/** - * set font family - * @name setFontFamily - * @methodOf Kinetic.TextPath.prototype - * @param {String} fontFamily - */ - -/** - * set font size - * @name setFontSize - * @methodOf Kinetic.TextPath.prototype - * @param {int} fontSize - */ - -/** - * set font style. Can be "normal", "italic", or "bold". "normal" is the default. - * @name setFontStyle - * @methodOf Kinetic.TextPath.prototype - * @param {String} fontStyle - */ - -/** - * set text fill color - * @name setTextFill - * @methodOf Kinetic.TextPath.prototype - * @param {String} textFill - */ - -/** - * set text stroke color - * @name setFontStroke - * @methodOf Kinetic.TextPath.prototype - * @param {String} textStroke - */ - -/** - * set text stroke width - * @name setTextStrokeWidth - * @methodOf Kinetic.TextPath.prototype - * @param {int} textStrokeWidth - */ - -/** - * get font family - * @name getFontFamily - * @methodOf Kinetic.TextPath.prototype - */ - -/** - * get font size - * @name getFontSize - * @methodOf Kinetic.TextPath.prototype - */ - -/** - * get font style - * @name getFontStyle - * @methodOf Kinetic.TextPath.prototype - */ - -/** - * get text fill color - * @name getTextFill - * @methodOf Kinetic.TextPath.prototype - */ - -/** - * get text stroke color - * @name getTextStroke - * @methodOf Kinetic.TextPath.prototype - */ - -/** - * get text stroke width - * @name getTextStrokeWidth - * @methodOf Kinetic.TextPath.prototype - */ - -/** - * get text - * @name getText - * @methodOf Kinetic.TextPath.prototype - */ diff --git a/dist/kinetic-current.min.js b/dist/kinetic-current.min.js deleted file mode 100644 index 8ca8c845..00000000 --- a/dist/kinetic-current.min.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * KineticJS JavaScript Library current - * http://www.kineticjs.com/ - * Copyright 2012, Eric Rowell - * Licensed under the MIT or GPL Version 2 licenses. - * Date: Nov 03 2012 - * - * Copyright (C) 2011 - 2012 by Eric Rowell - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -var Kinetic={};Kinetic.Filters={},Kinetic.Plugins={},Kinetic.Global={BUFFER_WHITELIST:["fill","stroke","textFill","textStroke"],BUFFER_BLACKLIST:["shadow"],stages:[],idCounter:0,tempNodes:{},shapes:{},warn:function(a){console&&console.warn&&console.warn("Kinetic warning: "+a)},extend:function(a,b){for(var c in b.prototype)c in a.prototype||(a.prototype[c]=b.prototype[c])},_pullNodes:function(a){var b=this.tempNodes;for(var c in b){var d=b[c];d.getStage()!==undefined&&d.getStage()._id===a._id&&(a._addId(d),a._addName(d),this._removeTempNode(d))}},_addTempNode:function(a){this.tempNodes[a._id]=a},_removeTempNode:function(a){delete this.tempNodes[a._id]}},Kinetic.Filters.Grayscale=function(a){var b=a.data;for(var c=0;c0},_getXY:function(a){if(this._isNumber(a))return{x:a,y:a};if(this._isArray(a)){if(a.length===1){var b=a[0];if(this._isNumber(b))return{x:b,y:b};if(this._isArray(b))return{x:b[0],y:b[1]};if(this._isObject(b))return b}else if(a.length>=2)return{x:a[0],y:a[1]}}else if(this._isObject(a))return a;return{x:0,y:0}},_getSize:function(a){if(this._isNumber(a))return{width:a,height:a};if(this._isArray(a))if(a.length===1){var b=a[0];if(this._isNumber(b))return{width:b,height:b};if(this._isArray(b)){if(b.length>=4)return{width:b[2],height:b[3]};if(b.length>=2)return{width:b[0],height:b[1]}}else if(this._isObject(b))return b}else{if(a.length>=4)return{width:a[2],height:a[3]};if(a.length>=2)return{width:a[0],height:a[1]}}else if(this._isObject(a))return a;return{width:0,height:0}},_getPoints:function(a){if(a===undefined)return[];if(this._isObject(a[0]))return a;var b=[];for(var c=0;c>16&255,g:b>>8&255,b:b&255}},_getRandomColorKey:function(){var a=Math.round(Math.random()*255),b=Math.round(Math.random()*255),c=Math.round(Math.random()*255);return this._rgbToHex(a,b,c)},_merge:function(a,b){var c=this._clone(b);for(var d in a)this._isObject(a[d])?c[d]=this._merge(a[d],c[d]):c[d]=a[d];return c},_clone:function(a){var b={};for(var c in a)this._isObject(a[c])?b[c]=this._clone(a[c]):b[c]=a[c];return b}},Kinetic.Canvas=function(a,b){this.element=document.createElement("canvas"),this.context=this.element.getContext("2d"),this.element.width=a,this.element.height=b},Kinetic.Canvas.prototype={clear:function(){var a=this.getContext(),b=this.getElement();a.clearRect(0,0,b.width,b.height)},getElement:function(){return this.element},getContext:function(){return this.context},setWidth:function(a){this.element.width=a},setHeight:function(a){this.element.height=a},getWidth:function(){return this.element.width},getHeight:function(){return this.element.height},setSize:function(a,b){this.setWidth(a),this.setHeight(b)},toDataURL:function(a,b){try{return this.element.toDataURL(a,b)}catch(c){return this.element.toDataURL()}}},Kinetic.Tween=function(a,b,c,d,e,f){this._listeners=[],this.addListener(this),this.obj=a,this.propFunc=b,this.begin=d,this._pos=d,this.setDuration(f),this.isPlaying=!1,this._change=0,this.prevTime=0,this.prevPos=0,this.looping=!1,this._time=0,this._position=0,this._startTime=0,this._finish=0,this.name="",this.func=c,this.setFinish(e)},Kinetic.Tween.prototype={setTime:function(a){this.prevTime=this._time,a>this.getDuration()?this.looping?(this.rewind(a-this._duration),this.update(),this.broadcastMessage("onLooped",{target:this,type:"onLooped"})):(this._time=this._duration,this.update(),this.stop(),this.broadcastMessage("onFinished",{target:this,type:"onFinished"})):a<0?(this.rewind(),this.update()):(this._time=a,this.update())},getTime:function(){return this._time},setDuration:function(a){this._duration=a===null||a<=0?1e5:a},getDuration:function(){return this._duration},setPosition:function(a){this.prevPos=this._pos,this.propFunc(a),this._pos=a,this.broadcastMessage("onChanged",{target:this,type:"onChanged"})},getPosition:function(a){return a===undefined&&(a=this._time),this.func(a,this.begin,this._change,this._duration)},setFinish:function(a){this._change=a-this.begin},getFinish:function(){return this.begin+this._change},start:function(){this.rewind(),this.startEnterFrame(),this.broadcastMessage("onStarted",{target:this,type:"onStarted"})},rewind:function(a){this.stop(),this._time=a===undefined?0:a,this.fixTime(),this.update()},fforward:function(){this._time=this._duration,this.fixTime(),this.update()},update:function(){this.setPosition(this.getPosition(this._time))},startEnterFrame:function(){this.stopEnterFrame(),this.isPlaying=!0,this.onEnterFrame()},onEnterFrame:function(){this.isPlaying&&this.nextFrame()},nextFrame:function(){this.setTime((this.getTimer()-this._startTime)/1e3)},stop:function(){this.stopEnterFrame(),this.broadcastMessage("onStopped",{target:this,type:"onStopped"})},stopEnterFrame:function(){this.isPlaying=!1},continueTo:function(a,b){this.begin=this._pos,this.setFinish(a),this._duration!==undefined&&this.setDuration(b),this.start()},resume:function(){this.fixTime(),this.startEnterFrame(),this.broadcastMessage("onResumed",{target:this,type:"onResumed"})},yoyo:function(){this.continueTo(this.begin,this._time)},addListener:function(a){return this.removeListener(a),this._listeners.push(a)},removeListener:function(a){var b=this._listeners,c=b.length;while(c--)if(b[c]==a)return b.splice(c,1),!0;return!1},broadcastMessage:function(){var a=[];for(var b=0;b0){this._runFrames();var a=this;Kinetic.Animation.requestAnimFrame(function(){a._animationLoop()})}else this.animRunning=!1},Kinetic.Animation._handleAnimation=function(){var a=this;this.animRunning||(this.animRunning=!0,a._animationLoop())},Kinetic.Animation.requestAnimFrame=function(a){var b=Kinetic.DD&&Kinetic.DD.moving?this.fixedRequestAnimFrame:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||Kinetic.Animation.fixedRequestAnimFrame;b(a)},Kinetic.Node=function(a){this._nodeInit(a)},Kinetic.Node.prototype={_nodeInit:function(a){this.defaultNodeAttrs={visible:!0,listening:!0,name:undefined,opacity:1,x:0,y:0,scale:{x:1,y:1},rotation:0,offset:{x:0,y:0},draggable:!1},this.setDefaultAttrs(this.defaultNodeAttrs),this.eventListeners={},this.setAttrs(a);var b=this;this.on("idChange.kinetic",function(a){var c=b.getStage();c&&(c._removeId(a.oldVal),c._addId(b))}),this.on("nameChange.kinetic",function(a){var c=b.getStage();c&&(c._removeName(a.oldVal,b._id),c._addName(b))})},on:function(a,b){var c=a.split(" ");for(var d=0;d1?g[1]:"";this.eventListeners[h]||(this.eventListeners[h]=[]),this.eventListeners[h].push({name:i,handler:b})}},off:function(a){var b=a.split(" ");for(var c=0;c1)if(g)this.eventListeners[g]&&this._off(g,f[1]);else for(var d in this.eventListeners)this._off(d,f[1]);else delete this.eventListeners[g]}},remove:function(){var a=this.getParent();if(a&&this.index!==undefined&&a.children[this.index]._id==this._id){var b=a.getStage();b&&(b._removeId(this.getId()),b._removeName(this.getName(),this._id)),Kinetic.Global._removeTempNode(this),a.children.splice(this.index,1),a._setChildrenIndices();while(this.children&&this.children.length>0)this.children[0].remove()}},getAttrs:function(){return this.attrs},setDefaultAttrs:function(a){this.attrs===undefined&&(this.attrs={});if(a)for(var b in a)this.attrs[b]===undefined&&(this.attrs[b]=a[b])},setAttrs:function(a){if(a)for(var b in a){var c="set"+b.charAt(0).toUpperCase()+b.slice(1);Kinetic.Type._isFunction(this[c])?this[c](a[b]):this.setAttr(b,a[b])}},isVisible:function(){return this.attrs.visible&&this.getParent()&&!this.getParent().isVisible()?!1:this.attrs.visible},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},getZIndex:function(){return this.index},getAbsoluteZIndex:function(){function e(b){var f=[];for(var g=0;g0&&f[0].getLevel()<=a&&e(f)}var a=this.getLevel(),b=this.getStage(),c=this,d=0;return c.nodeType!=="Stage"&&e(c.getStage().getChildren()),d},getLevel:function(){var a=0,b=this.parent;while(b)a++,b=b.parent;return a},setPosition:function(){var a=Kinetic.Type._getXY([].slice.call(arguments));this.setAttr("x",a.x),this.setAttr("y",a.y)},getPosition:function(){return{x:this.attrs.x,y:this.attrs.y}},getAbsolutePosition:function(){var a=this.getAbsoluteTransform(),b=this.getOffset();return a.translate(b.x,b.y),a.getTranslation()},setAbsolutePosition:function(){var a=Kinetic.Type._getXY([].slice.call(arguments)),b=this._clearTransform();this.attrs.x=b.x,this.attrs.y=b.y,delete b.x,delete b.y;var c=this.getAbsoluteTransform();c.invert(),c.translate(a.x,a.y),a={x:this.attrs.x+c.getTranslation().x,y:this.attrs.y+c.getTranslation().y},this.setPosition(a.x,a.y),this._setTransform(b)},move:function(){var a=Kinetic.Type._getXY([].slice.call(arguments)),b=this.getX(),c=this.getY();a.x!==undefined&&(b+=a.x),a.y!==undefined&&(c+=a.y),this.setPosition(b,c)},getRotationDeg:function(){return this.getRotation()*180/Math.PI},setRotationDeg:function(a){this.setRotation(a*Math.PI/180)},rotate:function(a){this.setRotation(this.getRotation()+a)},rotateDeg:function(a){this.setRotation(this.getRotation()+a*Math.PI/180)},moveToTop:function(){var a=this.index;return this.parent.children.splice(a,1),this.parent.children.push(this),this.parent._setChildrenIndices(),!0},moveUp:function(){var a=this.index;if(a0)return this.parent.children.splice(a,1),this.parent.children.splice(a-1,0,this),this.parent._setChildrenIndices(),!0},moveToBottom:function(){var a=this.index;if(a>0)return this.parent.children.splice(a,1),this.parent.children.unshift(this),this.parent._setChildrenIndices(),!0},setZIndex:function(a){var b=this.index;this.parent.children.splice(b,1),this.parent.children.splice(a,0,this),this.parent._setChildrenIndices()},getAbsoluteOpacity:function(){var a=this.getOpacity();return this.getParent()&&(a*=this.getParent().getAbsoluteOpacity()),a},moveTo:function(a){var b=this.parent;b.children.splice(this.index,1),b._setChildrenIndices(),a.children.push(this),this.index=a.children.length-1,this.parent=a,a._setChildrenIndices()},toObject:function(){var a={},b=Kinetic.Type;a.attrs={};for(var c in this.attrs){var d=this.attrs[c];!b._isFunction(d)&&!b._isElement(d)&&(!b._isObject(d)||!b._hasMethods(d))&&(a.attrs[c]=d)}return a.nodeType=this.nodeType,a.shapeType=this.shapeType,a},toJSON:function(){return JSON.stringify(this.toObject())},getParent:function(){return this.parent},getLayer:function(){return this.getParent().getLayer()},getStage:function(){return this.getParent()?this.getParent().getStage():undefined},simulate:function(a,b){this._handleEvent(a,b||{})},fire:function(a,b){this._executeHandlers(a,b||{})},getAbsoluteTransform:function(){var a=new Kinetic.Transform,b=[],c=this.parent;b.unshift(this);while(c)b.unshift(c),c=c.parent;for(var d=0;d=c.tweens.length&&c.onFinished()}}},Kinetic.Transition.prototype={start:function(){for(var a=0;a0)this.children[0].remove()},add:function(a){a._id=Kinetic.Global.idCounter++,a.index=this.children.length,a.parent=this,this.children.push(a);var b=a.getStage();if(!b)Kinetic.Global._addTempNode(a);else{b._addId(a),b._addName(a);var c=Kinetic.Global;c._pullNodes(b)}return this},get:function(a){var b=new Kinetic.Collection;if(a.charAt(0)==="#"){var c=this._getNodeById(a.slice(1));c&&b.push(c)}else if(a.charAt(0)==="."){var d=this._getNodesByName(a.slice(1));Kinetic.Collection.apply(b,d)}else{var e=[],f=this.getChildren();for(var g=0;g=0;d--){var e=c[d];if(e.isVisible()&&e.isListening()){var f=e.bufferCanvas.context.getImageData(Math.round(a.x),Math.round(a.y),1,1).data;if(f[3]===255){var g=Kinetic.Type._rgbToHex(f[0],f[1],f[2]);return b=Kinetic.Global.shapes[g],{shape:b,pixel:f}}if(f[0]>0||f[1]>0||f[2]>0||f[3]>0)return{pixel:f}}}return null},_getNodeById:function(a){return this.ids[a]||null},_getNodesByName:function(a){return this.names[a]||[]},_resizeDOM:function(){if(this.content){var a=this.attrs.width,b=this.attrs.height;this.content.style.width=a+"px",this.content.style.height=b+"px",this.bufferCanvas.setSize(a,b);var c=this.children;for(var d=0;d0},remove:function(){Kinetic.Node.prototype.remove.call(this),delete Kinetic.Global.shapes[this.colorKey]},draw:function(a){if(this.attrs.drawFunc&&Kinetic.Node.prototype._shouldDraw.call(this,a)){var b=this.getStage(),c=a.getContext(),d=[],e=this.parent;d.unshift(this);while(e)d.unshift(e),e=e.parent;c.save();for(var f=0;f0&&d&&(this.attrs.height==="auto"||e*(c+1)this.attrs.width-this.attrs.padding*2){if(f==0)break;var i=h.lastIndexOf(" "),j=h.lastIndexOf("-"),k=Math.max(i,j);if(k>=0){g=a.splice(0,1+k).join("");break}g=a.splice(0,f).join("");break}f++,f===a.length&&(g=a.splice(0,f).join(""))}this.textWidth=Math.max(this.textWidth,this._getTextSize(g).width),g!==undefined&&(b.push(g),d=!0),c++}this.textArr=b}},Kinetic.Global.extend(Kinetic.Text,Kinetic.Shape),Kinetic.Node.addGettersSetters(Kinetic.Text,["fontFamily","fontSize","fontStyle","textFill","textStroke","textStrokeWidth","padding","align","lineHeight"]),Kinetic.Node.addGetters(Kinetic.Text,["text"]),Kinetic.Line=function(a){this._initLine(a)},Kinetic.Line.prototype={_initLine:function(a){this.setDefaultAttrs({points:[],lineCap:"butt",dashArray:[],detectionType:"pixel"}),this.shapeType="Line",a.drawFunc=this.drawFunc,Kinetic.Shape.call(this,a)},drawFunc:function(a){var b={};a.beginPath(),a.moveTo(this.attrs.points[0].x,this.attrs.points[0].y);for(var c=1;c0){var f=this.attrs.points[c-1].x,g=this.attrs.points[c-1].y;this._dashedLine(a,f,g,d,e,this.attrs.dashArray)}else a.lineTo(d,e)}this.stroke(a)},setPoints:function(a){this.setAttr("points",Kinetic.Type._getPoints(a))},_dashedLine:function(a,b,c,d,e,f){var g=f.length,h=d-b,i=e-c,j=h>i,k=j?i/h:h/i;k>9999?k=9999:k<-9999&&(k=-9999);var l=Math.sqrt(h*h+i*i),m=0,n=!0;while(l>=.1&&m<1e4){var o=f[m++%g];o===0&&(o=.001),o>l&&(o=l);var p=Math.sqrt(o*o/(1+k*k));j?(b+=h<0&&i<0?p*-1:p,c+=h<0&&i<0?k*p*-1:k*p):(b+=h<0&&i<0?k*p*-1:k*p,c+=h<0&&i<0?p*-1:p),a[n?"lineTo":"moveTo"](b,c),l-=o,n=!n}a.moveTo(d,e)}},Kinetic.Global.extend(Kinetic.Line,Kinetic.Shape),Kinetic.Node.addGettersSetters(Kinetic.Line,["dashArray"]),Kinetic.Node.addGetters(Kinetic.Line,["points"]),Kinetic.Sprite=function(a){this._initSprite(a)},Kinetic.Sprite.prototype={_initSprite:function(a){this.setDefaultAttrs({index:0,frameRate:17}),a.drawFunc=this.drawFunc,Kinetic.Shape.call(this,a),this.anim=new Kinetic.Animation;var b=this;this.on("animationChange.kinetic",function(){b.setIndex(0)})},drawFunc:function(a){var b=this.attrs.animation,c=this.attrs.index,d=this.attrs.animations[b][c];a.beginPath(),a.rect(0,0,d.width,d.height),a.closePath(),this.fill(a),this.stroke(a),this.attrs.image&&(a.beginPath(),a.rect(0,0,d.width,d.height),a.closePath(),this.drawImage(a,this.attrs.image,d.x,d.y,d.width,d.height,0,0,d.width,d.height))},start:function(){var a=this,b=this.getLayer();this.anim.node=b,this.interval=setInterval(function(){var b=a.attrs.index;a._updateIndex(),a.afterFrameFunc&&b===a.afterFrameIndex&&(a.afterFrameFunc(),delete a.afterFrameFunc,delete a.afterFrameIndex)},1e3/this.attrs.frameRate),this.anim.start()},stop:function(){this.anim.stop(),clearInterval(this.interval)},afterFrame:function(a,b){this.afterFrameIndex=a,this.afterFrameFunc=b},_updateIndex:function(){var a=this.attrs.index,b=this.attrs.animation;ai?h:i,o=h>i?1:h/i,p=h>i?i/h:1;a.translate(f,g),a.rotate(l),a.scale(o,p),a.arc(0,0,n,j,j+k,1-m),a.scale(1/o,1/p),a.rotate(-l),a.translate(-f,-g);break;case"z":a.closePath()}}this.fill(a),this.stroke(a)}},Kinetic.Global.extend(Kinetic.Path,Kinetic.Shape),Kinetic.Path.getLineLength=function(a,b,c,d){return Math.sqrt((c-a)*(c-a)+(d-b)*(d-b))},Kinetic.Path.getPointOnLine=function(a,b,c,d,e,f,g){f===undefined&&(f=b),g===undefined&&(g=c);var h=(e-c)/(d-b+1e-8),i=Math.sqrt(a*a/(1+h*h));d0&&k[0]===""&&k.shift();for(var l=0;l0){if(isNaN(k[0]))break;var m=null,n=[],o=g,p=h;switch(j){case"l":g+=k.shift(),h+=k.shift(),m="L",n.push(g,h);break;case"L":g=k.shift(),h=k.shift(),n.push(g,h);break;case"m":g+=k.shift(),h+=k.shift(),m="M",n.push(g,h),j="l";break;case"M":g=k.shift(),h=k.shift(),m="M",n.push(g,h),j="L";break;case"h":g+=k.shift(),m="L",n.push(g,h);break;case"H":g=k.shift(),m="L",n.push(g,h);break;case"v":h+=k.shift(),m="L",n.push(g,h);break;case"V":h=k.shift(),m="L",n.push(g,h);break;case"C":n.push(k.shift(),k.shift(),k.shift(),k.shift()),g=k.shift(),h=k.shift(),n.push(g,h);break;case"c":n.push(g+k.shift(),h+k.shift(),g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),m="C",n.push(g,h);break;case"S":var q=g,r=h,s=f[f.length-1];s.command==="C"&&(q=g+(g-s.points[2]),r=h+(h-s.points[3])),n.push(q,r,k.shift(),k.shift()),g=k.shift(),h=k.shift(),m="C",n.push(g,h);break;case"s":var q=g,r=h,s=f[f.length-1];s.command==="C"&&(q=g+(g-s.points[2]),r=h+(h-s.points[3])),n.push(q,r,g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),m="C",n.push(g,h);break;case"Q":n.push(k.shift(),k.shift()),g=k.shift(),h=k.shift(),n.push(g,h);break;case"q":n.push(g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),m="Q",n.push(g,h);break;case"T":var q=g,r=h,s=f[f.length-1];s.command==="Q"&&(q=g+(g-s.points[0]),r=h+(h-s.points[1])),g=k.shift(),h=k.shift(),m="Q",n.push(q,r,g,h);break;case"t":var q=g,r=h,s=f[f.length-1];s.command==="Q"&&(q=g+(g-s.points[0]),r=h+(h-s.points[1])),g+=k.shift(),h+=k.shift(),m="Q",n.push(q,r,g,h);break;case"A":var t=k.shift(),u=k.shift(),v=k.shift(),w=k.shift(),x=k.shift(),y=g,z=h;g=k.shift(),h=k.shift(),m="A",n=this.convertEndpointToCenterParameterization(y,z,g,h,w,x,t,u,v);break;case"a":var t=k.shift(),u=k.shift(),v=k.shift(),w=k.shift(),x=k.shift(),y=g,z=h;g+=k.shift(),h+=k.shift(),m="A",n=this.convertEndpointToCenterParameterization(y,z,g,h,w,x,t,u,v)}f.push({command:m||j,points:n,start:{x:o,y:p},pathLength:this.calcLength(o,p,m||j,n)})}(j==="z"||j==="Z")&&f.push({command:"z",points:[],start:undefined,pathLength:0})}return f},Kinetic.Path.calcLength=function(a,b,c,d){var e,f,g,h=Kinetic.Path;switch(c){case"L":return h.getLineLength(a,b,d[0],d[1]);case"C":e=0,f=h.getPointOnCubicBezier(0,a,b,d[0],d[1],d[2],d[3],d[4],d[5]);for(t=.01;t<=1;t+=.01)g=h.getPointOnCubicBezier(t,a,b,d[0],d[1],d[2],d[3],d[4],d[5]),e+=h.getLineLength(f.x,f.y,g.x,g.y),f=g;return e;case"Q":e=0,f=h.getPointOnQuadraticBezier(0,a,b,d[0],d[1],d[2],d[3]);for(t=.01;t<=1;t+=.01)g=h.getPointOnQuadraticBezier(t,a,b,d[0],d[1],d[2],d[3]),e+=h.getLineLength(f.x,f.y,g.x,g.y),f=g;return e;case"A":e=0;var i=d[4],j=d[5],k=d[4]+j,l=Math.PI/180;Math.abs(i-k)k;t-=l)g=h.getPointOnEllipticalArc(d[0],d[1],d[2],d[3],t,0),e+=h.getLineLength(f.x,f.y,g.x,g.y),f=g;else for(t=i+l;t1&&(g*=Math.sqrt(m),h*=Math.sqrt(m));var n=Math.sqrt((g*g*h*h-g*g*l*l-h*h*k*k)/(g*g*l*l+h*h*k*k));e==f&&(n*=-1),isNaN(n)&&(n=0);var o=n*g*l/h,p=n*-h*k/g,q=(a+c)/2+Math.cos(j)*o-Math.sin(j)*p,r=(b+d)/2+Math.sin(j)*o+Math.cos(j)*p,s=function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1])},t=function(a,b){return(a[0]*b[0]+a[1]*b[1])/(s(a)*s(b))},u=function(a,b){return(a[0]*b[1]=1&&(y=0),f===0&&y>0&&(y-=2*Math.PI),f==1&&y<0&&(y+=2*Math.PI),[q,r,g,h,v,y,j,f]},Kinetic.Node.addGettersSetters(Kinetic.Path,["data"]),Kinetic.TextPath=function(a){this._initTextPath(a)},Kinetic.TextPath.prototype={_initTextPath:function(a){this.setDefaultAttrs({fontFamily:"Calibri",fontSize:12,fontStyle:"normal",detectionType:"path" -,text:""}),this.dummyCanvas=document.createElement("canvas"),this.shapeType="TextPath",this.dataArray=[];var b=this;a.drawFunc=this.drawFunc,Kinetic.Shape.call(this,a),this.dataArray=Kinetic.Path.parsePathData(this.attrs.data),this.on("dataChange",function(){b.dataArray=Kinetic.Path.parsePathData(this.attrs.data)});var c=["text","textStroke","textStrokeWidth"];for(var d=0;d0)return g=c,b[c];b[c].command=="M"&&(d={x:b[c].points[0],y:b[c].points[1]})}return{}},j=function(b,c){var g=a._getTextSize(b).width,j=0,k=0,l=!1;e=undefined;while(Math.abs(g-j)/g>.01&&k<25){k++;var m=j;while(f===undefined)f=i(),f&&m+f.pathLengthg?e=Kinetic.Path.getPointOnLine(g,d.x,d.y,f.points[0],f.points[1],d.x,d.y):f=undefined;break;case"A":var o=f.points[4],p=f.points[5],q=f.points[4]+p;h===0?h=o+1e-8:g>j?h+=Math.PI/180*p/Math.abs(p):h-=Math.PI/360*p/Math.abs(p),Math.abs(h)>Math.abs(q)&&(h=q,n=!0),e=Kinetic.Path.getPointOnEllipticalArc(f.points[0],f.points[1],f.points[2],f.points[3],h,f.points[6]);break;case"C":h===0?g>f.pathLength?h=1e-8:h=g/f.pathLength:g>j?h+=(g-j)/f.pathLength:h-=(j-g)/f.pathLength,h>1&&(h=1,n=!0),e=Kinetic.Path.getPointOnCubicBezier(h,f.start.x,f.start.y,f.points[0],f.points[1],f.points[2],f.points[3],f.points[4],f.points[5]);break;case"Q":h===0?h=g/f.pathLength:g>j?h+=(g-j)/f.pathLength:h-=(j-g)/f.pathLength,h>1&&(h=1,n=!0),e=Kinetic.Path.getPointOnQuadraticBezier(h,f.start.x,f.start.y,f.points[0],f.points[1],f.points[2],f.points[3])}e!==undefined&&(j=Kinetic.Path.getLineLength(d.x,d.y,e.x,e.y)),n&&(n=!1,f=undefined)}};for(var k=0;k