diff --git a/Thorfile b/Thorfile index 6e5f2582..35ed74c6 100644 --- a/Thorfile +++ b/Thorfile @@ -4,7 +4,7 @@ class Build < Thor # This is the list of files to concatenate. The first file will appear at the top of the final file. All files are relative to the lib directory. FILES = [ "license.js", "src/Global.js", "src/Transition.js", "src/filters/Grayscale.js", - "src/util/Type.js", "src/util/Canvas.js", "src/util/Class.js", "src/util/Tween.js", "src/util/Transform.js", + "src/util/Type.js", "src/util/Canvas.js", "src/util/Class.js", "src/util/Tween.js", "src/util/Transform.js", "src/util/Geometry.js", "src/Animation.js", "src/Node.js", "src/Container.js", "src/Stage.js", "src/Layer.js", "src/Group.js", "src/Shape.js", "src/shapes/Rect.js", "src/shapes/Ellipse.js", "src/shapes/Image.js", "src/shapes/Polygon.js", "src/shapes/Text.js", "src/shapes/Line.js", "src/shapes/Sprite.js" ] diff --git a/dist/kinetic-core.js b/dist/kinetic-core.js index d665efe8..e4a3559a 100644 --- a/dist/kinetic-core.js +++ b/dist/kinetic-core.js @@ -1083,6 +1083,109 @@ Kinetic.Transform.prototype = { } }; +/* + * Utility methods written by jfollas to + * handle length and point measurements + */ +Kinetic.Geometry = { + getLineLength: function(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + }, + 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)); + 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)); + rise = m * run; + pt = { + x: ix + run, + y: iy + rise + }; + } + + return pt; + }, + 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 + }; + }, + 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 + }; + }, + 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) + }; + } +}; + /////////////////////////////////////////////////////////////////////// // Animation /////////////////////////////////////////////////////////////////////// @@ -4559,9 +4662,7 @@ Kinetic.Ellipse = Kinetic.Shape.extend({ // call super constructor this._super(config); - this._convertRadius(); - var that = this; this.on('radiusChange.kinetic', function() { that._convertRadius(); @@ -4891,7 +4992,6 @@ Kinetic.Text = Kinetic.Shape.extend({ var attr = attrs[n]; this.on(attr + 'Change.kinetic', that._setTextData); } - that._setTextData(); }, drawFunc: function(context) { @@ -5403,7 +5503,6 @@ Kinetic.Sprite = Kinetic.Shape.extend({ config.drawFunc = this.drawFunc; // call super constructor this._super(config); - var that = this; this.on('animationChange.kinetic', function() { // reset index when animation changes diff --git a/dist/kinetic-core.min.js b/dist/kinetic-core.min.js index e69de29b..eb35ca54 100644 --- a/dist/kinetic-core.min.js +++ b/dist/kinetic-core.min.js @@ -0,0 +1,29 @@ +/** + * KineticJS JavaScript Library core + * http://www.kineticjs.com/ + * Copyright 2012, Eric Rowell + * Licensed under the MIT or GPL Version 2 licenses. + * Date: Jul 28 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={BUBBLE_WHITELIST:["mousedown","mousemove","mouseup","mouseover","mouseout","click","dblclick","touchstart","touchmove","touchend","tap","dbltap","dragstart","dragmove","dragend"],stages:[],idCounter:0,tempNodes:[],maxDragTimeInterval:20,drag:{moving:!1,offset:{x:0,y:0},lastDrawTime:0},warn:function(a){console&&console.warn&&console.warn("Kinetic warning: "+a)},_pullNodes:function(a){var b=this.tempNodes;for(var c=0;c=c.tweens.length&&c.onFinished()}}},Kinetic.Transition.prototype={start:function(){for(var a=0;a0},_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;cthis.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._updateFrameObject(),this._runFrames();var a=this;requestAnimFrame(function(){a._animationLoop()})}else this.animRunning=!1,this.frame.lastTime=0},_handleAnimation:function(){var a=this;this.animRunning?this.frame.lastTime=0:(this.animRunning=!0,a._animationLoop())}},requestAnimFrame=function(a){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),Kinetic.Node=Kinetic.Class.extend({init:function(a){this.defaultNodeAttrs={visible:!0,listening:!0,name:undefined,alpha:1,x:0,y:0,scale:{x:1,y:1},rotation:0,offset:{x:0,y:0},dragConstraint:"none",dragBounds:{},draggable:!1,dragThrottle:80},this.setDefaultAttrs(this.defaultNodeAttrs),this.eventListeners={},this.lastDragTime=0,this.setAttrs(a),this.on("draggableChange.kinetic",function(){this._onDraggableChange()});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))}),this._onDraggableChange()},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){var h=f[1];for(var i=0;i0&&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(Array.prototype.slice.call(arguments));this.setAttrs(a)},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(Array.prototype.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(Array.prototype.slice.call(arguments)),b=this.getX(),c=this.getY();a.x!==undefined&&(b+=a.x),a.y!==undefined&&(c+=a.y),this.setAttrs({x:b,y:c})},getRotationDeg:function(){return this.attrs.rotation*180/Math.PI},rotate:function(a){this.setAttrs({rotation:this.getRotation()+a})},rotateDeg:function(a){this.setAttrs({rotation:this.getRotation()+a*Math.PI/180})},moveToTop:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.push(this),this.parent._setChildrenIndices()},moveUp:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.splice(a+1,0,this),this.parent._setChildrenIndices()},moveDown:function(){var a=this.index;a>0&&(this.parent.children.splice(a,1),this.parent.children.splice(a-1,0,this),this.parent._setChildrenIndices())},moveToBottom:function(){var a=this.index;this.parent.children.splice(a,1),this.parent.children.unshift(this),this.parent._setChildrenIndices()},setZIndex:function(a){var b=this.index;this.parent.children.splice(b,1),this.parent.children.splice(a,0,this),this.parent._setChildrenIndices()},getAbsoluteAlpha:function(){var a=1,b=this;while(b.nodeType!=="Stage")a*=b.attrs.alpha,b=b.parent;return a},isDragging:function(){var a=Kinetic.Global;return a.drag.node!==undefined&&a.drag.node._id===this._id&&a.drag.moving},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()},getParent:function(){return this.parent},getLayer:function(){return this.nodeType==="Layer"?this:this.getParent().getLayer()},getStage:function(){return this.nodeType!=="Stage"&&this.getParent()?this.getParent().getStage():this.nodeType==="Stage"?this:undefined},simulate:function(a){this._handleEvent(a,{})},transitionTo:function(a){var b=Kinetic.Animation;this.transAnim&&(b._removeAnimation(this.transAnim),this.transAnim=null);var c=this.nodeType==="Stage"?this:this.getLayer(),d=this,e=new Kinetic.Transition(this,a),f={func:function(){e._onEnterFrame()},node:c};return this.transAnim=f,b._addAnimation(f),e.onFinished=function(){b._removeAnimation(f),d.transAnim=null,a.callback!==undefined&&a.callback(),f.node.draw()},e.start(),b._handleAnimation(),e},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=0&&!b.cancelBubble&&this.parent&&this._handleEvent.call(this.parent,a,b)}}}),Kinetic.Node.addSetters=function(constructor,a){for(var b=0;b0)this.remove(this.children[0])},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){var c=Kinetic.Global;c.tempNodes.push(a)}else{b._addId(a),b._addName(a);var c=Kinetic.Global;c._pullNodes(b)}return this._add!==undefined&&this._add(a),this},remove:function(a){if(a&&a.index!==undefined&&this.children[a.index]._id==a._id){var b=this.getStage();b!==undefined&&(b._removeId(a.getId()),b._removeName(a.getName(),a._id));var c=Kinetic.Global;for(var d=0;d0)a.remove(a.children[0]);this._remove!==undefined&&this._remove(a)}return this},get:function(a){var b=this.getStage(),c,d=a.slice(1);if(a.charAt(0)==="#")c=b.ids[d]!==undefined?[b.ids[d]]:[];else{if(a.charAt(0)!==".")return a==="Shape"||a==="Group"||a==="Layer"?this._getNodes(a):!1;c=b.names[d]!==undefined?b.names[d]:[]}var e=[];for(var f=0;f=0;d--){var e=c[d];if(e.getListening())if(e.nodeType==="Shape"){var f=this._detectEvent +(e,b);if(f)return!0}else{var f=this._traverseChildren(e,b);if(f)return!0}}return!1},_handleStageEvent:function(a){var b=Kinetic.Global;a||(a=window.event),this._setMousePosition(a),this._setTouchPosition(a),this.pathCanvas.clear(),this.targetFound=!1;var c=!1;for(var d=this.children.length-1;d>=0;d--){var e=this.children[d];if(e.isVisible()&&d>=0&&e.getListening()&&this._traverseChildren(e,a)){c=!0;break}}!c&&this.mouseoutShape&&(this.mouseoutShape._handleEvent("mouseout",a),this.mouseoutShape=undefined)},_bindContentEvents:function(){var a=Kinetic.Global,b=this,c=["mousedown","mousemove","mouseup","mouseover","mouseout","touchstart","touchmove","touchend"];for(var d=0;d=h||e>200){var i=b.getUserPosition(),j=d.attrs.dragConstraint,k=d.attrs.dragBounds,l={x:d.attrs.x,y:d.attrs.y},m={x:i.x-c.drag.offset.x,y:i.y-c.drag.offset.y};k.left!==undefined&&m.xk.right&&(m.x=k.right),k.top!==undefined&&m.yk.bottom&&(m.y=k.bottom),d.setAbsolutePosition(m),j==="horizontal"?d.attrs.y=l.y:j==="vertical"&&(d.attrs.x=l.x),c.drag.node.nodeType==="Stage"?c.drag.node.draw():c.drag.node.getLayer().draw(),c.drag.moving||(c.drag.moving=!0,c.drag.node._handleEvent("dragstart",a)),c.drag.node._handleEvent("dragmove",a),d.lastDragTime=(new Date).getTime()}}},_buildDOM:function(){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.pathCanvas=new Kinetic.Canvas({width:this.attrs.width,height:this.attrs.height}),this.pathCanvas.strip(),this._resizeDOM()},_addId:function(a){a.attrs.id!==undefined&&(this.ids[a.attrs.id]=a)},_removeId:function(a){a!==undefined&&delete this.ids[a]},_addName:function(a){var b=a.attrs.name;b!==undefined&&(this.names[b]===undefined&&(this.names[b]=[]),this.names[b].push(a))},_removeName:function(a,b){if(a!==undefined){var c=this.names[a];if(c!==undefined){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.Node.addGettersSetters(Kinetic.Text,["fontFamily","fontSize","fontStyle","textFill","textStroke","textStrokeWidth","padding","align","lineHeight","text","width","height","cornerRadius","fill","stroke","strokeWidth","shadow"]),Kinetic.Line=Kinetic.Shape.extend({init:function(a){this.setDefaultAttrs({points:[],lineCap:"butt",dashArray:[],detectionType:"pixel"}),this.shapeType="Line",a.drawFunc=this.drawFunc,this._super(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.attrs.lineCap||(a.lineCap=this.attrs.lineCap),this.stroke(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.Node.addGettersSetters(Kinetic.Line,["dashArray","lineCap","points"]),Kinetic.Sprite=Kinetic.Shape.extend({init:function(a){this.setDefaultAttrs({index:0,frameRate:17}),a.drawFunc=this.drawFunc,this._super(a);var b=this;this.on("animationChange.kinetic",function(){b.setIndex(0)})},drawFunc:function(a){if(this.attrs.image){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.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(),c=Kinetic.Animation;this.anim&&(c._removeAnimation(this.anim),this.anim=null),this.anim={node:b},c._addAnimation(this.anim),this.interval=setInterval(function(){var b=a.attrs.index;a._updateIndex(),a.afterFrameFunc&&b===a.afterFrameIndex&&a.afterFrameFunc()},1e3/this.attrs.frameRate),c._handleAnimation()},stop:function(){var a=Kinetic.Animation;this.anim&&(a._removeAnimation(this.anim),this.anim=null),clearInterval(this.interval)},afterFrame:function(a,b){this.afterFrameIndex=a,this.afterFrameFunc=b},_updateIndex:function(){var a=this.attrs.index,b=this.attrs.animation;a 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; + }, + calcLength: function(x, y, cmd, points) { + var len, p1, p2; + var g = Kinetic.Geometry; + + switch (cmd) { + case 'L': + return g.getLineLength(x, y, points[0], points[1]); + case 'C': + // Approximates by breaking curve into 100 line segments + len = 0.0; + p1 = g.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 = g.getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); + len += g.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 = g.getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]); + for( t = 0.01; t <= 1; t += 0.01) { + p2 = g.getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]); + len += g.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 = g.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); + if(dTheta < 0) {// clockwise + for( t = start - inc; t > end; t -= inc) { + p2 = g.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + len += g.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } + else {// counter-clockwise + for( t = start + inc; t < end; t += inc) { + p2 = g.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + len += g.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } + p2 = g.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); + len += g.getLineLength(p1.x, p1.y, p2.x, p2.y); + + return len; + } + + return 0; + }, + 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]; } }); diff --git a/src/plugins/shapes/TextPath.js b/src/plugins/shapes/TextPath.js index 01b32636..2bec925c 100644 --- a/src/plugins/shapes/TextPath.js +++ b/src/plugins/shapes/TextPath.js @@ -10,7 +10,6 @@ */ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ init: function(config) { - this.setDefaultAttrs({ fontFamily: 'Calibri', fontSize: 12, @@ -25,14 +24,11 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ var that = this; config.drawFunc = this.drawFunc; - // call super constructor this._super(config); - - this.dataArray = Kinetic.Plugins.PathHelper.parsePathData(this.attrs.data); - + this.dataArray = this.parsePathData(this.attrs.data); this.on('dataChange', function() { - that.dataArray = Kinetic.Plugins.PathHelper.parsePathData(that.attrs.data); + that.dataArray = this.parsePathData(this.attrs.data); }); // update text data for certain attr changes var attrs = ['text', 'textStroke', 'textStrokeWidth']; @@ -40,7 +36,6 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ var attr = attrs[n]; this.on(attr + 'Change', that._setTextData); } - that._setTextData(); }, drawFunc: function(context) { @@ -83,6 +78,9 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ context.restore(); }, + parsePathData: Kinetic.Plugins.Path.prototype.parsePathData, + calcLength: Kinetic.Plugins.Path.prototype.calcLength, + convertEndpointToCenterParameterization: Kinetic.Plugins.Path.prototype.convertEndpointToCenterParameterization, /** * get text width in pixels * @name getTextWidth @@ -181,8 +179,8 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ switch (pathCmd.command) { case 'L': - if(Kinetic.Plugins.PathHelper.getLineLength(p0.x, p0.y, pathCmd.points[0], pathCmd.points[1]) > glyphWidth) { - p1 = Kinetic.Plugins.PathHelper.getPointOnLine(glyphWidth, p0.x, p0.y, pathCmd.points[0], pathCmd.points[1], p0.x, p0.y); + if(Kinetic.Geometry.getLineLength(p0.x, p0.y, pathCmd.points[0], pathCmd.points[1]) > glyphWidth) { + p1 = Kinetic.Geometry.getPointOnLine(glyphWidth, p0.x, p0.y, pathCmd.points[0], pathCmd.points[1], p0.x, p0.y); } else pathCmd = undefined; @@ -207,7 +205,7 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ currentT = end; needNewSegment = true; } - p1 = Kinetic.Plugins.PathHelper.getPointOnEllipticalArc(pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], currentT, pathCmd.points[6]); + p1 = Kinetic.Geometry.getPointOnEllipticalArc(pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], currentT, pathCmd.points[6]); break; case 'C': if(currentT === 0) { @@ -225,7 +223,7 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ currentT = 1.0; needNewSegment = true; } - p1 = Kinetic.Plugins.PathHelper.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]); + p1 = Kinetic.Geometry.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) @@ -239,13 +237,13 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ currentT = 1.0; needNewSegment = true; } - p1 = Kinetic.Plugins.PathHelper.getPointOnQuadraticBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3]); + p1 = Kinetic.Geometry.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.Plugins.PathHelper.getLineLength(p0.x, p0.y, p1.x, p1.y); + currLen = Kinetic.Geometry.getLineLength(p0.x, p0.y, p1.x, p1.y); } if(needNewSegment) { @@ -262,7 +260,7 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ if(p0 === undefined || p1 === undefined) break; - var width = Kinetic.Plugins.PathHelper.getLineLength(p0.x, p0.y, p1.x, p1.y); + var width = Kinetic.Geometry.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. @@ -270,7 +268,7 @@ Kinetic.Plugins.TextPath = Kinetic.Shape.extend({ var kern = 0; // placeholder for future implementation - var midpoint = Kinetic.Plugins.PathHelper.getPointOnLine(kern + width / 2.0, p0.x, p0.y, p1.x, p1.y); + var midpoint = Kinetic.Geometry.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({ diff --git a/src/plugins/util/PathHelper.js b/src/plugins/util/PathHelper.js deleted file mode 100644 index 9c653471..00000000 --- a/src/plugins/util/PathHelper.js +++ /dev/null @@ -1,495 +0,0 @@ -/////////////////////////////////////////////////////////////////////// -// PathHelper -/////////////////////////////////////////////////////////////////////// -Kinetic.Plugins.PathHelper = { - - /** - * 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 - */ - 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; - }, - calcLength : function (x, y, cmd, points) { - var len, - p1, - p2; - - switch (cmd) { - case 'L': - return this.getLineLength(x, y, points[0], points[1]); - case 'C': - // Approximates by breaking curve into 100 line segments - len = 0.0; - p1 = this.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 = this.getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); - len += this.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 = this.getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]); - for (t = 0.01; t <= 1; t += 0.01) { - p2 = this.getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]); - len += this.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 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); - if (dTheta < 0) { // clockwise - for (t = start - inc; t > end; t -= inc) { - p2 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); - len += this.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - } else { // counter-clockwise - for (t = start + inc; t < end; t += inc) { - p2 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); - len += this.getLineLength(p1.x, p1.y, p2.x, p2.y); - p1 = p2; - } - } - - p2 = this.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); - len += this.getLineLength(p1.x, p1.y, p2.x, p2.y); - - return len; - } - - return 0; - }, - getLineLength : function (x1, y1, x2, y2) { - - return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); - }, - 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)); - 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)); - rise = m * run; - - pt = { - x : ix + run, - y : iy + rise - }; - } - - return pt; - }, - 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 - }; - }, - 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 - }; - }, - 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) - }; - }, - 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]; - } - -}; diff --git a/src/shapes/Ellipse.js b/src/shapes/Ellipse.js index 93c7b285..46e93702 100644 --- a/src/shapes/Ellipse.js +++ b/src/shapes/Ellipse.js @@ -21,9 +21,7 @@ Kinetic.Ellipse = Kinetic.Shape.extend({ // call super constructor this._super(config); - this._convertRadius(); - var that = this; this.on('radiusChange.kinetic', function() { that._convertRadius(); diff --git a/src/shapes/Sprite.js b/src/shapes/Sprite.js index 9d2494da..cc3c8f15 100644 --- a/src/shapes/Sprite.js +++ b/src/shapes/Sprite.js @@ -17,7 +17,6 @@ Kinetic.Sprite = Kinetic.Shape.extend({ config.drawFunc = this.drawFunc; // call super constructor this._super(config); - var that = this; this.on('animationChange.kinetic', function() { // reset index when animation changes diff --git a/src/shapes/Text.js b/src/shapes/Text.js index b6f7a723..5bd4ba52 100644 --- a/src/shapes/Text.js +++ b/src/shapes/Text.js @@ -38,7 +38,6 @@ Kinetic.Text = Kinetic.Shape.extend({ var attr = attrs[n]; this.on(attr + 'Change.kinetic', that._setTextData); } - that._setTextData(); }, drawFunc: function(context) { diff --git a/src/util/Geometry.js b/src/util/Geometry.js new file mode 100644 index 00000000..40bd2a5d --- /dev/null +++ b/src/util/Geometry.js @@ -0,0 +1,102 @@ +/* + * Utility methods written by jfollas to + * handle length and point measurements + */ +Kinetic.Geometry = { + getLineLength: function(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + }, + 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)); + 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)); + rise = m * run; + pt = { + x: ix + run, + y: iy + rise + }; + } + + return pt; + }, + 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 + }; + }, + 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 + }; + }, + 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) + }; + } +}; diff --git a/tests/html/performanceTests.html b/tests/html/performanceTests.html index 97e2c403..233560fd 100644 --- a/tests/html/performanceTests.html +++ b/tests/html/performanceTests.html @@ -11,6 +11,7 @@ +