diff --git a/Thorfile b/Thorfile index e0f2e72b..ea6120d2 100644 --- a/Thorfile +++ b/Thorfile @@ -4,9 +4,9 @@ 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/Tween.js", "src/util/Transform.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", "src/shapes/Star.js", "src/shapes/RegularPolygon.js", "src/shapes/Path.js", "src/shapes/TextPath.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", "src/shapes/Star.js", "src/shapes/RegularPolygon.js", "src/shapes/Path.js", "src/shapes/TextPath.js" ] desc "dev", "Concatenate all the js files into /dist/kinetic-VERSION.js." diff --git a/dist/kinetic-core.js b/dist/kinetic-core.js index 66d60df8..1518bba8 100644 --- a/dist/kinetic-core.js +++ b/dist/kinetic-core.js @@ -584,68 +584,6 @@ Kinetic.Canvas.prototype = { } }; -/////////////////////////////////////////////////////////////////////// -// Class -/////////////////////////////////////////////////////////////////////// -/* Simple JavaScript Inheritance -* By John Resig http://ejohn.org/ -* MIT Licensed. -*/ -// Inspired by base2 and Prototype -(function() { - var initializing = false; - // The base Class implementation (does nothing) - Kinetic.Class = function() { - }; - // Create a new Class that inherits from this class - Kinetic.Class.extend = function(prop) { - var _super = this.prototype; - - // Instantiate a base class (but only create the instance, - // don't run the init constructor) - initializing = true; - var prototype = new this(); - initializing = false; - - // Copy the properties over onto the new prototype - for(var name in prop) { - // Check if we're overwriting an existing function - prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" ? (function(name, fn) { - return function() { - var tmp = this._super; - - // Add a new ._super() method that is the same method - // but on the super-class - this._super = _super[name]; - - // The method only need to be bound temporarily, so we - // remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; - - return ret; - }; - })(name, prop[name]) : prop[name]; - } - - // The dummy class constructor - function Class() { - // All construction is actually done in the init method - if(!initializing && this.init) - this.init.apply(this, arguments); - } - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.prototype.constructor = Class; - - // And make this class extendable - Class.extend = arguments.callee; - - return Class; - }; -})(); /////////////////////////////////////////////////////////////////////// // Tween /////////////////////////////////////////////////////////////////////// @@ -2714,11 +2652,11 @@ Kinetic.Global.extend(Kinetic.Container, Kinetic.Node); * @param {Number} [config.dragBounds.left] */ Kinetic.Stage = function(config) { - this._stageInit(config); + this._initStage(config); }; Kinetic.Stage.prototype = { - _stageInit: function(config) { + _initStage: function(config) { this.setDefaultAttrs({ width: 400, height: 200 @@ -3563,11 +3501,11 @@ Kinetic.Node.addGettersSetters(Kinetic.Stage, ['width', 'height']); * @param {Number} [config.dragBounds.left] */ Kinetic.Layer = function(config) { - this._layerInit(config); + this._initLayer(config); }; Kinetic.Layer.prototype = { - _layerInit: function(config) { + _initLayer: function(config) { this.setDefaultAttrs({ clearBeforeDraw: true }); @@ -3757,11 +3695,11 @@ Kinetic.Node.addGettersSetters(Kinetic.Layer, ['clearBeforeDraw']); * @param {Number} [config.dragBounds.left] */ Kinetic.Group = function(config) { - this._groupInit(config); + this._initGroup(config); }; Kinetic.Group.prototype = { - _groupInit: function(config) { + _initGroup: function(config) { this.nodeType = 'Group'; // call super constructor @@ -3832,11 +3770,11 @@ Kinetic.Global.extend(Kinetic.Group, Kinetic.Container); * @param {Number} [config.dragBounds.left] */ Kinetic.Shape = function(config) { - this._shapeInit(config); + this._initShape(config); }; Kinetic.Shape.prototype = { - _shapeInit: function(config) { + _initShape: function(config) { this.nodeType = 'Shape'; this.appliedShadow = false; @@ -4286,10 +4224,10 @@ Kinetic.Node.addGettersSetters(Kinetic.Shape, ['fill', 'stroke', 'lineJoin', 'st * @param {Object} config */ Kinetic.Rect = function(config) { - this._rectInit(config); + this._initRect(config); } Kinetic.Rect.prototype = { - _rectInit: function(config) { + _initRect: function(config) { this.setDefaultAttrs({ width: 0, height: 0, @@ -4387,3 +4325,2167 @@ Kinetic.Node.addGettersSetters(Kinetic.Rect, ['width', 'height', 'cornerRadius'] * @name getCornerRadius * @methodOf Kinetic.Rect.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); + this._convertRadius(); + var that = this; + this.on('radiusChange.kinetic', function() { + that._convertRadius(); + }); + }, + 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); + }, + /** + * converts numeric radius into an object + */ + _convertRadius: function() { + var type = Kinetic.Type; + var radius = this.getRadius(); + // if radius is already an object then return + if(type._isObject(radius)) { + return false; + } + + /* + * directly set radius attr to avoid + * duplicate attr change event + */ + this.attrs.radius = type._getXY(radius); + } +}; +Kinetic.Global.extend(Kinetic.Ellipse, Kinetic.Shape); + +// Circle backwards compatibility +Kinetic.Circle = Kinetic.Ellipse; + +// add getters setters +Kinetic.Node.addGettersSetters(Kinetic.Ellipse, ['radius']); + +/** + * set radius + * @name setRadius + * @methodOf Kinetic.Ellipse.prototype + * @param {Number|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 + */ + +/** + * 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); + } + } + }, + /** + * set width and height + * @name setSize + * @methodOf Kinetic.Image.prototype + */ + setSize: function() { + var size = Kinetic.Type._getSize(Array.prototype.slice.call(arguments)); + this.setAttrs(size); + }, + /** + * return image size + * @name getSize + * @methodOf Kinetic.Image.prototype + */ + getSize: function() { + return { + width: this.attrs.width, + height: this.attrs.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.'); + } + }, + /** + * 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.setAttrs({ + width: this.attrs.image.width + }); + } + if(!this.attrs.height) { + this.setAttrs({ + height: this.attrs.image.height + }); + } + } + } +}; +Kinetic.Global.extend(Kinetic.Image, Kinetic.Shape); + +// add getters setters +Kinetic.Node.addGettersSetters(Kinetic.Image, ['image', 'crop', 'filter', 'width', 'height']); + +/** + * set width + * @name setWidth + * @methodOf Kinetic.Image.prototype + * @param {Number} width + */ + +/** + * set height + * @name setHeight + * @methodOf Kinetic.Image.prototype + * @param {Number} height + */ + +/** + * set image + * @name setImage + * @methodOf Kinetic.Image.prototype + * @param {ImageObject} image + */ + +/** + * set crop + * @name setCrop + * @methodOf Kinetic.Image.prototype + * @param {Object} config + */ + +/** + * set filter + * @name setFilter + * @methodOf Kinetic.Image.prototype + * @param {Object} config + */ + +/** + * get crop + * @name getCrop + * @methodOf Kinetic.Image.prototype + */ + +/** + * get image + * @name getImage + * @methodOf Kinetic.Image.prototype + */ + +/** + * get filter + * @name getFilter + * @methodOf Kinetic.Image.prototype + */ + +/** + * get width + * @name getWidth + * @methodOf Kinetic.Image.prototype + */ + +/** + * get height + * @name getHeight + * @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); + } +}; +Kinetic.Global.extend(Kinetic.Polygon, Kinetic.Shape); + +// add getters setters +Kinetic.Node.addGettersSetters(Kinetic.Polygon, ['points']); + +/** + * set points array + * @name setPoints + * @methodOf Kinetic.Polygon.prototype + * @param {Array} points 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] + */ + +/** + * 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.getBoxWidth(); + var boxHeight = this.getBoxHeight(); + + 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 + for(var n = 0; n < textArr.length; n++) { + var text = textArr[n]; + + // horizontal alignment + context.save(); + if(this.attrs.align === 'right') { + context.translate(this.getBoxWidth() - this._getTextSize(text).width - p * 2, 0); + } + else if(this.attrs.align === 'center') { + context.translate((this.getBoxWidth() - this._getTextSize(text).width - p * 2) / 2, 0); + } + + this.fillText(context, text); + this.strokeText(context, text); + context.restore(); + + context.translate(0, lineHeightPx); + } + context.restore(); + }, + /** + * get box width + * @name getBoxWidth + * @methodOf Kinetic.Text.prototype + */ + getBoxWidth: function() { + return this.attrs.width === 'auto' ? this.getTextWidth() + this.attrs.padding * 2 : this.attrs.width; + }, + /** + * get box height + * @name getBoxHeight + * @methodOf Kinetic.Text.prototype + */ + getBoxHeight: function() { + return this.attrs.height === 'auto' ? (this.getTextHeight() * this.textArr.length * this.attrs.lineHeight) + this.attrs.padding * 2 : this.attrs.height; + }, + /** + * get text width in pixels + * @name getTextWidth + * @methodOf Kinetic.Text.prototype + */ + getTextWidth: function() { + return this.textWidth; + }, + /** + * get text height in pixels + * @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', 'text', 'width', 'height', 'cornerRadius', 'fill', 'stroke', 'strokeWidth', 'shadow']); + +/** + * 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 + */ + +/** + * set text + * @name setText + * @methodOf Kinetic.Text.prototype + * @param {String} text + */ + +/** + * set width of text box + * @name setWidth + * @methodOf Kinetic.Text.prototype + * @param {Number} width + */ + +/** + * set height of text box + * @name setHeight + * @methodOf Kinetic.Text.prototype + * @param {Number} height + */ + +/** + * set shadow of text or textbox + * @name setShadow + * @methodOf Kinetic.Text.prototype + * @param {Object} config + */ + +/** + * 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 + */ + +/** + * get width of text box + * @name getWidth + * @methodOf Kinetic.Text.prototype + */ + +/** + * get height of text box + * @name getHeight + * @methodOf Kinetic.Text.prototype + */ + +/** + * get shadow of text or textbox + * @name getShadow + * @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); + } + } + + if(!!this.attrs.lineCap) { + context.lineCap = this.attrs.lineCap; + } + + this.stroke(context); + }, + /** + * 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', 'lineCap', '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 + */ + +/** + * set line cap. Can be butt, round, or square + * @name setLineCap + * @methodOf Kinetic.Line.prototype + * @param {String} lineCap + */ + +/** + * 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] + */ + +/** + * get dash array + * @name getDashArray + * @methodOf Kinetic.Line.prototype + */ + +/** + * get line cap + * @name getLineCap + * @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(); + } + }, 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 obect + * @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)); + 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; +}; + +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; + + for(var i = 0; i < glyphInfo.length; i++) { + 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; + }, + _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', '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 + */ + +/** + * set text + * @name setText + * @methodOf Kinetic.TextPath.prototype + * @param {String} text + */ + +/** + * 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-core.min.js b/dist/kinetic-core.min.js index 8676af15..2101dbbc 100644 --- a/dist/kinetic-core.min.js +++ b/dist/kinetic-core.min.js @@ -25,5 +25,5 @@ * 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"],BUFFER_WHITELIST:["fill","stroke","textFill","textStroke"],BUFFER_BLACKLIST:["shadow"],stages:[],idCounter:0,tempNodes:{},shapes:{},maxDragTimeInterval:20,drag:{moving:!1,offset:{x:0,y:0},lastDrawTime:0},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.Transition=function(a,b){function d(a,b,e,f){for(var g in a)g!=="duration"&&g!=="easing"&&g!=="callback"&&(Kinetic.Type._isObject(a[g])?(e[g]={},d(a[g],b[g],e[g],f)):c._add(c._getTween(b,g,a[g],e,f)))}this.node=a,this.config=b,this.tweens=[];var c=this,e={};d(b,a.attrs,e,e);var f=0;for(var g=0;g=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;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)}},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)},strip:function(){var a=this.context},toDataURL:function(a,b){try{return this.element.toDataURL(a,b)}catch(c){return this.element.toDataURL()}}},function(){var a=!1;Kinetic.Class=function(){},Kinetic.Class.extend=function(b){function f(){!a&&this.init&&this.init.apply(this,arguments)}var c=this.prototype;a=!0;var d=new this;a=!1;for(var e in b)d[e]=typeof b[e]=="function"&&typeof c[e]=="function"?function(a,b){return function(){var d=this._super;this._super=c[a];var e=b.apply(this,arguments);return this._super=d,e}}(e,b[e]):b[e];return f.prototype=d,f.prototype.constructor=f,f.extend=arguments.callee,f}}(),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;requestAnimFrame(function(){a._animationLoop()})}else this.animRunning=!1},Kinetic.Animation._handleAnimation=function(){var a=this;this.animRunning||(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=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},dragConstraint:"none",dragBounds:{},draggable:!1},this.setDefaultAttrs(this.defaultNodeAttrs),this.eventListeners={},this.transAnim=new Kinetic.Animation,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()},getAbsoluteOpacity:function(){var a=1,b=this;while(b.nodeType!=="Stage")a*=b.attrs.opacity,b=b.parent;return a},isDragging:function(){var a=Kinetic.Global;return a.drag.node&&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=this.nodeType==="Stage"?this:this.getLayer(),c=this,d=new Kinetic.Transition(this,a);return this.transAnim.func=function(){d._onEnterFrame()},this.transAnim.node=b,d.onFinished=function(){c.transAnim.stop(),c.transAnim.node.draw(),a.callback&&a.callback()},d.start(),this.transAnim.start(),d},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&&(c&&c.parent?this._handleEvent.call(this.parent,a,b,c.parent):this._handleEvent.call(this.parent,a,b))}},_draw:function(a){if(this.isVisible()&&(!a||a.name!=="buffer"||this.getListening())){this.__draw&&this.__draw(a);var b=this.children;if(b)for(var c=0;c0)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)Kinetic.Global._addTempNode(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&&(b._removeId(a.getId()),b._removeName(a.getName(),a._id)),Kinetic.Global._removeTempNode(a),this.children.splice(a.index,1),this._setChildrenIndices();while(a.children&&a.children.length>0)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],f=e.bufferCanvas.context.getImageData(a.x,a.y,1,1).data;if(f[3]===255){var g=Kinetic.Type._rgbToHex(f[0],f[1],f[2]);b=Kinetic.Global.shapes[g];var h=Kinetic.Global.drag.moving;return{shape:b,pixel:f}}if(f[0]>0||f[1]>0||f[2]>0||f[3]>0)return{pixel:f}}return null},_resizeDOM:function(){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;dg.right&&(i.x=g.right),g.top!==undefined&&i.yg.bottom&&(i.y=g.bottom),d.setAbsolutePosition(i),f==="horizontal"?d.attrs.y=h.y:f==="vertical"&&(d.attrs.x=h.x),c.drag.moving||(c.drag.moving=!0,c.drag.node._handleEvent("dragstart",a)),c.drag.node._handleEvent("dragmove",a)}},_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._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)},__draw:function(a){if(this.attrs.drawFunc){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=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;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)}},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)},strip:function(){var a=this.context},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;requestAnimFrame(function(){a._animationLoop()})}else this.animRunning=!1},Kinetic.Animation._handleAnimation=function(){var a=this;this.animRunning||(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=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},dragConstraint:"none",dragBounds:{},draggable:!1},this.setDefaultAttrs(this.defaultNodeAttrs),this.eventListeners={},this.transAnim=new Kinetic.Animation,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()},getAbsoluteOpacity:function(){var a=1,b=this;while(b.nodeType!=="Stage")a*=b.attrs.opacity,b=b.parent;return a},isDragging:function(){var a=Kinetic.Global;return a.drag.node&&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=this.nodeType==="Stage"?this:this.getLayer(),c=this,d=new Kinetic.Transition(this,a);return this.transAnim.func=function(){d._onEnterFrame()},this.transAnim.node=b,d.onFinished=function(){c.transAnim.stop(),c.transAnim.node.draw(),a.callback&&a.callback()},d.start(),this.transAnim.start(),d},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&&(c&&c.parent?this._handleEvent.call(this.parent,a,b,c.parent):this._handleEvent.call(this.parent,a,b))}},_draw:function(a){if(this.isVisible()&&(!a||a.name!=="buffer"||this.getListening())){this.__draw&&this.__draw(a);var b=this.children;if(b)for(var c=0;c0)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)Kinetic.Global._addTempNode(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&&(b._removeId(a.getId()),b._removeName(a.getName(),a._id)),Kinetic.Global._removeTempNode(a),this.children.splice(a.index,1),this._setChildrenIndices();while(a.children&&a.children.length>0)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],f=e.bufferCanvas.context.getImageData(a.x,a.y,1,1).data;if(f[3]===255){var g=Kinetic.Type._rgbToHex(f[0],f[1],f[2]);b=Kinetic.Global.shapes[g];var h=Kinetic.Global.drag.moving;return{shape:b,pixel:f}}if(f[0]>0||f[1]>0||f[2]>0||f[3]>0)return{pixel:f}}return null},_resizeDOM:function(){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;dg.right&&(i.x=g.right),g.top!==undefined&&i.yg.bottom&&(i.y=g.bottom),d.setAbsolutePosition(i),f==="horizontal"?d.attrs.y=h.y:f==="vertical"&&(d.attrs.x=h.x),c.drag.moving||(c.drag.moving=!0,c.drag.node._handleEvent("dragstart",a)),c.drag.node._handleEvent("dragmove",a)}},_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._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)},__draw:function(a){if(this.attrs.drawFunc){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","text","width","height","cornerRadius","fill","stroke","strokeWidth","shadow"]),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.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.Global.extend(Kinetic.Line,Kinetic.Shape),Kinetic.Node.addGettersSetters(Kinetic.Line,["dashArray","lineCap","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()},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)),j=h*i,k;if((g-c)/(f-b+1e-8)===h)k={x:f+i,y:g+j};else{var l,m,n=this.getLineLength(b,c,d,e);if(n<1e-8)return undefined;var o=(f-b)*(d-b)+(g-c)*(e-c);o/=n*n,l=b+o*(d-b),m=c+o*(e-c);var p=this.getLineLength(f,g,l,m),q=Math.sqrt(a*a-p*p);i=Math.sqrt(q*q/(1+h*h)),j=h*i,k={x:l+i,y:m+j}}return k},Kinetic.Path.getPointOnCubicBezier=function(a,b,c,d,e,f,g,h,i){function j(a){return a*a*a}function k(a){return 3*a*a*(1-a)}function l(a){return 3*a*(1-a)*(1-a)}function m(a){return(1-a)*(1-a)*(1-a)}var n=h*j(a)+f*k(a)+d*l(a)+b*m(a),o=i*j(a)+g*k(a)+e*l(a)+c*m(a);return{x:n,y:o}},Kinetic.Path.getPointOnQuadraticBezier=function(a,b,c,d,e,f,g){function h(a){return a*a}function i(a){return 2*a*(1-a)}function j(a){return(1-a)*(1-a)}var k=f*h(a)+d*i(a)+b*j(a),l=g*h(a)+e*i(a)+c*j(a);return{x:k,y:l}},Kinetic.Path.getPointOnEllipticalArc=function(a,b,c,d,e,f){var g=Math.cos(f),h=Math.sin(f),i={x:c*Math.cos(e),y:d*Math.sin(e)};return{x:a+(i.x*g-i.y*h),y:b+(i.x*h+i.y*g)}},Kinetic.Path.parsePathData=function(a){if(!a)return[];var b=a,c=["m","M","l","L","v","V","h","H","z","Z","c","C","q","Q","t","T","s","S","a","A"];b=b.replace(new RegExp(" ","g"),",");for(var d=0;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