diff --git a/konva.js b/konva.js index 6a2e2d8e3..cce88dc81 100644 --- a/konva.js +++ b/konva.js @@ -1,17994 +1,17994 @@ -/* - * Konva JavaScript Framework v1.4.0 - * http://konvajs.github.io/ - * Licensed under the MIT or GPL Version 2 licenses. - * Date: Fri Feb 24 2017 - * - * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) - * Modified work Copyright (C) 2014 - 2015 by Anton Lavrenov (Konva) - * - * @license - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// runtime check for already included Konva -(function(global) { - 'use strict'; - /** - * @namespace Konva - */ - - var PI_OVER_180 = Math.PI / 180; - - var Konva = { - // public - version: '1.4.0', - - // private - stages: [], - idCounter: 0, - ids: {}, - names: {}, - shapes: {}, - listenClickTap: false, - inDblClickWindow: false, - - // configurations - enableTrace: false, - traceArrMax: 100, - dblClickWindow: 400, - /** - * Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device. - * But you may override such property, if you want to use your value. - * @property pixelRatio - * @default undefined - * @memberof Konva - * @example - * Konva.pixelRatio = 1; - */ - pixelRatio: undefined, - /** - * Drag distance property. If you start to drag a node you may want to wait until pointer is moved to some distance from start point, - * only then start dragging. - * @property dragDistance - * @default 0 - * @memberof Konva - * @example - * Konva.dragDistance = 10; - */ - dragDistance: 0, - /** - * Use degree values for angle properties. You may set this property to false if you want to use radiant values. - * @property angleDeg - * @default true - * @memberof Konva - * @example - * node.rotation(45); // 45 degrees - * Konva.angleDeg = false; - * node.rotation(Math.PI / 2); // PI/2 radian - */ - angleDeg: true, - /** - * Show different warnings about errors or wrong API usage - * @property showWarnings - * @default true - * @memberof Konva - * @example - * Konva.showWarnings = false; - */ - showWarnings: true, - - /** - * @namespace Filters - * @memberof Konva - */ - Filters: {}, - - /** - * returns whether or not drag and drop is currently active - * @method - * @memberof Konva - */ - isDragging: function() { - var dd = Konva.DD; - - // if DD is not included with the build, then - // drag and drop is not even possible - if (dd) { - return dd.isDragging; - } - return false; - }, - /** - * returns whether or not a drag and drop operation is ready, but may - * not necessarily have started - * @method - * @memberof Konva - */ - isDragReady: function() { - var dd = Konva.DD; - - // if DD is not included with the build, then - // drag and drop is not even possible - if (dd) { - return !!dd.node; - } - return false; - }, - _addId: function(node, id) { - if (id !== undefined) { - this.ids[id] = node; - } - }, - _removeId: function(id) { - if (id !== undefined) { - delete this.ids[id]; - } - }, - _addName: function(node, name) { - if (name) { - if (!this.names[name]) { - this.names[name] = []; - } - this.names[name].push(node); - } - }, - _removeName: function(name, _id) { - if (!name) { - return; - } - var nodes = this.names[name]; - if (!nodes) { - return; - } - for (var n = 0; n < nodes.length; n++) { - var no = nodes[n]; - if (no._id === _id) { - nodes.splice(n, 1); - } - } - if (nodes.length === 0) { - delete this.names[name]; - } - }, - getAngle: function(angle) { - return this.angleDeg ? angle * PI_OVER_180 : angle; - }, - _detectIE: function(ua) { - var msie = ua.indexOf('msie '); - if (msie > 0) { - // IE 10 or older => return version number - return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); - } - - var trident = ua.indexOf('trident/'); - if (trident > 0) { - // IE 11 => return version number - var rv = ua.indexOf('rv:'); - return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); - } - - var edge = ua.indexOf('edge/'); - if (edge > 0) { - // Edge (IE 12+) => return version number - return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); - } - - // other browser - return false; - }, - _parseUA: function(userAgent) { - var ua = userAgent.toLowerCase(), - // jQuery UA regex - match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - (ua.indexOf('compatible') < 0 && - /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)) || [], - // adding mobile flag as well - mobile = !!userAgent.match( - /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i - ), - ieMobile = !!userAgent.match(/IEMobile/i); - - return { - browser: match[1] || '', - version: match[2] || '0', - isIE: Konva._detectIE(ua), - // adding mobile flab - mobile: mobile, - ieMobile: ieMobile // If this is true (i.e., WP8), then Konva touch events are executed instead of equivalent Konva mouse events - }; - }, - // user agent - UA: undefined - }; - - var glob = typeof global !== 'undefined' - ? global - : typeof window !== 'undefined' - ? window - : typeof WorkerGlobalScope !== 'undefined' ? self : {}; - - Konva.UA = Konva._parseUA((glob.navigator && glob.navigator.userAgent) || ''); - - if (glob.Konva) { - console.error( - 'Konva instance is already exist in current eviroment. ' + - 'Please use only one instance.' - ); - } - glob.Konva = Konva; - Konva.global = glob; - - if (typeof exports === 'object') { - // runtime-check for browserify and nw.js (node-webkit) - if (glob.window && glob.window.document) { - Konva.document = glob.window.document; - Konva.window = glob.window; - } else { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like enviroments that support module.exports, - // like Node. - var Canvas = require('canvas'); - var jsdom = require('jsdom').jsdom; - - Konva.window = jsdom( - '' - ).defaultView; - Konva.document = Konva.window.document; - Konva.window.Image = Canvas.Image; - Konva._nodeCanvas = Canvas; - } - module.exports = Konva; - return; - } else if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(function() { - return Konva; - }); - } - Konva.document = document; - Konva.window = window; -})(typeof global !== 'undefined' ? global : window); - -/*eslint-disable eqeqeq, no-cond-assign, no-empty*/ -(function() { - 'use strict'; - /** - * Collection constructor. Collection extends - * Array. This class is used in conjunction with {@link Konva.Container#get} - * @constructor - * @memberof Konva - */ - Konva.Collection = function() { - var args = [].slice.call(arguments), length = args.length, i = 0; - - this.length = length; - for (; i < length; i++) { - this[i] = args[i]; - } - return this; - }; - Konva.Collection.prototype = []; - /** - * iterate through node array and run a function for each node. - * The node and index is passed into the function - * @method - * @memberof Konva.Collection.prototype - * @param {Function} func - * @example - * // get all nodes with name foo inside layer, and set x to 10 for each - * layer.get('.foo').each(function(shape, n) { - * shape.setX(10); - * }); - */ - Konva.Collection.prototype.each = function(func) { - for (var n = 0; n < this.length; n++) { - func(this[n], n); - } - }; - /** - * convert collection into an array - * @method - * @memberof Konva.Collection.prototype - */ - Konva.Collection.prototype.toArray = function() { - var arr = [], len = this.length, n; - - for (n = 0; n < len; n++) { - arr.push(this[n]); - } - return arr; - }; - /** - * convert array into a collection - * @method - * @memberof Konva.Collection - * @param {Array} arr - */ - Konva.Collection.toCollection = function(arr) { - var collection = new Konva.Collection(), len = arr.length, n; - - for (n = 0; n < len; n++) { - collection.push(arr[n]); - } - return collection; - }; - - // map one method by it's name - Konva.Collection._mapMethod = function(methodName) { - Konva.Collection.prototype[methodName] = function() { - var len = this.length, i; - - var args = [].slice.call(arguments); - for (i = 0; i < len; i++) { - this[i][methodName].apply(this[i], args); - } - - return this; - }; - }; - - Konva.Collection.mapMethods = function(constructor) { - var prot = constructor.prototype; - for (var methodName in prot) { - Konva.Collection._mapMethod(methodName); - } - }; - - /* - * Last updated November 2011 - * By Simon Sarris - * www.simonsarris.com - * sarris@acm.org - * - * Free to use and distribute at will - * So long as you are nice to people, etc - */ - - /* - * The usage of this class was inspired by some of the work done by a forked - * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform - * class. Modified by Eric Rowell - */ - - /** - * Transform constructor - * @constructor - * @param {Array} [m] Optional six-element matrix - * @memberof Konva - */ - Konva.Transform = function(m) { - this.m = (m && m.slice()) || [1, 0, 0, 1, 0, 0]; - }; - - Konva.Transform.prototype = { - /** - * Copy Konva.Transform object - * @method - * @memberof Konva.Transform.prototype - * @returns {Konva.Transform} - */ - copy: function() { - return new Konva.Transform(this.m); - }, - /** - * Transform point - * @method - * @memberof Konva.Transform.prototype - * @param {Object} point 2D point(x, y) - * @returns {Object} 2D point(x, y) - */ - point: function(point) { - var m = this.m; - return { - x: m[0] * point.x + m[2] * point.y + m[4], - y: m[1] * point.x + m[3] * point.y + m[5] - }; - }, - /** - * Apply translation - * @method - * @memberof Konva.Transform.prototype - * @param {Number} x - * @param {Number} y - * @returns {Konva.Transform} - */ - translate: function(x, y) { - this.m[4] += this.m[0] * x + this.m[2] * y; - this.m[5] += this.m[1] * x + this.m[3] * y; - return this; - }, - /** - * Apply scale - * @method - * @memberof Konva.Transform.prototype - * @param {Number} sx - * @param {Number} sy - * @returns {Konva.Transform} - */ - scale: function(sx, sy) { - this.m[0] *= sx; - this.m[1] *= sx; - this.m[2] *= sy; - this.m[3] *= sy; - return this; - }, - /** - * Apply rotation - * @method - * @memberof Konva.Transform.prototype - * @param {Number} rad Angle in radians - * @returns {Konva.Transform} - */ - rotate: function(rad) { - var c = Math.cos(rad); - var s = Math.sin(rad); - var m11 = this.m[0] * c + this.m[2] * s; - var m12 = this.m[1] * c + this.m[3] * s; - var m21 = this.m[0] * (-s) + this.m[2] * c; - var m22 = this.m[1] * (-s) + this.m[3] * c; - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - return this; - }, - /** - * Returns the translation - * @method - * @memberof Konva.Transform.prototype - * @returns {Object} 2D point(x, y) - */ - getTranslation: function() { - return { - x: this.m[4], - y: this.m[5] - }; - }, - /** - * Apply skew - * @method - * @memberof Konva.Transform.prototype - * @param {Number} sx - * @param {Number} sy - * @returns {Konva.Transform} - */ - skew: function(sx, sy) { - var m11 = this.m[0] + this.m[2] * sy; - var m12 = this.m[1] + this.m[3] * sy; - var m21 = this.m[2] + this.m[0] * sx; - var m22 = this.m[3] + this.m[1] * sx; - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - return this; - }, - /** - * Transform multiplication - * @method - * @memberof Konva.Transform.prototype - * @param {Konva.Transform} matrix - * @returns {Konva.Transform} - */ - multiply: function(matrix) { - var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1]; - var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1]; - - var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3]; - var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3]; - - var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4]; - var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5]; - - this.m[0] = m11; - this.m[1] = m12; - this.m[2] = m21; - this.m[3] = m22; - this.m[4] = dx; - this.m[5] = dy; - return this; - }, - /** - * Invert the matrix - * @method - * @memberof Konva.Transform.prototype - * @returns {Konva.Transform} - */ - invert: function() { - var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]); - var m0 = this.m[3] * d; - var m1 = (-this.m[1]) * d; - var m2 = (-this.m[2]) * d; - var m3 = this.m[0] * d; - var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]); - var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]); - this.m[0] = m0; - this.m[1] = m1; - this.m[2] = m2; - this.m[3] = m3; - this.m[4] = m4; - this.m[5] = m5; - return this; - }, - /** - * return matrix - * @method - * @memberof Konva.Transform.prototype - */ - getMatrix: function() { - return this.m; - }, - /** - * set to absolute position via translation - * @method - * @memberof Konva.Transform.prototype - * @returns {Konva.Transform} - * @author ericdrowell - */ - setAbsolutePosition: function(x, y) { - var m0 = this.m[0], - m1 = this.m[1], - m2 = this.m[2], - m3 = this.m[3], - m4 = this.m[4], - m5 = this.m[5], - yt = (m0 * (y - m5) - m1 * (x - m4)) / (m0 * m3 - m1 * m2), - xt = (x - m4 - m2 * yt) / m0; - - return this.translate(xt, yt); - } - }; - - // CONSTANTS - var CONTEXT_2D = '2d', - OBJECT_ARRAY = '[object Array]', - OBJECT_NUMBER = '[object Number]', - OBJECT_STRING = '[object String]', - PI_OVER_DEG180 = Math.PI / 180, - DEG180_OVER_PI = 180 / Math.PI, - HASH = '#', - EMPTY_STRING = '', - ZERO = '0', - KONVA_WARNING = 'Konva warning: ', - KONVA_ERROR = 'Konva error: ', - RGB_PAREN = 'rgb(', - COLORS = { - aliceblue: [240, 248, 255], - antiquewhite: [250, 235, 215], - aqua: [0, 255, 255], - aquamarine: [127, 255, 212], - azure: [240, 255, 255], - beige: [245, 245, 220], - bisque: [255, 228, 196], - black: [0, 0, 0], - blanchedalmond: [255, 235, 205], - blue: [0, 0, 255], - blueviolet: [138, 43, 226], - brown: [165, 42, 42], - burlywood: [222, 184, 135], - cadetblue: [95, 158, 160], - chartreuse: [127, 255, 0], - chocolate: [210, 105, 30], - coral: [255, 127, 80], - cornflowerblue: [100, 149, 237], - cornsilk: [255, 248, 220], - crimson: [220, 20, 60], - cyan: [0, 255, 255], - darkblue: [0, 0, 139], - darkcyan: [0, 139, 139], - darkgoldenrod: [184, 132, 11], - darkgray: [169, 169, 169], - darkgreen: [0, 100, 0], - darkgrey: [169, 169, 169], - darkkhaki: [189, 183, 107], - darkmagenta: [139, 0, 139], - darkolivegreen: [85, 107, 47], - darkorange: [255, 140, 0], - darkorchid: [153, 50, 204], - darkred: [139, 0, 0], - darksalmon: [233, 150, 122], - darkseagreen: [143, 188, 143], - darkslateblue: [72, 61, 139], - darkslategray: [47, 79, 79], - darkslategrey: [47, 79, 79], - darkturquoise: [0, 206, 209], - darkviolet: [148, 0, 211], - deeppink: [255, 20, 147], - deepskyblue: [0, 191, 255], - dimgray: [105, 105, 105], - dimgrey: [105, 105, 105], - dodgerblue: [30, 144, 255], - firebrick: [178, 34, 34], - floralwhite: [255, 255, 240], - forestgreen: [34, 139, 34], - fuchsia: [255, 0, 255], - gainsboro: [220, 220, 220], - ghostwhite: [248, 248, 255], - gold: [255, 215, 0], - goldenrod: [218, 165, 32], - gray: [128, 128, 128], - green: [0, 128, 0], - greenyellow: [173, 255, 47], - grey: [128, 128, 128], - honeydew: [240, 255, 240], - hotpink: [255, 105, 180], - indianred: [205, 92, 92], - indigo: [75, 0, 130], - ivory: [255, 255, 240], - khaki: [240, 230, 140], - lavender: [230, 230, 250], - lavenderblush: [255, 240, 245], - lawngreen: [124, 252, 0], - lemonchiffon: [255, 250, 205], - lightblue: [173, 216, 230], - lightcoral: [240, 128, 128], - lightcyan: [224, 255, 255], - lightgoldenrodyellow: [250, 250, 210], - lightgray: [211, 211, 211], - lightgreen: [144, 238, 144], - lightgrey: [211, 211, 211], - lightpink: [255, 182, 193], - lightsalmon: [255, 160, 122], - lightseagreen: [32, 178, 170], - lightskyblue: [135, 206, 250], - lightslategray: [119, 136, 153], - lightslategrey: [119, 136, 153], - lightsteelblue: [176, 196, 222], - lightyellow: [255, 255, 224], - lime: [0, 255, 0], - limegreen: [50, 205, 50], - linen: [250, 240, 230], - magenta: [255, 0, 255], - maroon: [128, 0, 0], - mediumaquamarine: [102, 205, 170], - mediumblue: [0, 0, 205], - mediumorchid: [186, 85, 211], - mediumpurple: [147, 112, 219], - mediumseagreen: [60, 179, 113], - mediumslateblue: [123, 104, 238], - mediumspringgreen: [0, 250, 154], - mediumturquoise: [72, 209, 204], - mediumvioletred: [199, 21, 133], - midnightblue: [25, 25, 112], - mintcream: [245, 255, 250], - mistyrose: [255, 228, 225], - moccasin: [255, 228, 181], - navajowhite: [255, 222, 173], - navy: [0, 0, 128], - oldlace: [253, 245, 230], - olive: [128, 128, 0], - olivedrab: [107, 142, 35], - orange: [255, 165, 0], - orangered: [255, 69, 0], - orchid: [218, 112, 214], - palegoldenrod: [238, 232, 170], - palegreen: [152, 251, 152], - paleturquoise: [175, 238, 238], - palevioletred: [219, 112, 147], - papayawhip: [255, 239, 213], - peachpuff: [255, 218, 185], - peru: [205, 133, 63], - pink: [255, 192, 203], - plum: [221, 160, 203], - powderblue: [176, 224, 230], - purple: [128, 0, 128], - rebeccapurple: [102, 51, 153], - red: [255, 0, 0], - rosybrown: [188, 143, 143], - royalblue: [65, 105, 225], - saddlebrown: [139, 69, 19], - salmon: [250, 128, 114], - sandybrown: [244, 164, 96], - seagreen: [46, 139, 87], - seashell: [255, 245, 238], - sienna: [160, 82, 45], - silver: [192, 192, 192], - skyblue: [135, 206, 235], - slateblue: [106, 90, 205], - slategray: [119, 128, 144], - slategrey: [119, 128, 144], - snow: [255, 255, 250], - springgreen: [0, 255, 127], - steelblue: [70, 130, 180], - tan: [210, 180, 140], - teal: [0, 128, 128], - thistle: [216, 191, 216], - transparent: [255, 255, 255, 0], - tomato: [255, 99, 71], - turquoise: [64, 224, 208], - violet: [238, 130, 238], - wheat: [245, 222, 179], - white: [255, 255, 255], - whitesmoke: [245, 245, 245], - yellow: [255, 255, 0], - yellowgreen: [154, 205, 5] - }, - RGB_REGEX = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/; - - /** - * @namespace Util - * @memberof Konva - */ - Konva.Util = { - /* - * cherry-picked utilities from underscore.js - */ - _isElement: function(obj) { - return !!(obj && obj.nodeType == 1); - }, - _isFunction: function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }, - _isObject: function(obj) { - return !!obj && obj.constructor === Object; - }, - _isArray: function(obj) { - return Object.prototype.toString.call(obj) === OBJECT_ARRAY; - }, - _isNumber: function(obj) { - return Object.prototype.toString.call(obj) === OBJECT_NUMBER; - }, - _isString: function(obj) { - return Object.prototype.toString.call(obj) === OBJECT_STRING; - }, - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. Normally, the throttled function will run - // as much as it can, without ever going more than once per `wait` duration; - // but if you'd like to disable the execution on the leading edge, pass - // `{leading: false}`. To disable execution on the trailing edge, ditto. - _throttle: function(func, wait, opts) { - var context, args, result; - var timeout = null; - var previous = 0; - var options = opts || {}; - var later = function() { - previous = options.leading === false ? 0 : new Date().getTime(); - timeout = null; - result = func.apply(context, args); - context = args = null; - }; - return function() { - var now = new Date().getTime(); - if (!previous && options.leading === false) { - previous = now; - } - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - /* - * other utils - */ - _hasMethods: function(obj) { - var names = [], key; - - for (key in obj) { - if (!obj.hasOwnProperty(key)) { - continue; - } - if (this._isFunction(obj[key])) { - names.push(key); - } - } - return names.length > 0; - }, - isValidSelector: function(selector) { - if (typeof selector !== 'string') { - return false; - } - var firstChar = selector[0]; - return firstChar === '#' || - firstChar === '.' || - firstChar === firstChar.toUpperCase(); - }, - createCanvasElement: function() { - var canvas = Konva.document.createElement('canvas'); - // on some environments canvas.style is readonly - try { - canvas.style = canvas.style || {}; - } catch (e) {} - return canvas; - }, - isBrowser: function() { - return typeof exports !== 'object'; - }, - _isInDocument: function(el) { - while (el = el.parentNode) { - if (el == Konva.document) { - return true; - } - } - return false; - }, - _simplifyArray: function(arr) { - var retArr = [], len = arr.length, util = Konva.Util, n, val; - - for (n = 0; n < len; n++) { - val = arr[n]; - if (util._isNumber(val)) { - val = Math.round(val * 1000) / 1000; - } else if (!util._isString(val)) { - val = val.toString(); - } - - retArr.push(val); - } - - return retArr; - }, - /* - * arg can be an image object or image data - */ - _getImage: function(arg, callback) { - var imageObj, canvas; - - // if arg is null or undefined - if (!arg) { - callback(null); - } else if (this._isElement(arg)) { - // if arg is already an image object - callback(arg); - } else if (this._isString(arg)) { - // if arg is a string, then it's a data url - imageObj = new Konva.window.Image(); - imageObj.onload = function() { - callback(imageObj); - }; - imageObj.src = arg; - } else if (arg.data) { - //if arg is an object that contains the data property, it's an image object - canvas = Konva.Util.createCanvasElement(); - canvas.width = arg.width; - canvas.height = arg.height; - var _context = canvas.getContext(CONTEXT_2D); - _context.putImageData(arg, 0, 0); - this._getImage(canvas.toDataURL(), callback); - } else { - callback(null); - } - }, - _getRGBAString: function(obj) { - var red = obj.red || 0, - green = obj.green || 0, - blue = obj.blue || 0, - alpha = obj.alpha || 1; - - return ['rgba(', red, ',', green, ',', blue, ',', alpha, ')'].join( - EMPTY_STRING - ); - }, - _rgbToHex: function(r, g, b) { - return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - }, - _hexToRgb: function(hex) { - hex = hex.replace(HASH, EMPTY_STRING); - var bigint = parseInt(hex, 16); - return { - r: bigint >> 16 & 255, - g: bigint >> 8 & 255, - b: bigint & 255 - }; - }, - /** - * return random hex color - * @method - * @memberof Konva.Util.prototype - */ - getRandomColor: function() { - var randColor = (Math.random() * 0xffffff << 0).toString(16); - while (randColor.length < 6) { - randColor = ZERO + randColor; - } - return HASH + randColor; - }, - /** - * return value with default fallback - * @method - * @memberof Konva.Util.prototype - */ - get: function(val, def) { - if (val === undefined) { - return def; - } else { - return val; - } - }, - /** - * get RGB components of a color - * @method - * @memberof Konva.Util.prototype - * @param {String} color - * @example - * // each of the following examples return {r:0, g:0, b:255} - * var rgb = Konva.Util.getRGB('blue'); - * var rgb = Konva.Util.getRGB('#0000ff'); - * var rgb = Konva.Util.getRGB('rgb(0,0,255)'); - */ - getRGB: function(color) { - var rgb; - // color string - if (color in COLORS) { - rgb = COLORS[color]; - return { - r: rgb[0], - g: rgb[1], - b: rgb[2] - }; - } else if (color[0] === HASH) { - // hex - return this._hexToRgb(color.substring(1)); - } else if (color.substr(0, 4) === RGB_PAREN) { - // rgb string - rgb = RGB_REGEX.exec(color.replace(/ /g, '')); - return { - r: parseInt(rgb[1], 10), - g: parseInt(rgb[2], 10), - b: parseInt(rgb[3], 10) - }; - } else { - // default - return { - r: 0, - g: 0, - b: 0 - }; - } - }, - // convert any color string to RGBA object - // from https://github.com/component/color-parser - colorToRGBA: function(str) { - str = str || 'black'; - return Konva.Util._namedColorToRBA(str) || - Konva.Util._hex3ColorToRGBA(str) || - Konva.Util._hex6ColorToRGBA(str) || - Konva.Util._rgbColorToRGBA(str) || - Konva.Util._rgbaColorToRGBA(str); - }, - // Parse named css color. Like "green" - _namedColorToRBA: function(str) { - var c = COLORS[str.toLowerCase()]; - if (!c) { - return null; - } - return { - r: c[0], - g: c[1], - b: c[2], - a: 1 - }; - }, - // Parse rgb(n, n, n) - _rgbColorToRGBA: function(str) { - if (str.indexOf('rgb(') === 0) { - str = str.match(/rgb\(([^)]+)\)/)[1]; - var parts = str.split(/ *, */).map(Number); - return { - r: parts[0], - g: parts[1], - b: parts[2], - a: 1 - }; - } - }, - // Parse rgba(n, n, n, n) - _rgbaColorToRGBA: function(str) { - if (str.indexOf('rgba(') === 0) { - str = str.match(/rgba\(([^)]+)\)/)[1]; - var parts = str.split(/ *, */).map(Number); - return { - r: parts[0], - g: parts[1], - b: parts[2], - a: parts[3] - }; - } - }, - // Parse #nnnnnn - _hex6ColorToRGBA: function(str) { - if (str[0] === '#' && str.length === 7) { - return { - r: parseInt(str.slice(1, 3), 16), - g: parseInt(str.slice(3, 5), 16), - b: parseInt(str.slice(5, 7), 16), - a: 1 - }; - } - }, - // Parse #nnn - _hex3ColorToRGBA: function(str) { - if (str[0] === '#' && str.length === 4) { - return { - r: parseInt(str[1] + str[1], 16), - g: parseInt(str[2] + str[2], 16), - b: parseInt(str[3] + str[3], 16), - a: 1 - }; - } - }, - // o1 takes precedence over o2 - _merge: function(o1, o2) { - var retObj = this._clone(o2); - for (var key in o1) { - if (this._isObject(o1[key])) { - retObj[key] = this._merge(o1[key], retObj[key]); - } else { - retObj[key] = o1[key]; - } - } - return retObj; - }, - cloneObject: function(obj) { - var retObj = {}; - for (var key in obj) { - if (this._isObject(obj[key])) { - retObj[key] = this.cloneObject(obj[key]); - } else if (this._isArray(obj[key])) { - retObj[key] = this.cloneArray(obj[key]); - } else { - retObj[key] = obj[key]; - } - } - return retObj; - }, - cloneArray: function(arr) { - return arr.slice(0); - }, - _degToRad: function(deg) { - return deg * PI_OVER_DEG180; - }, - _radToDeg: function(rad) { - return rad * DEG180_OVER_PI; - }, - _capitalize: function(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - }, - throw: function(str) { - throw new Error(KONVA_ERROR + str); - }, - error: function(str) { - console.error(KONVA_ERROR + str); - }, - warn: function(str) { - /* - * IE9 on Windows7 64bit will throw a JS error - * if we don't use window.console in the conditional - */ - if (Konva.global.console && console.warn && Konva.showWarnings) { - console.warn(KONVA_WARNING + str); - } - }, - extend: function(child, parent) { - function Ctor() { - this.constructor = child; - } - Ctor.prototype = parent.prototype; - var oldProto = child.prototype; - child.prototype = new Ctor(); - for (var key in oldProto) { - if (oldProto.hasOwnProperty(key)) { - child.prototype[key] = oldProto[key]; - } - } - child.__super__ = parent.prototype; - // create reference to parent - child.super = parent; - }, - /** - * adds methods to a constructor prototype - * @method - * @memberof Konva.Util.prototype - * @param {Function} constructor - * @param {Object} methods - */ - addMethods: function(constructor, methods) { - var key; - - for (key in methods) { - constructor.prototype[key] = methods[key]; - } - }, - _getControlPoints: function(x0, y0, x1, y1, x2, y2, t) { - var d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), - d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), - fa = t * d01 / (d01 + d12), - fb = t * d12 / (d01 + d12), - p1x = x1 - fa * (x2 - x0), - p1y = y1 - fa * (y2 - y0), - p2x = x1 + fb * (x2 - x0), - p2y = y1 + fb * (y2 - y0); - - return [p1x, p1y, p2x, p2y]; - }, - _expandPoints: function(p, tension) { - var len = p.length, allPoints = [], n, cp; - - for (n = 2; n < len - 2; n += 2) { - cp = Konva.Util._getControlPoints( - p[n - 2], - p[n - 1], - p[n], - p[n + 1], - p[n + 2], - p[n + 3], - tension - ); - allPoints.push(cp[0]); - allPoints.push(cp[1]); - allPoints.push(p[n]); - allPoints.push(p[n + 1]); - allPoints.push(cp[2]); - allPoints.push(cp[3]); - } - - return allPoints; - }, - _removeLastLetter: function(str) { - return str.substring(0, str.length - 1); - }, - each: function(obj, func) { - for (var key in obj) { - func(key, obj[key]); - } - }, - _getProjectionToSegment: function(x1, y1, x2, y2, x3, y3) { - var x, y, dist; - - var pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); - if (pd2 == 0) { - x = x1; - y = y1; - dist = (x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2); - } else { - var u = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / pd2; - if (u < 0) { - x = x1; - y = y1; - dist = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3); - } else if (u > 1.0) { - x = x2; - y = y2; - dist = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); - } else { - x = x1 + u * (x2 - x1); - y = y1 + u * (y2 - y1); - dist = (x - x3) * (x - x3) + (y - y3) * (y - y3); - } - } - return [x, y, dist]; - }, - // line as array of points. - // line might be closed - _getProjectionToLine: function(pt, line, isClosed) { - var pc = Konva.Util.cloneObject(pt); - var dist = Number.MAX_VALUE; - line.forEach(function(p1, i) { - if (!isClosed && i === line.length - 1) { - return; - } - var p2 = line[(i + 1) % line.length]; - var proj = Konva.Util._getProjectionToSegment( - p1.x, - p1.y, - p2.x, - p2.y, - pt.x, - pt.y - ); - var px = proj[0], py = proj[1], pdist = proj[2]; - if (pdist < dist) { - pc.x = px; - pc.y = py; - dist = pdist; - } - }); - return pc; - }, - _prepareArrayForTween: function(startArray, endArray, isClosed) { - var n, start = [], end = []; - if (startArray.length > endArray.length) { - var temp = endArray; - endArray = startArray; - startArray = temp; - } - for (n = 0; n < startArray.length; n += 2) { - start.push({ - x: startArray[n], - y: startArray[n + 1] - }); - } - for (n = 0; n < endArray.length; n += 2) { - end.push({ - x: endArray[n], - y: endArray[n + 1] - }); - } - - var newStart = []; - end.forEach(function(point) { - var pr = Konva.Util._getProjectionToLine(point, start, isClosed); - newStart.push(pr.x); - newStart.push(pr.y); - }); - return newStart; - }, - _prepareToStringify: function(obj) { - var desc; - - obj.visitedByCircularReferenceRemoval = true; - - for (var key in obj) { - if ( - !(obj.hasOwnProperty(key) && obj[key] && typeof obj[key] == 'object') - ) { - continue; - } - desc = Object.getOwnPropertyDescriptor(obj, key); - if ( - obj[key].visitedByCircularReferenceRemoval || - Konva.Util._isElement(obj[key]) - ) { - if (desc.configurable) { - delete obj[key]; - } else { - return null; - } - } else if (Konva.Util._prepareToStringify(obj[key]) === null) { - if (desc.configurable) { - delete obj[key]; - } else { - return null; - } - } - } - - delete obj.visitedByCircularReferenceRemoval; - - return obj; - } - }; -})(); - -(function() { - 'use strict'; - // calculate pixel ratio - var canvas = Konva.Util.createCanvasElement(), - context = canvas.getContext('2d'), - _pixelRatio = (function() { - var devicePixelRatio = Konva.window.devicePixelRatio || 1, - backingStoreRatio = context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || - 1; - return devicePixelRatio / backingStoreRatio; - })(); - - /** - * Canvas Renderer constructor - * @constructor - * @abstract - * @memberof Konva - * @param {Object} config - * @param {Number} config.width - * @param {Number} config.height - * @param {Number} config.pixelRatio KonvaJS automatically handles pixel ratio adjustments in order to render crisp drawings - * on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios - * of 1. Some high end tablets and phones, like iPhones and iPads (not the mini) have a device pixel ratio - * of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel - * ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise - * specified, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel - * ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1. - */ - Konva.Canvas = function(config) { - this.init(config); - }; - - Konva.Canvas.prototype = { - init: function(config) { - var conf = config || {}; - - var pixelRatio = conf.pixelRatio || Konva.pixelRatio || _pixelRatio; - - this.pixelRatio = pixelRatio; - this._canvas = Konva.Util.createCanvasElement(); - - // set inline styles - this._canvas.style.padding = 0; - this._canvas.style.margin = 0; - this._canvas.style.border = 0; - this._canvas.style.background = 'transparent'; - this._canvas.style.position = 'absolute'; - this._canvas.style.top = 0; - this._canvas.style.left = 0; - }, - /** - * get canvas context - * @method - * @memberof Konva.Canvas.prototype - * @returns {CanvasContext} context - */ - getContext: function() { - return this.context; - }, - /** - * get pixel ratio - * @method - * @memberof Konva.Canvas.prototype - * @returns {Number} pixel ratio - */ - getPixelRatio: function() { - return this.pixelRatio; - }, - /** - * get pixel ratio - * @method - * @memberof Konva.Canvas.prototype - * @param {Number} pixelRatio KonvaJS automatically handles pixel ratio adustments in order to render crisp drawings - * on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios - * of 1. Some high end tablets and phones, like iPhones and iPads have a device pixel ratio - * of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel - * ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise - * specificed, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel - * ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1. - */ - setPixelRatio: function(pixelRatio) { - var previousRatio = this.pixelRatio; - this.pixelRatio = pixelRatio; - this.setSize( - this.getWidth() / previousRatio, - this.getHeight() / previousRatio - ); - }, - /** - * set width - * @method - * @memberof Konva.Canvas.prototype - * @param {Number} width - */ - setWidth: function(width) { - // take into account pixel ratio - this.width = this._canvas.width = width * this.pixelRatio; - this._canvas.style.width = width + 'px'; - - var pixelRatio = this.pixelRatio, _context = this.getContext()._context; - _context.scale(pixelRatio, pixelRatio); - }, - /** - * set height - * @method - * @memberof Konva.Canvas.prototype - * @param {Number} height - */ - setHeight: function(height) { - // take into account pixel ratio - this.height = this._canvas.height = height * this.pixelRatio; - this._canvas.style.height = height + 'px'; - var pixelRatio = this.pixelRatio, _context = this.getContext()._context; - _context.scale(pixelRatio, pixelRatio); - }, - /** - * get width - * @method - * @memberof Konva.Canvas.prototype - * @returns {Number} width - */ - getWidth: function() { - return this.width; - }, - /** - * get height - * @method - * @memberof Konva.Canvas.prototype - * @returns {Number} height - */ - getHeight: function() { - return this.height; - }, - /** - * set size - * @method - * @memberof Konva.Canvas.prototype - * @param {Number} width - * @param {Number} height - */ - setSize: function(width, height) { - this.setWidth(width); - this.setHeight(height); - }, - /** - * to data url - * @method - * @memberof Konva.Canvas.prototype - * @param {String} mimeType - * @param {Number} quality between 0 and 1 for jpg mime types - * @returns {String} data url string - */ - toDataURL: function(mimeType, quality) { - try { - // If this call fails (due to browser bug, like in Firefox 3.6), - // then revert to previous no-parameter image/png behavior - return this._canvas.toDataURL(mimeType, quality); - } catch (e) { - try { - return this._canvas.toDataURL(); - } catch (err) { - Konva.Util.warn('Unable to get data URL. ' + err.message); - return ''; - } - } - } - }; - - Konva.SceneCanvas = function(config) { - var conf = config || {}; - var width = conf.width || 0, height = conf.height || 0; - - Konva.Canvas.call(this, conf); - this.context = new Konva.SceneContext(this); - this.setSize(width, height); - }; - - Konva.Util.extend(Konva.SceneCanvas, Konva.Canvas); - - Konva.HitCanvas = function(config) { - var conf = config || {}; - var width = conf.width || 0, height = conf.height || 0; - - Konva.Canvas.call(this, conf); - this.context = new Konva.HitContext(this); - this.setSize(width, height); - this.hitCanvas = true; - }; - Konva.Util.extend(Konva.HitCanvas, Konva.Canvas); -})(); - -(function() { - 'use strict'; - var COMMA = ',', - OPEN_PAREN = '(', - CLOSE_PAREN = ')', - OPEN_PAREN_BRACKET = '([', - CLOSE_BRACKET_PAREN = '])', - SEMICOLON = ';', - DOUBLE_PAREN = '()', - // EMPTY_STRING = '', - EQUALS = '=', - // SET = 'set', - CONTEXT_METHODS = [ - 'arc', - 'arcTo', - 'beginPath', - 'bezierCurveTo', - 'clearRect', - 'clip', - 'closePath', - 'createLinearGradient', - 'createPattern', - 'createRadialGradient', - 'drawImage', - 'fill', - 'fillText', - 'getImageData', - 'createImageData', - 'lineTo', - 'moveTo', - 'putImageData', - 'quadraticCurveTo', - 'rect', - 'restore', - 'rotate', - 'save', - 'scale', - 'setLineDash', - 'setTransform', - 'stroke', - 'strokeText', - 'transform', - 'translate' - ]; - - var CONTEXT_PROPERTIES = [ - 'fillStyle', - 'strokeStyle', - 'shadowColor', - 'shadowBlur', - 'shadowOffsetX', - 'shadowOffsetY', - 'lineCap', - 'lineJoin', - 'lineWidth', - 'miterLimit', - 'font', - 'textAlign', - 'textBaseline', - 'globalAlpha', - 'globalCompositeOperation' - ]; - - /** - * Canvas Context constructor - * @constructor - * @abstract - * @memberof Konva - */ - Konva.Context = function(canvas) { - this.init(canvas); - }; - - Konva.Context.prototype = { - init: function(canvas) { - this.canvas = canvas; - this._context = canvas._canvas.getContext('2d'); - - if (Konva.enableTrace) { - this.traceArr = []; - this._enableTrace(); - } - }, - /** - * fill shape - * @method - * @memberof Konva.Context.prototype - * @param {Konva.Shape} shape - */ - fillShape: function(shape) { - if (shape.getFillEnabled()) { - this._fill(shape); - } - }, - /** - * stroke shape - * @method - * @memberof Konva.Context.prototype - * @param {Konva.Shape} shape - */ - strokeShape: function(shape) { - if (shape.getStrokeEnabled()) { - this._stroke(shape); - } - }, - /** - * fill then stroke - * @method - * @memberof Konva.Context.prototype - * @param {Konva.Shape} shape - */ - fillStrokeShape: function(shape) { - var fillEnabled = shape.getFillEnabled(); - if (fillEnabled) { - this._fill(shape); - } - if (shape.getStrokeEnabled()) { - this._stroke(shape); - } - }, - /** - * get context trace if trace is enabled - * @method - * @memberof Konva.Context.prototype - * @param {Boolean} relaxed if false, return strict context trace, which includes method names, method parameters - * properties, and property values. If true, return relaxed context trace, which only returns method names and - * properites. - * @returns {String} - */ - getTrace: function(relaxed) { - var traceArr = this.traceArr, - len = traceArr.length, - str = '', - n, - trace, - method, - args; - - for (n = 0; n < len; n++) { - trace = traceArr[n]; - method = trace.method; - - // methods - if (method) { - args = trace.args; - str += method; - if (relaxed) { - str += DOUBLE_PAREN; - } else { - if (Konva.Util._isArray(args[0])) { - str += OPEN_PAREN_BRACKET + - args.join(COMMA) + - CLOSE_BRACKET_PAREN; - } else { - str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN; - } - } - } else { - // properties - str += trace.property; - if (!relaxed) { - str += EQUALS + trace.val; - } - } - - str += SEMICOLON; - } - - return str; - }, - /** - * clear trace if trace is enabled - * @method - * @memberof Konva.Context.prototype - */ - clearTrace: function() { - this.traceArr = []; - }, - _trace: function(str) { - var traceArr = this.traceArr, len; - - traceArr.push(str); - len = traceArr.length; - - if (len >= Konva.traceArrMax) { - traceArr.shift(); - } - }, - /** - * reset canvas context transform - * @method - * @memberof Konva.Context.prototype - */ - reset: function() { - var pixelRatio = this.getCanvas().getPixelRatio(); - this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); - }, - /** - * get canvas - * @method - * @memberof Konva.Context.prototype - * @returns {Konva.Canvas} - */ - getCanvas: function() { - return this.canvas; - }, - /** - * clear canvas - * @method - * @memberof Konva.Context.prototype - * @param {Object} [bounds] - * @param {Number} [bounds.x] - * @param {Number} [bounds.y] - * @param {Number} [bounds.width] - * @param {Number} [bounds.height] - */ - clear: function(bounds) { - var canvas = this.getCanvas(); - - if (bounds) { - this.clearRect( - bounds.x || 0, - bounds.y || 0, - bounds.width || 0, - bounds.height || 0 - ); - } else { - this.clearRect( - 0, - 0, - canvas.getWidth() / canvas.pixelRatio, - canvas.getHeight() / canvas.pixelRatio - ); - } - }, - _applyLineCap: function(shape) { - var lineCap = shape.getLineCap(); - if (lineCap) { - this.setAttr('lineCap', lineCap); - } - }, - _applyOpacity: function(shape) { - var absOpacity = shape.getAbsoluteOpacity(); - if (absOpacity !== 1) { - this.setAttr('globalAlpha', absOpacity); - } - }, - _applyLineJoin: function(shape) { - var lineJoin = shape.getLineJoin(); - if (lineJoin) { - this.setAttr('lineJoin', lineJoin); - } - }, - setAttr: function(attr, val) { - this._context[attr] = val; - }, - - // context pass through methods - arc: function() { - var a = arguments; - this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - beginPath: function() { - this._context.beginPath(); - }, - bezierCurveTo: function() { - var a = arguments; - this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - clearRect: function() { - var a = arguments; - this._context.clearRect(a[0], a[1], a[2], a[3]); - }, - clip: function() { - this._context.clip(); - }, - closePath: function() { - this._context.closePath(); - }, - createImageData: function() { - var a = arguments; - if (a.length === 2) { - return this._context.createImageData(a[0], a[1]); - } else if (a.length === 1) { - return this._context.createImageData(a[0]); - } - }, - createLinearGradient: function() { - var a = arguments; - return this._context.createLinearGradient(a[0], a[1], a[2], a[3]); - }, - createPattern: function() { - var a = arguments; - return this._context.createPattern(a[0], a[1]); - }, - createRadialGradient: function() { - var a = arguments; - return this._context.createRadialGradient( - a[0], - a[1], - a[2], - a[3], - a[4], - a[5] - ); - }, - drawImage: function() { - var a = arguments, _context = this._context; - - if (a.length === 3) { - _context.drawImage(a[0], a[1], a[2]); - } else if (a.length === 5) { - _context.drawImage(a[0], a[1], a[2], a[3], a[4]); - } else if (a.length === 9) { - _context.drawImage( - a[0], - a[1], - a[2], - a[3], - a[4], - a[5], - a[6], - a[7], - a[8] - ); - } - }, - isPointInPath: function(x, y) { - return this._context.isPointInPath(x, y); - }, - fill: function() { - this._context.fill(); - }, - fillRect: function(x, y, width, height) { - this._context.fillRect(x, y, width, height); - }, - strokeRect: function(x, y, width, height) { - this._context.strokeRect(x, y, width, height); - }, - fillText: function() { - var a = arguments; - this._context.fillText(a[0], a[1], a[2]); - }, - measureText: function(text) { - return this._context.measureText(text); - }, - getImageData: function() { - var a = arguments; - return this._context.getImageData(a[0], a[1], a[2], a[3]); - }, - lineTo: function() { - var a = arguments; - this._context.lineTo(a[0], a[1]); - }, - moveTo: function() { - var a = arguments; - this._context.moveTo(a[0], a[1]); - }, - rect: function() { - var a = arguments; - this._context.rect(a[0], a[1], a[2], a[3]); - }, - putImageData: function() { - var a = arguments; - this._context.putImageData(a[0], a[1], a[2]); - }, - quadraticCurveTo: function() { - var a = arguments; - this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]); - }, - restore: function() { - this._context.restore(); - }, - rotate: function() { - var a = arguments; - this._context.rotate(a[0]); - }, - save: function() { - this._context.save(); - }, - scale: function() { - var a = arguments; - this._context.scale(a[0], a[1]); - }, - setLineDash: function() { - var a = arguments, _context = this._context; - - // works for Chrome and IE11 - if (this._context.setLineDash) { - _context.setLineDash(a[0]); - } else if ('mozDash' in _context) { - // verified that this works in firefox - _context.mozDash = a[0]; - } else if ('webkitLineDash' in _context) { - // does not currently work for Safari - _context.webkitLineDash = a[0]; - } - - // no support for IE9 and IE10 - }, - getLineDash: function() { - return this._context.getLineDash(); - }, - setTransform: function() { - var a = arguments; - this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - stroke: function() { - this._context.stroke(); - }, - strokeText: function() { - var a = arguments; - this._context.strokeText(a[0], a[1], a[2]); - }, - transform: function() { - var a = arguments; - this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - translate: function() { - var a = arguments; - this._context.translate(a[0], a[1]); - }, - _enableTrace: function() { - var that = this, - len = CONTEXT_METHODS.length, - _simplifyArray = Konva.Util._simplifyArray, - origSetter = this.setAttr, - n, - args; - - // to prevent creating scope function at each loop - var func = function(methodName) { - var origMethod = that[methodName], ret; - - that[methodName] = function() { - args = _simplifyArray(Array.prototype.slice.call(arguments, 0)); - ret = origMethod.apply(that, arguments); - - that._trace({ - method: methodName, - args: args - }); - - return ret; - }; - }; - // methods - for (n = 0; n < len; n++) { - func(CONTEXT_METHODS[n]); - } - - // attrs - that.setAttr = function() { - origSetter.apply(that, arguments); - var prop = arguments[0]; - var val = arguments[1]; - if ( - prop === 'shadowOffsetX' || - prop === 'shadowOffsetY' || - prop === 'shadowBlur' - ) { - val = val / this.canvas.getPixelRatio(); - } - that._trace({ - property: prop, - val: val - }); - }; - } - }; - - CONTEXT_PROPERTIES.forEach(function(prop) { - Object.defineProperty(Konva.Context.prototype, prop, { - get: function() { - return this._context[prop]; - }, - set: function(val) { - this._context[prop] = val; - } - }); - }); - - Konva.SceneContext = function(canvas) { - Konva.Context.call(this, canvas); - }; - - Konva.SceneContext.prototype = { - _fillColor: function(shape) { - var fill = shape.fill(); - - this.setAttr('fillStyle', fill); - shape._fillFunc(this); - }, - _fillPattern: function(shape) { - var fillPatternX = shape.getFillPatternX(), - fillPatternY = shape.getFillPatternY(), - fillPatternScale = shape.getFillPatternScale(), - fillPatternRotation = Konva.getAngle(shape.getFillPatternRotation()), - fillPatternOffset = shape.getFillPatternOffset(); - - if (fillPatternX || fillPatternY) { - this.translate(fillPatternX || 0, fillPatternY || 0); - } - if (fillPatternRotation) { - this.rotate(fillPatternRotation); - } - if (fillPatternScale) { - this.scale(fillPatternScale.x, fillPatternScale.y); - } - if (fillPatternOffset) { - this.translate((-1) * fillPatternOffset.x, (-1) * fillPatternOffset.y); - } - - this.setAttr( - 'fillStyle', - this.createPattern( - shape.getFillPatternImage(), - shape.getFillPatternRepeat() || 'repeat' - ) - ); - this.fill(); - }, - _fillLinearGradient: function(shape) { - var start = shape.getFillLinearGradientStartPoint(), - end = shape.getFillLinearGradientEndPoint(), - colorStops = shape.getFillLinearGradientColorStops(), - grd = this.createLinearGradient(start.x, start.y, end.x, end.y); - - if (colorStops) { - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - this.setAttr('fillStyle', grd); - shape._fillFunc(this); - } - }, - _fillRadialGradient: function(shape) { - var start = shape.getFillRadialGradientStartPoint(), - end = shape.getFillRadialGradientEndPoint(), - startRadius = shape.getFillRadialGradientStartRadius(), - endRadius = shape.getFillRadialGradientEndRadius(), - colorStops = shape.getFillRadialGradientColorStops(), - grd = this.createRadialGradient( - start.x, - start.y, - startRadius, - end.x, - end.y, - endRadius - ); - - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - this.setAttr('fillStyle', grd); - this.fill(); - }, - _fill: function(shape) { - var hasColor = shape.fill(), - hasPattern = shape.getFillPatternImage(), - hasLinearGradient = shape.getFillLinearGradientColorStops(), - hasRadialGradient = shape.getFillRadialGradientColorStops(), - fillPriority = shape.getFillPriority(); - - // priority fills - if (hasColor && fillPriority === 'color') { - this._fillColor(shape); - } else if (hasPattern && fillPriority === 'pattern') { - this._fillPattern(shape); - } else if (hasLinearGradient && fillPriority === 'linear-gradient') { - this._fillLinearGradient(shape); - } else if (hasRadialGradient && fillPriority === 'radial-gradient') { - this._fillRadialGradient(shape); - } else if (hasColor) { - // now just try and fill with whatever is available - this._fillColor(shape); - } else if (hasPattern) { - this._fillPattern(shape); - } else if (hasLinearGradient) { - this._fillLinearGradient(shape); - } else if (hasRadialGradient) { - this._fillRadialGradient(shape); - } - }, - _stroke: function(shape) { - var dash = shape.dash(), - // ignore strokeScaleEnabled for Text - strokeScaleEnabled = shape.getStrokeScaleEnabled() || - shape instanceof Konva.Text; - - if (shape.hasStroke()) { - if (!strokeScaleEnabled) { - this.save(); - this.setTransform(1, 0, 0, 1, 0, 0); - } - - this._applyLineCap(shape); - if (dash && shape.dashEnabled()) { - this.setLineDash(dash); - } - - this.setAttr('lineWidth', shape.strokeWidth()); - this.setAttr('strokeStyle', shape.stroke()); - - if (!shape.getShadowForStrokeEnabled()) { - this.setAttr('shadowColor', 'rgba(0,0,0,0)'); - } - shape._strokeFunc(this); - - if (!strokeScaleEnabled) { - this.restore(); - } - } - }, - _applyShadow: function(shape) { - var util = Konva.Util, - color = util.get(shape.getShadowRGBA(), 'black'), - blur = util.get(shape.getShadowBlur(), 5), - offset = util.get(shape.getShadowOffset(), { - x: 0, - y: 0 - }), - // TODO: get this info from transform?? - scale = shape.getAbsoluteScale(), - ratio = this.canvas.getPixelRatio(), - scaleX = scale.x * ratio, - scaleY = scale.y * ratio; - - this.setAttr('shadowColor', color); - this.setAttr('shadowBlur', blur * ratio * Math.min(scaleX, scaleY)); - this.setAttr('shadowOffsetX', offset.x * scaleX); - this.setAttr('shadowOffsetY', offset.y * scaleY); - } - }; - Konva.Util.extend(Konva.SceneContext, Konva.Context); - - Konva.HitContext = function(canvas) { - Konva.Context.call(this, canvas); - }; - - Konva.HitContext.prototype = { - _fill: function(shape) { - this.save(); - this.setAttr('fillStyle', shape.colorKey); - shape._fillFuncHit(this); - this.restore(); - }, - _stroke: function(shape) { - if (shape.hasStroke() && shape.strokeHitEnabled()) { - // ignore strokeScaleEnabled for Text - var strokeScaleEnabled = shape.getStrokeScaleEnabled() || - shape instanceof Konva.Text; - if (!strokeScaleEnabled) { - this.save(); - this.setTransform(1, 0, 0, 1, 0, 0); - } - this._applyLineCap(shape); - this.setAttr('lineWidth', shape.strokeWidth()); - this.setAttr('strokeStyle', shape.colorKey); - shape._strokeFuncHit(this); - if (!strokeScaleEnabled) { - this.restore(); - } - } - } - }; - Konva.Util.extend(Konva.HitContext, Konva.Context); -})(); - -(function() { - 'use strict'; - // CONSTANTS - var GET = 'get', SET = 'set'; - - Konva.Factory = { - addGetterSetter: function(constructor, attr, def, validator, after) { - this.addGetter(constructor, attr, def); - this.addSetter(constructor, attr, validator, after); - this.addOverloadedGetterSetter(constructor, attr); - }, - addGetter: function(constructor, attr, def) { - var method = GET + Konva.Util._capitalize(attr); - - constructor.prototype[method] = function() { - var val = this.attrs[attr]; - return val === undefined ? def : val; - }; - }, - addSetter: function(constructor, attr, validator, after) { - var method = SET + Konva.Util._capitalize(attr); - - constructor.prototype[method] = function(val) { - if (validator) { - val = validator.call(this, val); - } - - this._setAttr(attr, val); - - if (after) { - after.call(this); - } - - return this; - }; - }, - addComponentsGetterSetter: function( - constructor, - attr, - components, - validator, - after - ) { - var len = components.length, - capitalize = Konva.Util._capitalize, - getter = GET + capitalize(attr), - setter = SET + capitalize(attr), - n, - component; - - // getter - constructor.prototype[getter] = function() { - var ret = {}; - - for (n = 0; n < len; n++) { - component = components[n]; - ret[component] = this.getAttr(attr + capitalize(component)); - } - - return ret; - }; - - // setter - constructor.prototype[setter] = function(val) { - var oldVal = this.attrs[attr], key; - - if (validator) { - val = validator.call(this, val); - } - - for (key in val) { - if (!val.hasOwnProperty(key)) { - continue; - } - this._setAttr(attr + capitalize(key), val[key]); - } - - this._fireChangeEvent(attr, oldVal, val); - - if (after) { - after.call(this); - } - - return this; - }; - - this.addOverloadedGetterSetter(constructor, attr); - }, - addOverloadedGetterSetter: function(constructor, attr) { - var capitalizedAttr = Konva.Util._capitalize(attr), - setter = SET + capitalizedAttr, - getter = GET + capitalizedAttr; - - constructor.prototype[attr] = function() { - // setting - if (arguments.length) { - this[setter](arguments[0]); - return this; - } - // getting - return this[getter](); - }; - }, - addDeprecatedGetterSetter: function(constructor, attr, def, validator) { - var method = GET + Konva.Util._capitalize(attr); - var message = attr + - ' property is deprecated and will be removed soon. Look at Konva change log for more information.'; - constructor.prototype[method] = function() { - Konva.Util.error(message); - var val = this.attrs[attr]; - return val === undefined ? def : val; - }; - this.addSetter(constructor, attr, validator, function() { - Konva.Util.error(message); - }); - this.addOverloadedGetterSetter(constructor, attr); - }, - backCompat: function(constructor, methods) { - Konva.Util.each(methods, function(oldMethodName, newMethodName) { - var method = constructor.prototype[newMethodName]; - constructor.prototype[oldMethodName] = function() { - method.apply(this, arguments); - Konva.Util.error( - oldMethodName + - ' method is deprecated and will be removed soon. Use ' + - newMethodName + - ' instead' - ); - }; - }); - }, - afterSetFilter: function() { - this._filterUpToDate = false; - } - }; - - Konva.Validators = { - /** - * @return {number} - */ - RGBComponent: function(val) { - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } - return Math.round(val); - }, - alphaComponent: function(val) { - if (val > 1) { - return 1; - } else if (val < 0.0001) { - // chrome does not honor alpha values of 0 - return 0.0001; - } - - return val; - } - }; -})(); - -(function(Konva) { - 'use strict'; - // CONSTANTS - var ABSOLUTE_OPACITY = 'absoluteOpacity', - ABSOLUTE_TRANSFORM = 'absoluteTransform', - ABSOLUTE_SCALE = 'absoluteScale', - CHANGE = 'Change', - CHILDREN = 'children', - DOT = '.', - EMPTY_STRING = '', - GET = 'get', - ID = 'id', - KONVA = 'konva', - LISTENING = 'listening', - MOUSEENTER = 'mouseenter', - MOUSELEAVE = 'mouseleave', - NAME = 'name', - SET = 'set', - SHAPE = 'Shape', - SPACE = ' ', - STAGE = 'stage', - TRANSFORM = 'transform', - UPPER_STAGE = 'Stage', - VISIBLE = 'visible', - CLONE_BLACK_LIST = ['id'], - TRANSFORM_CHANGE_STR = [ - 'xChange.konva', - 'yChange.konva', - 'scaleXChange.konva', - 'scaleYChange.konva', - 'skewXChange.konva', - 'skewYChange.konva', - 'rotationChange.konva', - 'offsetXChange.konva', - 'offsetYChange.konva', - 'transformsEnabledChange.konva' - ].join(SPACE), - SCALE_CHANGE_STR = ['scaleXChange.konva', 'scaleYChange.konva'].join(SPACE); - - /** - * Node constructor. Nodes are entities that can be transformed, layered, - * and have bound events. The stage, layers, groups, and shapes all extend Node. - * @constructor - * @memberof Konva - * @abstract - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - */ - Konva.Node = function(config) { - this._init(config); - }; - - Konva.Util.addMethods(Konva.Node, { - _init: function(config) { - var that = this; - this._id = Konva.idCounter++; - this.eventListeners = {}; - this.attrs = {}; - this._cache = {}; - this._filterUpToDate = false; - this._isUnderCache = false; - this.setAttrs(config); - - // event bindings for cache handling - this.on(TRANSFORM_CHANGE_STR, function() { - this._clearCache(TRANSFORM); - that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - }); - - this.on(SCALE_CHANGE_STR, function() { - that._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - }); - - this.on('visibleChange.konva', function() { - that._clearSelfAndDescendantCache(VISIBLE); - }); - this.on('listeningChange.konva', function() { - that._clearSelfAndDescendantCache(LISTENING); - }); - this.on('opacityChange.konva', function() { - that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - }); - }, - _clearCache: function(attr) { - if (attr) { - delete this._cache[attr]; - } else { - this._cache = {}; - } - }, - _getCache: function(attr, privateGetter) { - var cache = this._cache[attr]; - - // if not cached, we need to set it using the private getter method. - if (cache === undefined) { - this._cache[attr] = privateGetter.call(this); - } - - return this._cache[attr]; - }, - /* - * when the logic for a cached result depends on ancestor propagation, use this - * method to clear self and children cache - */ - _clearSelfAndDescendantCache: function(attr) { - this._clearCache(attr); - - if (this.children) { - this.getChildren().each(function(node) { - node._clearSelfAndDescendantCache(attr); - }); - } - }, - /** - * clear cached canvas - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Node} - * @example - * node.clearCache(); - */ - clearCache: function() { - delete this._cache.canvas; - this._filterUpToDate = false; - return this; - }, - /** - * cache node to improve drawing performance, apply filters, or create more accurate - * hit regions. For all basic shapes size of cache canvas will be automatically detected. - * If you need to cache your custom `Konva.Shape` instance you have to pass shape's bounding box - * properties. Look at [link to demo page](link to demo page) for more information. - * @method - * @memberof Konva.Node.prototype - * @param {Object} [config] - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Number} [config.offset] increase canvas size by `offset` pixel in all directions. - * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached - * region for debugging purposes - * @param {Number} [config.pixelRatio] change quality (or pixel ratio) of cached image. pixelRatio = 2 will produce 2x sized cache. - * @returns {Konva.Node} - * @example - * // cache a shape with the x,y position of the bounding box at the center and - * // the width and height of the bounding box equal to the width and height of - * // the shape obtained from shape.width() and shape.height() - * image.cache(); - * - * // cache a node and define the bounding box position and size - * node.cache({ - * x: -30, - * y: -30, - * width: 100, - * height: 200 - * }); - * - * // cache a node and draw a red border around the bounding box - * // for debugging purposes - * node.cache({ - * x: -30, - * y: -30, - * width: 100, - * height: 200, - * offset : 10, - * drawBorder: true - * }); - */ - cache: function(config) { - var conf = config || {}, - rect = this.getClientRect(true), - width = conf.width || rect.width, - height = conf.height || rect.height, - pixelRatio = conf.pixelRatio, - x = conf.x || rect.x, - y = conf.y || rect.y, - offset = conf.offset || 0, - drawBorder = conf.drawBorder || false; - - if (!width || !height) { - throw new Error('Width or height of caching configuration equals 0.'); - } - - width += offset * 2; - height += offset * 2; - - x -= offset; - y -= offset; - - var cachedSceneCanvas = new Konva.SceneCanvas({ - pixelRatio: pixelRatio, - width: width, - height: height - }), - cachedFilterCanvas = new Konva.SceneCanvas({ - pixelRatio: pixelRatio, - width: width, - height: height - }), - cachedHitCanvas = new Konva.HitCanvas({ - pixelRatio: 1, - width: width, - height: height - }), - sceneContext = cachedSceneCanvas.getContext(), - hitContext = cachedHitCanvas.getContext(); - - cachedHitCanvas.isCache = true; - - this.clearCache(); - - sceneContext.save(); - hitContext.save(); - - sceneContext.translate(-x, -y); - hitContext.translate(-x, -y); - - // extra flag to skip on getAbsolute opacity calc - this._isUnderCache = true; - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - - this.drawScene(cachedSceneCanvas, this, true); - this.drawHit(cachedHitCanvas, this, true); - this._isUnderCache = false; - - sceneContext.restore(); - hitContext.restore(); - - // this will draw a red border around the cached box for - // debugging purposes - if (drawBorder) { - sceneContext.save(); - sceneContext.beginPath(); - sceneContext.rect(0, 0, width, height); - sceneContext.closePath(); - sceneContext.setAttr('strokeStyle', 'red'); - sceneContext.setAttr('lineWidth', 5); - sceneContext.stroke(); - sceneContext.restore(); - } - - this._cache.canvas = { - scene: cachedSceneCanvas, - filter: cachedFilterCanvas, - hit: cachedHitCanvas, - x: x, - y: y - }; - - return this; - }, - /** - * Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). - * The rectangle position is relative to parent container. - * @method - * @memberof Konva.Node.prototype - * @param {Boolean} [skipTransform] flag should we skip transformation to rectangle - * @returns {Object} rect with {x, y, width, height} properties - * @example - * var rect = new Konva.Rect({ - * width : 100, - * height : 100, - * x : 50, - * y : 50, - * strokeWidth : 4, - * stroke : 'black', - * offsetX : 50, - * scaleY : 2 - * }); - * - * // get client rect without think off transformations (position, rotation, scale, offset, etc) - * rect.getClientRect(true); - * // returns { - * // x : -2, // two pixels for stroke / 2 - * // y : -2, - * // width : 104, // increased by 4 for stroke - * // height : 104 - * //} - * - * // get client rect with transformation applied - * rect.getClientRect(); - * // returns Object {x: -2, y: 46, width: 104, height: 208} - */ - getClientRect: function() { - // abstract method - // redefine in Container and Shape - throw new Error('abstract "getClientRect" method call'); - }, - _transformedRect: function(rect) { - var points = [ - { x: rect.x, y: rect.y }, - { x: rect.x + rect.width, y: rect.y }, - { x: rect.x + rect.width, y: rect.y + rect.height }, - { x: rect.x, y: rect.y + rect.height } - ]; - var minX, minY, maxX, maxY; - var trans = this.getTransform(); - points.forEach(function(point) { - var transformed = trans.point(point); - if (minX === undefined) { - minX = maxX = transformed.x; - minY = maxY = transformed.y; - } - minX = Math.min(minX, transformed.x); - minY = Math.min(minY, transformed.y); - maxX = Math.max(maxX, transformed.x); - maxY = Math.max(maxY, transformed.y); - }); - return { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY - }; - }, - _drawCachedSceneCanvas: function(context) { - context.save(); - context._applyOpacity(this); - context.translate(this._cache.canvas.x, this._cache.canvas.y); - - var cacheCanvas = this._getCachedSceneCanvas(); - var ratio = cacheCanvas.pixelRatio; - - context.drawImage( - cacheCanvas._canvas, - 0, - 0, - cacheCanvas.width / ratio, - cacheCanvas.height / ratio - ); - context.restore(); - }, - _drawCachedHitCanvas: function(context) { - var cachedCanvas = this._cache.canvas, hitCanvas = cachedCanvas.hit; - context.save(); - context.translate(this._cache.canvas.x, this._cache.canvas.y); - context.drawImage(hitCanvas._canvas, 0, 0); - context.restore(); - }, - _getCachedSceneCanvas: function() { - var filters = this.filters(), - cachedCanvas = this._cache.canvas, - sceneCanvas = cachedCanvas.scene, - filterCanvas = cachedCanvas.filter, - filterContext = filterCanvas.getContext(), - len, - imageData, - n, - filter; - - if (filters) { - if (!this._filterUpToDate) { - var ratio = sceneCanvas.pixelRatio; - - try { - len = filters.length; - filterContext.clear(); - - // copy cached canvas onto filter context - filterContext.drawImage( - sceneCanvas._canvas, - 0, - 0, - sceneCanvas.getWidth() / ratio, - sceneCanvas.getHeight() / ratio - ); - imageData = filterContext.getImageData( - 0, - 0, - filterCanvas.getWidth(), - filterCanvas.getHeight() - ); - - // apply filters to filter context - for (n = 0; n < len; n++) { - filter = filters[n]; - if (typeof filter !== 'function') { - Konva.Util.error( - 'Filter should be type of function, but got ' + - typeof filter + - ' insted. Please check correct filters' - ); - continue; - } - filter.call(this, imageData); - filterContext.putImageData(imageData, 0, 0); - } - } catch (e) { - Konva.Util.error('Unable to apply filter. ' + e.message); - } - - this._filterUpToDate = true; - } - - return filterCanvas; - } - return sceneCanvas; - }, - /** - * bind events to the node. KonvaJS supports mouseover, mousemove, - * mouseout, mouseenter, mouseleave, mousedown, mouseup, wheel, click, dblclick, touchstart, touchmove, - * touchend, tap, dbltap, dragstart, dragmove, and dragend events. The Konva Stage supports - * contentMouseover, contentMousemove, contentMouseout, contentMousedown, contentMouseup, contentWheel, contentContextmenu - * contentClick, contentDblclick, contentTouchstart, contentTouchmove, contentTouchend, contentTap, - * and contentDblTap. Pass in a string of events delimmited by a space to bind multiple events at once - * such as 'mousedown mouseup mousemove'. Include a namespace to bind an - * event by name such as 'click.foobar'. - * @method - * @memberof Konva.Node.prototype - * @param {String} evtStr e.g. 'click', 'mousedown touchstart', 'mousedown.foo touchstart.foo' - * @param {Function} handler The handler function is passed an event object - * @returns {Konva.Node} - * @example - * // add click listener - * node.on('click', function() { - * console.log('you clicked me!'); - * }); - * - * // get the target node - * node.on('click', function(evt) { - * console.log(evt.target); - * }); - * - * // stop event propagation - * node.on('click', function(evt) { - * evt.cancelBubble = true; - * }); - * - * // bind multiple listeners - * node.on('click touchstart', function() { - * console.log('you clicked/touched me!'); - * }); - * - * // namespace listener - * node.on('click.foo', function() { - * console.log('you clicked/touched me!'); - * }); - * - * // get the event type - * node.on('click tap', function(evt) { - * var eventType = evt.type; - * }); - * - * // get native event object - * node.on('click tap', function(evt) { - * var nativeEvent = evt.evt; - * }); - * - * // for change events, get the old and new val - * node.on('xChange', function(evt) { - * var oldVal = evt.oldVal; - * var newVal = evt.newVal; - * }); - * - * // get event targets - * // with event delegations - * layer.on('click', 'Group', function(evt) { - * var shape = evt.target; - * var group = evtn.currentTarger; - * }); - */ - on: function(evtStr, handler) { - if (arguments.length === 3) { - return this._delegate.apply(this, arguments); - } - var events = evtStr.split(SPACE), - len = events.length, - n, - event, - parts, - baseEvent, - name; - - /* - * loop through types and attach event listeners to - * each one. eg. 'click mouseover.namespace mouseout' - * will create three event bindings - */ - for (n = 0; n < len; n++) { - event = events[n]; - parts = event.split(DOT); - baseEvent = parts[0]; - name = parts[1] || EMPTY_STRING; - - // create events array if it doesn't exist - if (!this.eventListeners[baseEvent]) { - this.eventListeners[baseEvent] = []; - } - - this.eventListeners[baseEvent].push({ - name: name, - handler: handler - }); - } - - return this; - }, - /** - * remove event bindings from the node. Pass in a string of - * event types delimmited by a space to remove multiple event - * bindings at once such as 'mousedown mouseup mousemove'. - * include a namespace to remove an event binding by name - * such as 'click.foobar'. If you only give a name like '.foobar', - * all events in that namespace will be removed. - * @method - * @memberof Konva.Node.prototype - * @param {String} evtStr e.g. 'click', 'mousedown touchstart', '.foobar' - * @returns {Konva.Node} - * @example - * // remove listener - * node.off('click'); - * - * // remove multiple listeners - * node.off('click touchstart'); - * - * // remove listener by name - * node.off('click.foo'); - */ - off: function(evtStr) { - var events = (evtStr || '').split(SPACE), - len = events.length, - n, - t, - event, - parts, - baseEvent, - name; - - if (!evtStr) { - // remove all events - for (t in this.eventListeners) { - this._off(t); - } - } - for (n = 0; n < len; n++) { - event = events[n]; - parts = event.split(DOT); - baseEvent = parts[0]; - name = parts[1]; - - if (baseEvent) { - if (this.eventListeners[baseEvent]) { - this._off(baseEvent, name); - } - } else { - for (t in this.eventListeners) { - this._off(t, name); - } - } - } - return this; - }, - // some event aliases for third party integration like HammerJS - dispatchEvent: function(evt) { - var e = { - target: this, - type: evt.type, - evt: evt - }; - this.fire(evt.type, e); - return this; - }, - addEventListener: function(type, handler) { - // we have to pass native event to handler - this.on(type, function(evt) { - handler.call(this, evt.evt); - }); - return this; - }, - removeEventListener: function(type) { - this.off(type); - return this; - }, - // like node.on - _delegate: function(event, selector, handler) { - var stopNode = this; - this.on(event, function(evt) { - var targets = evt.target.findAncestors(selector, true, stopNode); - for (var i = 0; i < targets.length; i++) { - evt = Konva.Util.cloneObject(evt); - evt.currentTarget = targets[i]; - handler.call(targets[i], evt); - } - }); - }, - /** - * remove self from parent, but don't destroy - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Node} - * @example - * node.remove(); - */ - remove: function() { - var parent = this.getParent(); - - if (parent && parent.children) { - parent.children.splice(this.index, 1); - parent._setChildrenIndices(); - delete this.parent; - } - - // every cached attr that is calculated via node tree - // traversal must be cleared when removing a node - this._clearSelfAndDescendantCache(STAGE); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - this._clearSelfAndDescendantCache(VISIBLE); - this._clearSelfAndDescendantCache(LISTENING); - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - - return this; - }, - /** - * remove and destroy self - * @method - * @memberof Konva.Node.prototype - * @example - * node.destroy(); - */ - destroy: function() { - // remove from ids and names hashes - Konva._removeId(this.getId()); - - // remove all names - var names = (this.getName() || '').split(/\s/g); - for (var i = 0; i < names.length; i++) { - var subname = names[i]; - Konva._removeName(subname, this._id); - } - - this.remove(); - return this; - }, - /** - * get attr - * @method - * @memberof Konva.Node.prototype - * @param {String} attr - * @returns {Integer|String|Object|Array} - * @example - * var x = node.getAttr('x'); - */ - getAttr: function(attr) { - var method = GET + Konva.Util._capitalize(attr); - if (Konva.Util._isFunction(this[method])) { - return this[method](); - } - // otherwise get directly - return this.attrs[attr]; - }, - /** - * get ancestors - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Collection} - * @example - * shape.getAncestors().each(function(node) { - * console.log(node.getId()); - * }) - */ - getAncestors: function() { - var parent = this.getParent(), ancestors = new Konva.Collection(); - - while (parent) { - ancestors.push(parent); - parent = parent.getParent(); - } - - return ancestors; - }, - /** - * get attrs object literal - * @method - * @memberof Konva.Node.prototype - * @returns {Object} - */ - getAttrs: function() { - return this.attrs || {}; - }, - /** - * set multiple attrs at once using an object literal - * @method - * @memberof Konva.Node.prototype - * @param {Object} config object containing key value pairs - * @returns {Konva.Node} - * @example - * node.setAttrs({ - * x: 5, - * fill: 'red' - * }); - */ - setAttrs: function(config) { - var key, method; - - if (!config) { - return this; - } - for (key in config) { - if (key === CHILDREN) { - continue; - } - method = SET + Konva.Util._capitalize(key); - // use setter if available - if (Konva.Util._isFunction(this[method])) { - this[method](config[key]); - } else { - // otherwise set directly - this._setAttr(key, config[key]); - } - } - return this; - }, - /** - * determine if node is listening for events by taking into account ancestors. - * - * Parent | Self | isListening - * listening | listening | - * ----------+-----------+------------ - * T | T | T - * T | F | F - * F | T | T - * F | F | F - * ----------+-----------+------------ - * T | I | T - * F | I | F - * I | I | T - * - * @method - * @memberof Konva.Node.prototype - * @returns {Boolean} - */ - isListening: function() { - return this._getCache(LISTENING, this._isListening); - }, - _isListening: function() { - var listening = this.getListening(), parent = this.getParent(); - - // the following conditions are a simplification of the truth table above. - // please modify carefully - if (listening === 'inherit') { - if (parent) { - return parent.isListening(); - } else { - return true; - } - } else { - return listening; - } - }, - /** - * determine if node is visible by taking into account ancestors. - * - * Parent | Self | isVisible - * visible | visible | - * ----------+-----------+------------ - * T | T | T - * T | F | F - * F | T | T - * F | F | F - * ----------+-----------+------------ - * T | I | T - * F | I | F - * I | I | T - - * @method - * @memberof Konva.Node.prototype - * @returns {Boolean} - */ - isVisible: function() { - return this._getCache(VISIBLE, this._isVisible); - }, - _isVisible: function() { - var visible = this.getVisible(), parent = this.getParent(); - - // the following conditions are a simplification of the truth table above. - // please modify carefully - if (visible === 'inherit') { - if (parent) { - return parent.isVisible(); - } else { - return true; - } - } else { - return visible; - } - }, - /** - * determine if listening is enabled by taking into account descendants. If self or any children - * have _isListeningEnabled set to true, then self also has listening enabled. - * @method - * @memberof Konva.Node.prototype - * @returns {Boolean} - */ - shouldDrawHit: function(canvas) { - var layer = this.getLayer(); - return (canvas && canvas.isCache) || - (layer && - layer.hitGraphEnabled() && - this.isListening() && - this.isVisible()); - }, - /** - * show node - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Node} - */ - show: function() { - this.setVisible(true); - return this; - }, - /** - * hide node. Hidden nodes are no longer detectable - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Node} - */ - hide: function() { - this.setVisible(false); - return this; - }, - /** - * get zIndex relative to the node's siblings who share the same parent - * @method - * @memberof Konva.Node.prototype - * @returns {Integer} - */ - getZIndex: function() { - return this.index || 0; - }, - /** - * get absolute z-index which takes into account sibling - * and ancestor indices - * @method - * @memberof Konva.Node.prototype - * @returns {Integer} - */ - getAbsoluteZIndex: function() { - var depth = this.getDepth(), that = this, index = 0, nodes, len, n, child; - - function addChildren(children) { - nodes = []; - len = children.length; - for (n = 0; n < len; n++) { - child = children[n]; - index++; - - if (child.nodeType !== SHAPE) { - nodes = nodes.concat(child.getChildren().toArray()); - } - - if (child._id === that._id) { - n = len; - } - } - - if (nodes.length > 0 && nodes[0].getDepth() <= depth) { - addChildren(nodes); - } - } - if (that.nodeType !== UPPER_STAGE) { - addChildren(that.getStage().getChildren()); - } - - return index; - }, - /** - * get node depth in node tree. Returns an integer. - * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always - * be >= 2 - * @method - * @memberof Konva.Node.prototype - * @returns {Integer} - */ - getDepth: function() { - var depth = 0, parent = this.parent; - - while (parent) { - depth++; - parent = parent.parent; - } - return depth; - }, - setPosition: function(pos) { - this.setX(pos.x); - this.setY(pos.y); - return this; - }, - getPosition: function() { - return { - x: this.getX(), - y: this.getY() - }; - }, - /** - * get absolute position relative to the top left corner of the stage container div - * or relative to passed node - * @method - * @param {Object} [top] optional parent node - * @memberof Konva.Node.prototype - * @returns {Object} - */ - getAbsolutePosition: function(top) { - var absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), - absoluteTransform = new Konva.Transform(), - offset = this.offset(); - - // clone the matrix array - absoluteTransform.m = absoluteMatrix.slice(); - absoluteTransform.translate(offset.x, offset.y); - - return absoluteTransform.getTranslation(); - }, - /** - * set absolute position - * @method - * @memberof Konva.Node.prototype - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Konva.Node} - */ - setAbsolutePosition: function(pos) { - var origTrans = this._clearTransform(), it; - - // don't clear translation - this.attrs.x = origTrans.x; - this.attrs.y = origTrans.y; - delete origTrans.x; - delete origTrans.y; - - // unravel transform - it = this.getAbsoluteTransform(); - - it.invert(); - it.translate(pos.x, pos.y); - pos = { - x: this.attrs.x + it.getTranslation().x, - y: this.attrs.y + it.getTranslation().y - }; - - this.setPosition({ x: pos.x, y: pos.y }); - this._setTransform(origTrans); - - return this; - }, - _setTransform: function(trans) { - var key; - - for (key in trans) { - this.attrs[key] = trans[key]; - } - - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - }, - _clearTransform: function() { - var trans = { - x: this.getX(), - y: this.getY(), - rotation: this.getRotation(), - scaleX: this.getScaleX(), - scaleY: this.getScaleY(), - offsetX: this.getOffsetX(), - offsetY: this.getOffsetY(), - skewX: this.getSkewX(), - skewY: this.getSkewY() - }; - - this.attrs.x = 0; - this.attrs.y = 0; - this.attrs.rotation = 0; - this.attrs.scaleX = 1; - this.attrs.scaleY = 1; - this.attrs.offsetX = 0; - this.attrs.offsetY = 0; - this.attrs.skewX = 0; - this.attrs.skewY = 0; - - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - - // return original transform - return trans; - }, - /** - * move node by an amount relative to its current position - * @method - * @memberof Konva.Node.prototype - * @param {Object} change - * @param {Number} change.x - * @param {Number} change.y - * @returns {Konva.Node} - * @example - * // move node in x direction by 1px and y direction by 2px - * node.move({ - * x: 1, - * y: 2) - * }); - */ - move: function(change) { - var changeX = change.x, - changeY = change.y, - x = this.getX(), - y = this.getY(); - - if (changeX !== undefined) { - x += changeX; - } - - if (changeY !== undefined) { - y += changeY; - } - - this.setPosition({ x: x, y: y }); - return this; - }, - _eachAncestorReverse: function(func, top) { - var family = [], parent = this.getParent(), len, n; - - // if top node is defined, and this node is top node, - // there's no need to build a family tree. just execute - // func with this because it will be the only node - if (top && top._id === this._id) { - func(this); - return true; - } - - family.unshift(this); - - while (parent && (!top || parent._id !== top._id)) { - family.unshift(parent); - parent = parent.parent; - } - - len = family.length; - for (n = 0; n < len; n++) { - func(family[n]); - } - }, - /** - * rotate node by an amount in degrees relative to its current rotation - * @method - * @memberof Konva.Node.prototype - * @param {Number} theta - * @returns {Konva.Node} - */ - rotate: function(theta) { - this.setRotation(this.getRotation() + theta); - return this; - }, - /** - * move node to the top of its siblings - * @method - * @memberof Konva.Node.prototype - * @returns {Boolean} - */ - moveToTop: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveToTop function is ignored.'); - return false; - } - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.push(this); - this.parent._setChildrenIndices(); - return true; - }, - /** - * move node up - * @method - * @memberof Konva.Node.prototype - * @returns {Boolean} flag is moved or not - */ - moveUp: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveUp function is ignored.'); - return false; - } - var index = this.index, len = this.parent.getChildren().length; - if (index < len - 1) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index + 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** - * move node down - * @method - * @memberof Konva.Node.prototype - * @returns {Boolean} - */ - moveDown: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveDown function is ignored.'); - return false; - } - var index = this.index; - if (index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index - 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** - * move node to the bottom of its siblings - * @method - * @memberof Konva.Node.prototype - * @returns {Boolean} - */ - moveToBottom: function() { - if (!this.parent) { - Konva.Util.warn( - 'Node has no parent. moveToBottom function is ignored.' - ); - return false; - } - var index = this.index; - if (index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.unshift(this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** - * set zIndex relative to siblings - * @method - * @memberof Konva.Node.prototype - * @param {Integer} zIndex - * @returns {Konva.Node} - */ - setZIndex: function(zIndex) { - if (!this.parent) { - Konva.Util.warn('Node has no parent. zIndex parameter is ignored.'); - return false; - } - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.splice(zIndex, 0, this); - this.parent._setChildrenIndices(); - return this; - }, - /** - * get absolute opacity - * @method - * @memberof Konva.Node.prototype - * @returns {Number} - */ - getAbsoluteOpacity: function() { - return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); - }, - _getAbsoluteOpacity: function() { - var absOpacity = this.getOpacity(); - var parent = this.getParent(); - if (parent && !parent._isUnderCache) { - absOpacity *= this.getParent().getAbsoluteOpacity(); - } - return absOpacity; - }, - /** - * move node to another container - * @method - * @memberof Konva.Node.prototype - * @param {Container} newContainer - * @returns {Konva.Node} - * @example - * // move node from current layer into layer2 - * node.moveTo(layer2); - */ - moveTo: function(newContainer) { - // do nothing if new container is already parent - if (this.getParent() !== newContainer) { - // this.remove my be overrided by drag and drop - // buy we need original - (this.__originalRemove || this.remove).call(this); - newContainer.add(this); - } - return this; - }, - /** - * convert Node into an object for serialization. Returns an object. - * @method - * @memberof Konva.Node.prototype - * @returns {Object} - */ - toObject: function() { - var obj = {}, attrs = this.getAttrs(), key, val, getter, defaultValue; - - obj.attrs = {}; - - for (key in attrs) { - val = attrs[key]; - getter = this[key]; - // remove attr value so that we can extract the default value from the getter - delete attrs[key]; - defaultValue = getter ? getter.call(this) : null; - // restore attr value - attrs[key] = val; - if (defaultValue !== val) { - obj.attrs[key] = val; - } - } - - obj.className = this.getClassName(); - return Konva.Util._prepareToStringify(obj); - }, - /** - * convert Node into a JSON string. Returns a JSON string. - * @method - * @memberof Konva.Node.prototype - * @returns {String}} - */ - toJSON: function() { - return JSON.stringify(this.toObject()); - }, - /** - * get parent container - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Node} - */ - getParent: function() { - return this.parent; - }, - /** - * get all ancestros (parent then parent of the parent, etc) of the node - * @method - * @memberof Konva.Node.prototype - * @param {String} [selector] selector for search - * @param {Boolean} [includeSelf] show we think that node is ancestro itself? - * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) - * @returns {Array} [ancestors] - * @example - * // get one of the parent group - * var parentGroups = node.findAncestors('Group'); - */ - findAncestors: function(selector, includeSelf, stopNode) { - var res = []; - - if (includeSelf && this._isMatch(selector)) { - res.push(this); - } - var ancestor = this.parent; - while (ancestor) { - if (ancestor === stopNode) { - return res; - } - if (ancestor._isMatch(selector)) { - res.push(ancestor); - } - ancestor = ancestor.parent; - } - return res; - }, - /** - * get ancestor (parent or parent of the parent, etc) of the node that match passed selector - * @method - * @memberof Konva.Node.prototype - * @param {String} [selector] selector for search - * @param {Boolean} [includeSelf] show we think that node is ancestro itself? - * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) - * @returns {Konva.Node} ancestor - * @example - * // get one of the parent group - * var group = node.findAncestors('.mygroup'); - */ - findAncestor: function(selector, includeSelf, stopNode) { - return this.findAncestors(selector, includeSelf, stopNode)[0]; - }, - // is current node match passed selector? - _isMatch: function(selector) { - if (!selector) { - return false; - } - var selectorArr = selector.replace(/ /g, '').split(','), - len = selectorArr.length, - n, - sel; - - for (n = 0; n < len; n++) { - sel = selectorArr[n]; - if (!Konva.Util.isValidSelector(sel)) { - Konva.Util.warn( - 'Selector "' + - sel + - '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' - ); - Konva.Util.warn( - 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' - ); - Konva.Util.warn('Konva is awesome, right?'); - } - // id selector - if (sel.charAt(0) === '#') { - if (this.id() === sel.slice(1)) { - return true; - } - } else if (sel.charAt(0) === '.') { - // name selector - if (this.hasName(sel.slice(1))) { - return true; - } - } else if (this._get(sel).length !== 0) { - return true; - } - } - return false; - }, - /** - * get layer ancestor - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Layer} - */ - getLayer: function() { - var parent = this.getParent(); - return parent ? parent.getLayer() : null; - }, - /** - * get stage ancestor - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Stage} - */ - getStage: function() { - return this._getCache(STAGE, this._getStage); - }, - _getStage: function() { - var parent = this.getParent(); - if (parent) { - return parent.getStage(); - } else { - return undefined; - } - }, - /** - * fire event - * @method - * @memberof Konva.Node.prototype - * @param {String} eventType event type. can be a regular event, like click, mouseover, or mouseout, or it can be a custom event, like myCustomEvent - * @param {Event} [evt] event object - * @param {Boolean} [bubble] setting the value to false, or leaving it undefined, will result in the event - * not bubbling. Setting the value to true will result in the event bubbling. - * @returns {Konva.Node} - * @example - * // manually fire click event - * node.fire('click'); - * - * // fire custom event - * node.fire('foo'); - * - * // fire custom event with custom event object - * node.fire('foo', { - * bar: 10 - * }); - * - * // fire click event that bubbles - * node.fire('click', null, true); - */ - fire: function(eventType, evt, bubble) { - evt = evt || {}; - evt.target = evt.target || this; - // bubble - if (bubble) { - this._fireAndBubble(eventType, evt); - } else { - // no bubble - this._fire(eventType, evt); - } - return this; - }, - /** - * get absolute transform of the node which takes into - * account its ancestor transforms - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Transform} - */ - getAbsoluteTransform: function(top) { - // if using an argument, we can't cache the result. - if (top) { - return this._getAbsoluteTransform(top); - } else { - // if no argument, we can cache the result - return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); - } - }, - _getAbsoluteTransform: function(top) { - var at = new Konva.Transform(), transformsEnabled, trans; - - // start with stage and traverse downwards to self - this._eachAncestorReverse( - function(node) { - transformsEnabled = node.transformsEnabled(); - trans = node.getTransform(); - - if (transformsEnabled === 'all') { - at.multiply(trans); - } else if (transformsEnabled === 'position') { - at.translate(node.x(), node.y()); - } - }, - top - ); - return at; - }, - /** - * get absolute scale of the node which takes into - * account its ancestor scales - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Transform} - */ - getAbsoluteScale: function(top) { - // if using an argument, we can't cache the result. - if (top) { - return this._getAbsoluteTransform(top); - } else { - // if no argument, we can cache the result - return this._getCache(ABSOLUTE_SCALE, this._getAbsoluteScale); - } - }, - _getAbsoluteScale: function(top) { - // this is special logic for caching with some shapes with shadow - var parent = this; - while (parent) { - if (parent._isUnderCache) { - top = parent; - } - parent = parent.getParent(); - } - - var scaleX = 1, scaleY = 1; - - // start with stage and traverse downwards to self - this._eachAncestorReverse( - function(node) { - scaleX *= node.scaleX(); - scaleY *= node.scaleY(); - }, - top - ); - return { - x: scaleX, - y: scaleY - }; - }, - /** - * get transform of the node - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Transform} - */ - getTransform: function() { - return this._getCache(TRANSFORM, this._getTransform); - }, - _getTransform: function() { - var m = new Konva.Transform(), - x = this.getX(), - y = this.getY(), - rotation = Konva.getAngle(this.getRotation()), - scaleX = this.getScaleX(), - scaleY = this.getScaleY(), - skewX = this.getSkewX(), - skewY = this.getSkewY(), - offsetX = this.getOffsetX(), - offsetY = this.getOffsetY(); - - if (x !== 0 || y !== 0) { - m.translate(x, y); - } - if (rotation !== 0) { - m.rotate(rotation); - } - if (skewX !== 0 || skewY !== 0) { - m.skew(skewX, skewY); - } - if (scaleX !== 1 || scaleY !== 1) { - m.scale(scaleX, scaleY); - } - if (offsetX !== 0 || offsetY !== 0) { - m.translate((-1) * offsetX, (-1) * offsetY); - } - - return m; - }, - /** - * clone node. Returns a new Node instance with identical attributes. You can also override - * the node properties with an object literal, enabling you to use an existing node as a template - * for another node - * @method - * @memberof Konva.Node.prototype - * @param {Object} obj override attrs - * @returns {Konva.Node} - * @example - * // simple clone - * var clone = node.clone(); - * - * // clone a node and override the x position - * var clone = rect.clone({ - * x: 5 - * }); - */ - clone: function(obj) { - // instantiate new node - var attrs = Konva.Util.cloneObject(this.attrs), - key, - allListeners, - len, - n, - listener; - // filter black attrs - for (var i in CLONE_BLACK_LIST) { - var blockAttr = CLONE_BLACK_LIST[i]; - delete attrs[blockAttr]; - } - // apply attr overrides - for (key in obj) { - attrs[key] = obj[key]; - } - - var node = new this.constructor(attrs); - // copy over listeners - for (key in this.eventListeners) { - allListeners = this.eventListeners[key]; - len = allListeners.length; - for (n = 0; n < len; n++) { - listener = allListeners[n]; - /* - * don't include konva namespaced listeners because - * these are generated by the constructors - */ - if (listener.name.indexOf(KONVA) < 0) { - // if listeners array doesn't exist, then create it - if (!node.eventListeners[key]) { - node.eventListeners[key] = []; - } - node.eventListeners[key].push(listener); - } - } - } - return node; - }, - _toKonvaCanvas: function(config) { - config = config || {}; - - var stage = this.getStage(), - x = config.x || 0, - y = config.y || 0, - pixelRatio = config.pixelRatio || 1, - canvas = new Konva.SceneCanvas({ - width: config.width || - this.getWidth() || - (stage ? stage.getWidth() : 0), - height: config.height || - this.getHeight() || - (stage ? stage.getHeight() : 0), - pixelRatio: pixelRatio - }), - context = canvas.getContext(); - - context.save(); - - if (x || y) { - context.translate((-1) * x, (-1) * y); - } - - this.drawScene(canvas); - context.restore(); - - return canvas; - }, - /** - * converts node into an canvas element. - * @method - * @memberof Konva.Node.prototype - * @param {Object} config - * @param {Function} config.callback function executed when the composite has completed - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image. Default is 1. - * @example - * var canvas = node.toCanvas(); - */ - toCanvas: function(config) { - return this._toKonvaCanvas(config)._canvas; - }, - /** - * Creates a composite data URL. If MIME type is not - * specified, then "image/png" will result. For "image/jpeg", specify a quality - * level as quality (range 0.0 - 1.0) - * @method - * @memberof Konva.Node.prototype - * @param {Object} config - * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image url. Default is 1 - * @returns {String} - */ - toDataURL: function(config) { - config = config || {}; - var mimeType = config.mimeType || null, quality = config.quality || null; - return this._toKonvaCanvas(config).toDataURL(mimeType, quality); - }, - /** - * converts node into an image. Since the toImage - * method is asynchronous, a callback is required. toImage is most commonly used - * to cache complex drawings as an image so that they don't have to constantly be redrawn - * @method - * @memberof Konva.Node.prototype - * @param {Object} config - * @param {Function} config.callback function executed when the composite has completed - * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image. Default is 1. - * @example - * var image = node.toImage({ - * callback: function(img) { - * // do stuff with img - * } - * }); - */ - toImage: function(config) { - if (!config || !config.callback) { - throw 'callback required for toImage method config argument'; - } - Konva.Util._getImage(this.toDataURL(config), function(img) { - config.callback(img); - }); - }, - setSize: function(size) { - this.setWidth(size.width); - this.setHeight(size.height); - return this; - }, - getSize: function() { - return { - width: this.getWidth(), - height: this.getHeight() - }; - }, - getWidth: function() { - return this.attrs.width || 0; - }, - getHeight: function() { - return this.attrs.height || 0; - }, - /** - * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. - * @method - * @memberof Konva.Node.prototype - * @returns {String} - */ - getClassName: function() { - return this.className || this.nodeType; - }, - /** - * get the node type, which may return Stage, Layer, Group, or Node - * @method - * @memberof Konva.Node.prototype - * @returns {String} - */ - getType: function() { - return this.nodeType; - }, - getDragDistance: function() { - // compare with undefined because we need to track 0 value - if (this.attrs.dragDistance !== undefined) { - return this.attrs.dragDistance; - } else if (this.parent) { - return this.parent.getDragDistance(); - } else { - return Konva.dragDistance; - } - }, - _get: function(selector) { - return this.className === selector || this.nodeType === selector - ? [this] - : []; - }, - _off: function(type, name) { - var evtListeners = this.eventListeners[type], i, evtName; - - for (i = 0; i < evtListeners.length; i++) { - evtName = evtListeners[i].name; - // the following two conditions must be true in order to remove a handler: - // 1) the current event name cannot be konva unless the event name is konva - // this enables developers to force remove a konva specific listener for whatever reason - // 2) an event name is not specified, or if one is specified, it matches the current event name - if ( - (evtName !== 'konva' || name === 'konva') && - (!name || evtName === name) - ) { - evtListeners.splice(i, 1); - if (evtListeners.length === 0) { - delete this.eventListeners[type]; - break; - } - i--; - } - } - }, - _fireChangeEvent: function(attr, oldVal, newVal) { - this._fire(attr + CHANGE, { - oldVal: oldVal, - newVal: newVal - }); - }, - setId: function(id) { - var oldId = this.getId(); - - Konva._removeId(oldId); - Konva._addId(this, id); - this._setAttr(ID, id); - return this; - }, - setName: function(name) { - var oldNames = (this.getName() || '').split(/\s/g); - var newNames = (name || '').split(/\s/g); - var subname, i; - // remove all subnames - for (i = 0; i < oldNames.length; i++) { - subname = oldNames[i]; - if (newNames.indexOf(subname) === -1 && subname) { - Konva._removeName(subname, this._id); - } - } - - // add new names - for (i = 0; i < newNames.length; i++) { - subname = newNames[i]; - if (oldNames.indexOf(subname) === -1 && subname) { - Konva._addName(this, subname); - } - } - - this._setAttr(NAME, name); - return this; - }, - // naming methods - /** - * add name to node - * @method - * @memberof Konva.Node.prototype - * @param {String} name - * @returns {Konva.Node} - * @example - * node.name('red'); - * node.addName('selected'); - * node.name(); // return 'red selected' - */ - addName: function(name) { - if (!this.hasName(name)) { - var oldName = this.name(); - var newName = oldName ? oldName + ' ' + name : name; - this.setName(newName); - } - return this; - }, - /** - * check is node has name - * @method - * @memberof Konva.Node.prototype - * @param {String} name - * @returns {Boolean} - * @example - * node.name('red'); - * node.hasName('red'); // return true - * node.hasName('selected'); // return false - */ - hasName: function(name) { - var names = (this.name() || '').split(/\s/g); - return names.indexOf(name) !== -1; - }, - /** - * remove name from node - * @method - * @memberof Konva.Node.prototype - * @param {String} name - * @returns {Konva.Node} - * @example - * node.name('red selected'); - * node.removeName('selected'); - * node.hasName('selected'); // return false - * node.name(); // return 'red' - */ - removeName: function(name) { - var names = (this.name() || '').split(/\s/g); - var index = names.indexOf(name); - if (index !== -1) { - names.splice(index, 1); - this.setName(names.join(' ')); - } - return this; - }, - /** - * set attr - * @method - * @memberof Konva.Node.prototype - * @param {String} attr - * @param {*} val - * @returns {Konva.Node} - * @example - * node.setAttr('x', 5); - */ - setAttr: function(attr, val) { - var method = SET + Konva.Util._capitalize(attr), func = this[method]; - - if (Konva.Util._isFunction(func)) { - func.call(this, val); - } else { - // otherwise set directly - this._setAttr(attr, val); - } - return this; - }, - _setAttr: function(key, val) { - var oldVal; - oldVal = this.attrs[key]; - if (oldVal === val) { - return; - } - if (val === undefined || val === null) { - delete this.attrs[key]; - } else { - this.attrs[key] = val; - } - this._fireChangeEvent(key, oldVal, val); - }, - _setComponentAttr: function(key, component, val) { - var oldVal; - if (val !== undefined) { - oldVal = this.attrs[key]; - - if (!oldVal) { - // set value to default value using getAttr - this.attrs[key] = this.getAttr(key); - } - - this.attrs[key][component] = val; - this._fireChangeEvent(key, oldVal, val); - } - }, - _fireAndBubble: function(eventType, evt, compareShape) { - var okayToRun = true; - - if (evt && this.nodeType === SHAPE) { - evt.target = this; - } - - if ( - eventType === MOUSEENTER && - compareShape && - (this._id === compareShape._id || - (this.isAncestorOf && this.isAncestorOf(compareShape))) - ) { - okayToRun = false; - } else if ( - eventType === MOUSELEAVE && - compareShape && - (this._id === compareShape._id || - (this.isAncestorOf && this.isAncestorOf(compareShape))) - ) { - okayToRun = false; - } - if (okayToRun) { - this._fire(eventType, evt); - - // simulate event bubbling - var stopBubble = (eventType === MOUSEENTER || - eventType === MOUSELEAVE) && - (compareShape && - compareShape.isAncestorOf && - compareShape.isAncestorOf(this) && - !compareShape.isAncestorOf(this.parent)); - if ( - ((evt && !evt.cancelBubble) || !evt) && - this.parent && - this.parent.isListening() && - !stopBubble - ) { - if (compareShape && compareShape.parent) { - this._fireAndBubble.call( - this.parent, - eventType, - evt, - compareShape.parent - ); - } else { - this._fireAndBubble.call(this.parent, eventType, evt); - } - } - } - }, - _fire: function(eventType, evt) { - var events = this.eventListeners[eventType], i; - - evt = evt || {}; - evt.currentTarget = this; - evt.type = eventType; - - if (events) { - for (i = 0; i < events.length; i++) { - events[i].handler.call(this, evt); - } - } - }, - /** - * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn - * @method - * @memberof Konva.Node.prototype - * @returns {Konva.Node} - */ - draw: function() { - this.drawScene(); - this.drawHit(); - return this; - } - }); - - /** - * create node with JSON string or an Object. De-serializtion does not generate custom - * shape drawing functions, images, or event handlers (this would make the - * serialized object huge). If your app uses custom shapes, images, and - * event handlers (it probably does), then you need to select the appropriate - * shapes after loading the stage and set these properties via on(), setDrawFunc(), - * and setImage() methods - * @method - * @memberof Konva.Node - * @param {String|Object} json string or object - * @param {Element} [container] optional container dom element used only if you're - * creating a stage node - */ - Konva.Node.create = function(data, container) { - if (Konva.Util._isString(data)) { - data = JSON.parse(data); - } - return this._createNode(data, container); - }; - Konva.Node._createNode = function(obj, container) { - var className = Konva.Node.prototype.getClassName.call(obj), - children = obj.children, - no, - len, - n; - - // if container was passed in, add it to attrs - if (container) { - obj.attrs.container = container; - } - - no = new Konva[className](obj.attrs); - if (children) { - len = children.length; - for (n = 0; n < len; n++) { - no.add(this._createNode(children[n])); - } - } - - return no; - }; - - // =========================== add getters setters =========================== - - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'position'); - /** - * get/set node position relative to parent - * @name position - * @method - * @memberof Konva.Node.prototype - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Object} - * @example - * // get position - * var position = node.position(); - * - * // set position - * node.position({ - * x: 5 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'x', 0); - - /** - * get/set x position - * @name x - * @method - * @memberof Konva.Node.prototype - * @param {Number} x - * @returns {Object} - * @example - * // get x - * var x = node.x(); - * - * // set x - * node.x(5); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'y', 0); - - /** - * get/set y position - * @name y - * @method - * @memberof Konva.Node.prototype - * @param {Number} y - * @returns {Integer} - * @example - * // get y - * var y = node.y(); - * - * // set y - * node.y(5); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'opacity', 1); - - /** - * get/set opacity. Opacity values range from 0 to 1. - * A node with an opacity of 0 is fully transparent, and a node - * with an opacity of 1 is fully opaque - * @name opacity - * @method - * @memberof Konva.Node.prototype - * @param {Object} opacity - * @returns {Number} - * @example - * // get opacity - * var opacity = node.opacity(); - * - * // set opacity - * node.opacity(0.5); - */ - - Konva.Factory.addGetter(Konva.Node, 'name'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'name'); - - /** - * get/set name - * @name name - * @method - * @memberof Konva.Node.prototype - * @param {String} name - * @returns {String} - * @example - * // get name - * var name = node.name(); - * - * // set name - * node.name('foo'); - * - * // also node may have multiple names (as css classes) - * node.name('foo bar'); - */ - - Konva.Factory.addGetter(Konva.Node, 'id'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'id'); - - /** - * get/set id. Id is global for whole page. - * @name id - * @method - * @memberof Konva.Node.prototype - * @param {String} id - * @returns {String} - * @example - * // get id - * var name = node.id(); - * - * // set id - * node.id('foo'); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'rotation', 0); - - /** - * get/set rotation in degrees - * @name rotation - * @method - * @memberof Konva.Node.prototype - * @param {Number} rotation - * @returns {Number} - * @example - * // get rotation in degrees - * var rotation = node.rotation(); - * - * // set rotation in degrees - * node.rotation(45); - */ - - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'scale', ['x', 'y']); - - /** - * get/set scale - * @name scale - * @param {Object} scale - * @param {Number} scale.x - * @param {Number} scale.y - * @method - * @memberof Konva.Node.prototype - * @returns {Object} - * @example - * // get scale - * var scale = node.scale(); - * - * // set scale - * shape.scale({ - * x: 2 - * y: 3 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'scaleX', 1); - - /** - * get/set scale x - * @name scaleX - * @param {Number} x - * @method - * @memberof Konva.Node.prototype - * @returns {Number} - * @example - * // get scale x - * var scaleX = node.scaleX(); - * - * // set scale x - * node.scaleX(2); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'scaleY', 1); - - /** - * get/set scale y - * @name scaleY - * @param {Number} y - * @method - * @memberof Konva.Node.prototype - * @returns {Number} - * @example - * // get scale y - * var scaleY = node.scaleY(); - * - * // set scale y - * node.scaleY(2); - */ - - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'skew', ['x', 'y']); - - /** - * get/set skew - * @name skew - * @param {Object} skew - * @param {Number} skew.x - * @param {Number} skew.y - * @method - * @memberof Konva.Node.prototype - * @returns {Object} - * @example - * // get skew - * var skew = node.skew(); - * - * // set skew - * node.skew({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'skewX', 0); - - /** - * get/set skew x - * @name skewX - * @param {Number} x - * @method - * @memberof Konva.Node.prototype - * @returns {Number} - * @example - * // get skew x - * var skewX = node.skewX(); - * - * // set skew x - * node.skewX(3); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'skewY', 0); - - /** - * get/set skew y - * @name skewY - * @param {Number} y - * @method - * @memberof Konva.Node.prototype - * @returns {Number} - * @example - * // get skew y - * var skewY = node.skewY(); - * - * // set skew y - * node.skewY(3); - */ - - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'offset', ['x', 'y']); - - /** - * get/set offset. Offsets the default position and rotation point - * @method - * @memberof Konva.Node.prototype - * @param {Object} offset - * @param {Number} offset.x - * @param {Number} offset.y - * @returns {Object} - * @example - * // get offset - * var offset = node.offset(); - * - * // set offset - * node.offset({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'offsetX', 0); - - /** - * get/set offset x - * @name offsetX - * @method - * @memberof Konva.Node.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get offset x - * var offsetX = node.offsetX(); - * - * // set offset x - * node.offsetX(3); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'offsetY', 0); - - /** - * get/set offset y - * @name offsetY - * @method - * @memberof Konva.Node.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get offset y - * var offsetY = node.offsetY(); - * - * // set offset y - * node.offsetY(3); - */ - - Konva.Factory.addSetter(Konva.Node, 'dragDistance'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'dragDistance'); - - /** - * get/set drag distance - * @name dragDistance - * @method - * @memberof Konva.Node.prototype - * @param {Number} distance - * @returns {Number} - * @example - * // get drag distance - * var dragDistance = node.dragDistance(); - * - * // set distance - * // node starts dragging only if pointer moved more then 3 pixels - * node.dragDistance(3); - * // or set globally - * Konva.dragDistance = 3; - */ - - Konva.Factory.addSetter(Konva.Node, 'width', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'width'); - /** - * get/set width - * @name width - * @method - * @memberof Konva.Node.prototype - * @param {Number} width - * @returns {Number} - * @example - * // get width - * var width = node.width(); - * - * // set width - * node.width(100); - */ - - Konva.Factory.addSetter(Konva.Node, 'height', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'height'); - /** - * get/set height - * @name height - * @method - * @memberof Konva.Node.prototype - * @param {Number} height - * @returns {Number} - * @example - * // get height - * var height = node.height(); - * - * // set height - * node.height(100); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'listening', 'inherit'); - /** - * get/set listenig attr. If you need to determine if a node is listening or not - * by taking into account its parents, use the isListening() method - * @name listening - * @method - * @memberof Konva.Node.prototype - * @param {Boolean|String} listening Can be "inherit", true, or false. The default is "inherit". - * @returns {Boolean|String} - * @example - * // get listening attr - * var listening = node.listening(); - * - * // stop listening for events - * node.listening(false); - * - * // listen for events - * node.listening(true); - * - * // listen to events according to the parent - * node.listening('inherit'); - */ - - /** - * get/set preventDefault - * By default all shapes will prevent default behaviour - * of a browser on a pointer move or tap. - * that will prevent native scrolling when you are trying to drag&drop a node - * but sometimes you may need to enable default actions - * in that case you can set the property to false - * @name preventDefault - * @method - * @memberof Konva.Node.prototype - * @param {Number} preventDefault - * @returns {Number} - * @example - * // get preventDefault - * var shouldPrevent = shape.preventDefault(); - * - * // set preventDefault - * shape.preventDefault(false); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'preventDefault', true); - - Konva.Factory.addGetterSetter(Konva.Node, 'filters', undefined, function( - val - ) { - this._filterUpToDate = false; - return val; - }); - /** - * get/set filters. Filters are applied to cached canvases - * @name filters - * @method - * @memberof Konva.Node.prototype - * @param {Array} filters array of filters - * @returns {Array} - * @example - * // get filters - * var filters = node.filters(); - * - * // set a single filter - * node.cache(); - * node.filters([Konva.Filters.Blur]); - * - * // set multiple filters - * node.cache(); - * node.filters([ - * Konva.Filters.Blur, - * Konva.Filters.Sepia, - * Konva.Filters.Invert - * ]); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'visible', 'inherit'); - /** - * get/set visible attr. Can be "inherit", true, or false. The default is "inherit". - * If you need to determine if a node is visible or not - * by taking into account its parents, use the isVisible() method - * @name visible - * @method - * @memberof Konva.Node.prototype - * @param {Boolean|String} visible - * @returns {Boolean|String} - * @example - * // get visible attr - * var visible = node.visible(); - * - * // make invisible - * node.visible(false); - * - * // make visible - * node.visible(true); - * - * // make visible according to the parent - * node.visible('inherit'); - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'transformsEnabled', 'all'); - - /** - * get/set transforms that are enabled. Can be "all", "none", or "position". The default - * is "all" - * @name transformsEnabled - * @method - * @memberof Konva.Node.prototype - * @param {String} enabled - * @returns {String} - * @example - * // enable position transform only to improve draw performance - * node.transformsEnabled('position'); - * - * // enable all transforms - * node.transformsEnabled('all'); - */ - - /** - * get/set node size - * @name size - * @method - * @memberof Konva.Node.prototype - * @param {Object} size - * @param {Number} size.width - * @param {Number} size.height - * @returns {Object} - * @example - * // get node size - * var size = node.size(); - * var x = size.x; - * var y = size.y; - * - * // set size - * node.size({ - * width: 100, - * height: 200 - * }); - */ - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'size'); - - Konva.Factory.backCompat(Konva.Node, { - rotateDeg: 'rotate', - setRotationDeg: 'setRotation', - getRotationDeg: 'getRotation' - }); - - Konva.Collection.mapMethods(Konva.Node); -})(Konva); - +/* + * Konva JavaScript Framework v1.4.0 + * http://konvajs.github.io/ + * Licensed under the MIT or GPL Version 2 licenses. + * Date: Fri Feb 24 2017 + * + * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) + * Modified work Copyright (C) 2014 - 2015 by Anton Lavrenov (Konva) + * + * @license + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// runtime check for already included Konva +(function(global) { + 'use strict'; + /** + * @namespace Konva + */ + + var PI_OVER_180 = Math.PI / 180; + + var Konva = { + // public + version: '1.4.0', + + // private + stages: [], + idCounter: 0, + ids: {}, + names: {}, + shapes: {}, + listenClickTap: false, + inDblClickWindow: false, + + // configurations + enableTrace: false, + traceArrMax: 100, + dblClickWindow: 400, + /** + * Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device. + * But you may override such property, if you want to use your value. + * @property pixelRatio + * @default undefined + * @memberof Konva + * @example + * Konva.pixelRatio = 1; + */ + pixelRatio: undefined, + /** + * Drag distance property. If you start to drag a node you may want to wait until pointer is moved to some distance from start point, + * only then start dragging. + * @property dragDistance + * @default 0 + * @memberof Konva + * @example + * Konva.dragDistance = 10; + */ + dragDistance: 0, + /** + * Use degree values for angle properties. You may set this property to false if you want to use radiant values. + * @property angleDeg + * @default true + * @memberof Konva + * @example + * node.rotation(45); // 45 degrees + * Konva.angleDeg = false; + * node.rotation(Math.PI / 2); // PI/2 radian + */ + angleDeg: true, + /** + * Show different warnings about errors or wrong API usage + * @property showWarnings + * @default true + * @memberof Konva + * @example + * Konva.showWarnings = false; + */ + showWarnings: true, + + /** + * @namespace Filters + * @memberof Konva + */ + Filters: {}, + + /** + * returns whether or not drag and drop is currently active + * @method + * @memberof Konva + */ + isDragging: function() { + var dd = Konva.DD; + + // if DD is not included with the build, then + // drag and drop is not even possible + if (dd) { + return dd.isDragging; + } + return false; + }, + /** + * returns whether or not a drag and drop operation is ready, but may + * not necessarily have started + * @method + * @memberof Konva + */ + isDragReady: function() { + var dd = Konva.DD; + + // if DD is not included with the build, then + // drag and drop is not even possible + if (dd) { + return !!dd.node; + } + return false; + }, + _addId: function(node, id) { + if (id !== undefined) { + this.ids[id] = node; + } + }, + _removeId: function(id) { + if (id !== undefined) { + delete this.ids[id]; + } + }, + _addName: function(node, name) { + if (name) { + if (!this.names[name]) { + this.names[name] = []; + } + this.names[name].push(node); + } + }, + _removeName: function(name, _id) { + if (!name) { + return; + } + var nodes = this.names[name]; + if (!nodes) { + return; + } + for (var n = 0; n < nodes.length; n++) { + var no = nodes[n]; + if (no._id === _id) { + nodes.splice(n, 1); + } + } + if (nodes.length === 0) { + delete this.names[name]; + } + }, + getAngle: function(angle) { + return this.angleDeg ? angle * PI_OVER_180 : angle; + }, + _detectIE: function(ua) { + var msie = ua.indexOf('msie '); + if (msie > 0) { + // IE 10 or older => return version number + return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); + } + + var trident = ua.indexOf('trident/'); + if (trident > 0) { + // IE 11 => return version number + var rv = ua.indexOf('rv:'); + return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); + } + + var edge = ua.indexOf('edge/'); + if (edge > 0) { + // Edge (IE 12+) => return version number + return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); + } + + // other browser + return false; + }, + _parseUA: function(userAgent) { + var ua = userAgent.toLowerCase(), + // jQuery UA regex + match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + (ua.indexOf('compatible') < 0 && + /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)) || [], + // adding mobile flag as well + mobile = !!userAgent.match( + /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i + ), + ieMobile = !!userAgent.match(/IEMobile/i); + + return { + browser: match[1] || '', + version: match[2] || '0', + isIE: Konva._detectIE(ua), + // adding mobile flab + mobile: mobile, + ieMobile: ieMobile // If this is true (i.e., WP8), then Konva touch events are executed instead of equivalent Konva mouse events + }; + }, + // user agent + UA: undefined + }; + + var glob = typeof global !== 'undefined' + ? global + : typeof window !== 'undefined' + ? window + : typeof WorkerGlobalScope !== 'undefined' ? self : {}; + + Konva.UA = Konva._parseUA((glob.navigator && glob.navigator.userAgent) || ''); + + if (glob.Konva) { + console.error( + 'Konva instance is already exist in current eviroment. ' + + 'Please use only one instance.' + ); + } + glob.Konva = Konva; + Konva.global = glob; + + if (typeof exports === 'object') { + // runtime-check for browserify and nw.js (node-webkit) + if (glob.window && glob.window.document) { + Konva.document = glob.window.document; + Konva.window = glob.window; + } else { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + var Canvas = require('canvas'); + var jsdom = require('jsdom').jsdom; + + Konva.window = jsdom( + '' + ).defaultView; + Konva.document = Konva.window.document; + Konva.window.Image = Canvas.Image; + Konva._nodeCanvas = Canvas; + } + module.exports = Konva; + return; + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(function() { + return Konva; + }); + } + Konva.document = document; + Konva.window = window; +})(typeof global !== 'undefined' ? global : window); + +/*eslint-disable eqeqeq, no-cond-assign, no-empty*/ (function() { 'use strict'; /** - * Grayscale Filter - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Grayscale]); + * Collection constructor. Collection extends + * Array. This class is used in conjunction with {@link Konva.Container#get} + * @constructor + * @memberof Konva + */ + Konva.Collection = function() { + var args = [].slice.call(arguments), length = args.length, i = 0; + + this.length = length; + for (; i < length; i++) { + this[i] = args[i]; + } + return this; + }; + Konva.Collection.prototype = []; + /** + * iterate through node array and run a function for each node. + * The node and index is passed into the function + * @method + * @memberof Konva.Collection.prototype + * @param {Function} func + * @example + * // get all nodes with name foo inside layer, and set x to 10 for each + * layer.get('.foo').each(function(shape, n) { + * shape.setX(10); + * }); + */ + Konva.Collection.prototype.each = function(func) { + for (var n = 0; n < this.length; n++) { + func(this[n], n); + } + }; + /** + * convert collection into an array + * @method + * @memberof Konva.Collection.prototype + */ + Konva.Collection.prototype.toArray = function() { + var arr = [], len = this.length, n; + + for (n = 0; n < len; n++) { + arr.push(this[n]); + } + return arr; + }; + /** + * convert array into a collection + * @method + * @memberof Konva.Collection + * @param {Array} arr + */ + Konva.Collection.toCollection = function(arr) { + var collection = new Konva.Collection(), len = arr.length, n; + + for (n = 0; n < len; n++) { + collection.push(arr[n]); + } + return collection; + }; + + // map one method by it's name + Konva.Collection._mapMethod = function(methodName) { + Konva.Collection.prototype[methodName] = function() { + var len = this.length, i; + + var args = [].slice.call(arguments); + for (i = 0; i < len; i++) { + this[i][methodName].apply(this[i], args); + } + + return this; + }; + }; + + Konva.Collection.mapMethods = function(constructor) { + var prot = constructor.prototype; + for (var methodName in prot) { + Konva.Collection._mapMethod(methodName); + } + }; + + /* + * Last updated November 2011 + * By Simon Sarris + * www.simonsarris.com + * sarris@acm.org + * + * Free to use and distribute at will + * So long as you are nice to people, etc */ - Konva.Filters.Grayscale = function(imageData) { - var data = imageData.data, len = data.length, i, brightness; - for (i = 0; i < len; i += 4) { - brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; - // red - data[i] = brightness; - // green - data[i + 1] = brightness; - // blue - data[i + 2] = brightness; + /* + * The usage of this class was inspired by some of the work done by a forked + * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform + * class. Modified by Eric Rowell + */ + + /** + * Transform constructor + * @constructor + * @param {Array} [m] Optional six-element matrix + * @memberof Konva + */ + Konva.Transform = function(m) { + this.m = (m && m.slice()) || [1, 0, 0, 1, 0, 0]; + }; + + Konva.Transform.prototype = { + /** + * Copy Konva.Transform object + * @method + * @memberof Konva.Transform.prototype + * @returns {Konva.Transform} + */ + copy: function() { + return new Konva.Transform(this.m); + }, + /** + * Transform point + * @method + * @memberof Konva.Transform.prototype + * @param {Object} point 2D point(x, y) + * @returns {Object} 2D point(x, y) + */ + point: function(point) { + var m = this.m; + return { + x: m[0] * point.x + m[2] * point.y + m[4], + y: m[1] * point.x + m[3] * point.y + m[5] + }; + }, + /** + * Apply translation + * @method + * @memberof Konva.Transform.prototype + * @param {Number} x + * @param {Number} y + * @returns {Konva.Transform} + */ + translate: function(x, y) { + this.m[4] += this.m[0] * x + this.m[2] * y; + this.m[5] += this.m[1] * x + this.m[3] * y; + return this; + }, + /** + * Apply scale + * @method + * @memberof Konva.Transform.prototype + * @param {Number} sx + * @param {Number} sy + * @returns {Konva.Transform} + */ + scale: function(sx, sy) { + this.m[0] *= sx; + this.m[1] *= sx; + this.m[2] *= sy; + this.m[3] *= sy; + return this; + }, + /** + * Apply rotation + * @method + * @memberof Konva.Transform.prototype + * @param {Number} rad Angle in radians + * @returns {Konva.Transform} + */ + rotate: function(rad) { + var c = Math.cos(rad); + var s = Math.sin(rad); + var m11 = this.m[0] * c + this.m[2] * s; + var m12 = this.m[1] * c + this.m[3] * s; + var m21 = this.m[0] * (-s) + this.m[2] * c; + var m22 = this.m[1] * (-s) + this.m[3] * c; + this.m[0] = m11; + this.m[1] = m12; + this.m[2] = m21; + this.m[3] = m22; + return this; + }, + /** + * Returns the translation + * @method + * @memberof Konva.Transform.prototype + * @returns {Object} 2D point(x, y) + */ + getTranslation: function() { + return { + x: this.m[4], + y: this.m[5] + }; + }, + /** + * Apply skew + * @method + * @memberof Konva.Transform.prototype + * @param {Number} sx + * @param {Number} sy + * @returns {Konva.Transform} + */ + skew: function(sx, sy) { + var m11 = this.m[0] + this.m[2] * sy; + var m12 = this.m[1] + this.m[3] * sy; + var m21 = this.m[2] + this.m[0] * sx; + var m22 = this.m[3] + this.m[1] * sx; + this.m[0] = m11; + this.m[1] = m12; + this.m[2] = m21; + this.m[3] = m22; + return this; + }, + /** + * Transform multiplication + * @method + * @memberof Konva.Transform.prototype + * @param {Konva.Transform} matrix + * @returns {Konva.Transform} + */ + multiply: function(matrix) { + var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1]; + var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1]; + + var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3]; + var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3]; + + var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4]; + var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5]; + + this.m[0] = m11; + this.m[1] = m12; + this.m[2] = m21; + this.m[3] = m22; + this.m[4] = dx; + this.m[5] = dy; + return this; + }, + /** + * Invert the matrix + * @method + * @memberof Konva.Transform.prototype + * @returns {Konva.Transform} + */ + invert: function() { + var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]); + var m0 = this.m[3] * d; + var m1 = (-this.m[1]) * d; + var m2 = (-this.m[2]) * d; + var m3 = this.m[0] * d; + var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]); + var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]); + this.m[0] = m0; + this.m[1] = m1; + this.m[2] = m2; + this.m[3] = m3; + this.m[4] = m4; + this.m[5] = m5; + return this; + }, + /** + * return matrix + * @method + * @memberof Konva.Transform.prototype + */ + getMatrix: function() { + return this.m; + }, + /** + * set to absolute position via translation + * @method + * @memberof Konva.Transform.prototype + * @returns {Konva.Transform} + * @author ericdrowell + */ + setAbsolutePosition: function(x, y) { + var m0 = this.m[0], + m1 = this.m[1], + m2 = this.m[2], + m3 = this.m[3], + m4 = this.m[4], + m5 = this.m[5], + yt = (m0 * (y - m5) - m1 * (x - m4)) / (m0 * m3 - m1 * m2), + xt = (x - m4 - m2 * yt) / m0; + + return this.translate(xt, yt); + } + }; + + // CONSTANTS + var CONTEXT_2D = '2d', + OBJECT_ARRAY = '[object Array]', + OBJECT_NUMBER = '[object Number]', + OBJECT_STRING = '[object String]', + PI_OVER_DEG180 = Math.PI / 180, + DEG180_OVER_PI = 180 / Math.PI, + HASH = '#', + EMPTY_STRING = '', + ZERO = '0', + KONVA_WARNING = 'Konva warning: ', + KONVA_ERROR = 'Konva error: ', + RGB_PAREN = 'rgb(', + COLORS = { + aliceblue: [240, 248, 255], + antiquewhite: [250, 235, 215], + aqua: [0, 255, 255], + aquamarine: [127, 255, 212], + azure: [240, 255, 255], + beige: [245, 245, 220], + bisque: [255, 228, 196], + black: [0, 0, 0], + blanchedalmond: [255, 235, 205], + blue: [0, 0, 255], + blueviolet: [138, 43, 226], + brown: [165, 42, 42], + burlywood: [222, 184, 135], + cadetblue: [95, 158, 160], + chartreuse: [127, 255, 0], + chocolate: [210, 105, 30], + coral: [255, 127, 80], + cornflowerblue: [100, 149, 237], + cornsilk: [255, 248, 220], + crimson: [220, 20, 60], + cyan: [0, 255, 255], + darkblue: [0, 0, 139], + darkcyan: [0, 139, 139], + darkgoldenrod: [184, 132, 11], + darkgray: [169, 169, 169], + darkgreen: [0, 100, 0], + darkgrey: [169, 169, 169], + darkkhaki: [189, 183, 107], + darkmagenta: [139, 0, 139], + darkolivegreen: [85, 107, 47], + darkorange: [255, 140, 0], + darkorchid: [153, 50, 204], + darkred: [139, 0, 0], + darksalmon: [233, 150, 122], + darkseagreen: [143, 188, 143], + darkslateblue: [72, 61, 139], + darkslategray: [47, 79, 79], + darkslategrey: [47, 79, 79], + darkturquoise: [0, 206, 209], + darkviolet: [148, 0, 211], + deeppink: [255, 20, 147], + deepskyblue: [0, 191, 255], + dimgray: [105, 105, 105], + dimgrey: [105, 105, 105], + dodgerblue: [30, 144, 255], + firebrick: [178, 34, 34], + floralwhite: [255, 255, 240], + forestgreen: [34, 139, 34], + fuchsia: [255, 0, 255], + gainsboro: [220, 220, 220], + ghostwhite: [248, 248, 255], + gold: [255, 215, 0], + goldenrod: [218, 165, 32], + gray: [128, 128, 128], + green: [0, 128, 0], + greenyellow: [173, 255, 47], + grey: [128, 128, 128], + honeydew: [240, 255, 240], + hotpink: [255, 105, 180], + indianred: [205, 92, 92], + indigo: [75, 0, 130], + ivory: [255, 255, 240], + khaki: [240, 230, 140], + lavender: [230, 230, 250], + lavenderblush: [255, 240, 245], + lawngreen: [124, 252, 0], + lemonchiffon: [255, 250, 205], + lightblue: [173, 216, 230], + lightcoral: [240, 128, 128], + lightcyan: [224, 255, 255], + lightgoldenrodyellow: [250, 250, 210], + lightgray: [211, 211, 211], + lightgreen: [144, 238, 144], + lightgrey: [211, 211, 211], + lightpink: [255, 182, 193], + lightsalmon: [255, 160, 122], + lightseagreen: [32, 178, 170], + lightskyblue: [135, 206, 250], + lightslategray: [119, 136, 153], + lightslategrey: [119, 136, 153], + lightsteelblue: [176, 196, 222], + lightyellow: [255, 255, 224], + lime: [0, 255, 0], + limegreen: [50, 205, 50], + linen: [250, 240, 230], + magenta: [255, 0, 255], + maroon: [128, 0, 0], + mediumaquamarine: [102, 205, 170], + mediumblue: [0, 0, 205], + mediumorchid: [186, 85, 211], + mediumpurple: [147, 112, 219], + mediumseagreen: [60, 179, 113], + mediumslateblue: [123, 104, 238], + mediumspringgreen: [0, 250, 154], + mediumturquoise: [72, 209, 204], + mediumvioletred: [199, 21, 133], + midnightblue: [25, 25, 112], + mintcream: [245, 255, 250], + mistyrose: [255, 228, 225], + moccasin: [255, 228, 181], + navajowhite: [255, 222, 173], + navy: [0, 0, 128], + oldlace: [253, 245, 230], + olive: [128, 128, 0], + olivedrab: [107, 142, 35], + orange: [255, 165, 0], + orangered: [255, 69, 0], + orchid: [218, 112, 214], + palegoldenrod: [238, 232, 170], + palegreen: [152, 251, 152], + paleturquoise: [175, 238, 238], + palevioletred: [219, 112, 147], + papayawhip: [255, 239, 213], + peachpuff: [255, 218, 185], + peru: [205, 133, 63], + pink: [255, 192, 203], + plum: [221, 160, 203], + powderblue: [176, 224, 230], + purple: [128, 0, 128], + rebeccapurple: [102, 51, 153], + red: [255, 0, 0], + rosybrown: [188, 143, 143], + royalblue: [65, 105, 225], + saddlebrown: [139, 69, 19], + salmon: [250, 128, 114], + sandybrown: [244, 164, 96], + seagreen: [46, 139, 87], + seashell: [255, 245, 238], + sienna: [160, 82, 45], + silver: [192, 192, 192], + skyblue: [135, 206, 235], + slateblue: [106, 90, 205], + slategray: [119, 128, 144], + slategrey: [119, 128, 144], + snow: [255, 255, 250], + springgreen: [0, 255, 127], + steelblue: [70, 130, 180], + tan: [210, 180, 140], + teal: [0, 128, 128], + thistle: [216, 191, 216], + transparent: [255, 255, 255, 0], + tomato: [255, 99, 71], + turquoise: [64, 224, 208], + violet: [238, 130, 238], + wheat: [245, 222, 179], + white: [255, 255, 255], + whitesmoke: [245, 245, 245], + yellow: [255, 255, 0], + yellowgreen: [154, 205, 5] + }, + RGB_REGEX = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/; + + /** + * @namespace Util + * @memberof Konva + */ + Konva.Util = { + /* + * cherry-picked utilities from underscore.js + */ + _isElement: function(obj) { + return !!(obj && obj.nodeType == 1); + }, + _isFunction: function(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + }, + _isObject: function(obj) { + return !!obj && obj.constructor === Object; + }, + _isArray: function(obj) { + return Object.prototype.toString.call(obj) === OBJECT_ARRAY; + }, + _isNumber: function(obj) { + return Object.prototype.toString.call(obj) === OBJECT_NUMBER; + }, + _isString: function(obj) { + return Object.prototype.toString.call(obj) === OBJECT_STRING; + }, + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _throttle: function(func, wait, opts) { + var context, args, result; + var timeout = null; + var previous = 0; + var options = opts || {}; + var later = function() { + previous = options.leading === false ? 0 : new Date().getTime(); + timeout = null; + result = func.apply(context, args); + context = args = null; + }; + return function() { + var now = new Date().getTime(); + if (!previous && options.leading === false) { + previous = now; + } + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + /* + * other utils + */ + _hasMethods: function(obj) { + var names = [], key; + + for (key in obj) { + if (!obj.hasOwnProperty(key)) { + continue; + } + if (this._isFunction(obj[key])) { + names.push(key); + } + } + return names.length > 0; + }, + isValidSelector: function(selector) { + if (typeof selector !== 'string') { + return false; + } + var firstChar = selector[0]; + return firstChar === '#' || + firstChar === '.' || + firstChar === firstChar.toUpperCase(); + }, + createCanvasElement: function() { + var canvas = Konva.document.createElement('canvas'); + // on some environments canvas.style is readonly + try { + canvas.style = canvas.style || {}; + } catch (e) {} + return canvas; + }, + isBrowser: function() { + return typeof exports !== 'object'; + }, + _isInDocument: function(el) { + while (el = el.parentNode) { + if (el == Konva.document) { + return true; + } + } + return false; + }, + _simplifyArray: function(arr) { + var retArr = [], len = arr.length, util = Konva.Util, n, val; + + for (n = 0; n < len; n++) { + val = arr[n]; + if (util._isNumber(val)) { + val = Math.round(val * 1000) / 1000; + } else if (!util._isString(val)) { + val = val.toString(); + } + + retArr.push(val); + } + + return retArr; + }, + /* + * arg can be an image object or image data + */ + _getImage: function(arg, callback) { + var imageObj, canvas; + + // if arg is null or undefined + if (!arg) { + callback(null); + } else if (this._isElement(arg)) { + // if arg is already an image object + callback(arg); + } else if (this._isString(arg)) { + // if arg is a string, then it's a data url + imageObj = new Konva.window.Image(); + imageObj.onload = function() { + callback(imageObj); + }; + imageObj.src = arg; + } else if (arg.data) { + //if arg is an object that contains the data property, it's an image object + canvas = Konva.Util.createCanvasElement(); + canvas.width = arg.width; + canvas.height = arg.height; + var _context = canvas.getContext(CONTEXT_2D); + _context.putImageData(arg, 0, 0); + this._getImage(canvas.toDataURL(), callback); + } else { + callback(null); + } + }, + _getRGBAString: function(obj) { + var red = obj.red || 0, + green = obj.green || 0, + blue = obj.blue || 0, + alpha = obj.alpha || 1; + + return ['rgba(', red, ',', green, ',', blue, ',', alpha, ')'].join( + EMPTY_STRING + ); + }, + _rgbToHex: function(r, g, b) { + return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + }, + _hexToRgb: function(hex) { + hex = hex.replace(HASH, EMPTY_STRING); + var bigint = parseInt(hex, 16); + return { + r: bigint >> 16 & 255, + g: bigint >> 8 & 255, + b: bigint & 255 + }; + }, + /** + * return random hex color + * @method + * @memberof Konva.Util.prototype + */ + getRandomColor: function() { + var randColor = (Math.random() * 0xffffff << 0).toString(16); + while (randColor.length < 6) { + randColor = ZERO + randColor; + } + return HASH + randColor; + }, + /** + * return value with default fallback + * @method + * @memberof Konva.Util.prototype + */ + get: function(val, def) { + if (val === undefined) { + return def; + } else { + return val; + } + }, + /** + * get RGB components of a color + * @method + * @memberof Konva.Util.prototype + * @param {String} color + * @example + * // each of the following examples return {r:0, g:0, b:255} + * var rgb = Konva.Util.getRGB('blue'); + * var rgb = Konva.Util.getRGB('#0000ff'); + * var rgb = Konva.Util.getRGB('rgb(0,0,255)'); + */ + getRGB: function(color) { + var rgb; + // color string + if (color in COLORS) { + rgb = COLORS[color]; + return { + r: rgb[0], + g: rgb[1], + b: rgb[2] + }; + } else if (color[0] === HASH) { + // hex + return this._hexToRgb(color.substring(1)); + } else if (color.substr(0, 4) === RGB_PAREN) { + // rgb string + rgb = RGB_REGEX.exec(color.replace(/ /g, '')); + return { + r: parseInt(rgb[1], 10), + g: parseInt(rgb[2], 10), + b: parseInt(rgb[3], 10) + }; + } else { + // default + return { + r: 0, + g: 0, + b: 0 + }; + } + }, + // convert any color string to RGBA object + // from https://github.com/component/color-parser + colorToRGBA: function(str) { + str = str || 'black'; + return Konva.Util._namedColorToRBA(str) || + Konva.Util._hex3ColorToRGBA(str) || + Konva.Util._hex6ColorToRGBA(str) || + Konva.Util._rgbColorToRGBA(str) || + Konva.Util._rgbaColorToRGBA(str); + }, + // Parse named css color. Like "green" + _namedColorToRBA: function(str) { + var c = COLORS[str.toLowerCase()]; + if (!c) { + return null; + } + return { + r: c[0], + g: c[1], + b: c[2], + a: 1 + }; + }, + // Parse rgb(n, n, n) + _rgbColorToRGBA: function(str) { + if (str.indexOf('rgb(') === 0) { + str = str.match(/rgb\(([^)]+)\)/)[1]; + var parts = str.split(/ *, */).map(Number); + return { + r: parts[0], + g: parts[1], + b: parts[2], + a: 1 + }; + } + }, + // Parse rgba(n, n, n, n) + _rgbaColorToRGBA: function(str) { + if (str.indexOf('rgba(') === 0) { + str = str.match(/rgba\(([^)]+)\)/)[1]; + var parts = str.split(/ *, */).map(Number); + return { + r: parts[0], + g: parts[1], + b: parts[2], + a: parts[3] + }; + } + }, + // Parse #nnnnnn + _hex6ColorToRGBA: function(str) { + if (str[0] === '#' && str.length === 7) { + return { + r: parseInt(str.slice(1, 3), 16), + g: parseInt(str.slice(3, 5), 16), + b: parseInt(str.slice(5, 7), 16), + a: 1 + }; + } + }, + // Parse #nnn + _hex3ColorToRGBA: function(str) { + if (str[0] === '#' && str.length === 4) { + return { + r: parseInt(str[1] + str[1], 16), + g: parseInt(str[2] + str[2], 16), + b: parseInt(str[3] + str[3], 16), + a: 1 + }; + } + }, + // o1 takes precedence over o2 + _merge: function(o1, o2) { + var retObj = this._clone(o2); + for (var key in o1) { + if (this._isObject(o1[key])) { + retObj[key] = this._merge(o1[key], retObj[key]); + } else { + retObj[key] = o1[key]; + } + } + return retObj; + }, + cloneObject: function(obj) { + var retObj = {}; + for (var key in obj) { + if (this._isObject(obj[key])) { + retObj[key] = this.cloneObject(obj[key]); + } else if (this._isArray(obj[key])) { + retObj[key] = this.cloneArray(obj[key]); + } else { + retObj[key] = obj[key]; + } + } + return retObj; + }, + cloneArray: function(arr) { + return arr.slice(0); + }, + _degToRad: function(deg) { + return deg * PI_OVER_DEG180; + }, + _radToDeg: function(rad) { + return rad * DEG180_OVER_PI; + }, + _capitalize: function(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }, + throw: function(str) { + throw new Error(KONVA_ERROR + str); + }, + error: function(str) { + console.error(KONVA_ERROR + str); + }, + warn: function(str) { + /* + * IE9 on Windows7 64bit will throw a JS error + * if we don't use window.console in the conditional + */ + if (Konva.global.console && console.warn && Konva.showWarnings) { + console.warn(KONVA_WARNING + str); + } + }, + extend: function(child, parent) { + function Ctor() { + this.constructor = child; + } + Ctor.prototype = parent.prototype; + var oldProto = child.prototype; + child.prototype = new Ctor(); + for (var key in oldProto) { + if (oldProto.hasOwnProperty(key)) { + child.prototype[key] = oldProto[key]; + } + } + child.__super__ = parent.prototype; + // create reference to parent + child.super = parent; + }, + /** + * adds methods to a constructor prototype + * @method + * @memberof Konva.Util.prototype + * @param {Function} constructor + * @param {Object} methods + */ + addMethods: function(constructor, methods) { + var key; + + for (key in methods) { + constructor.prototype[key] = methods[key]; + } + }, + _getControlPoints: function(x0, y0, x1, y1, x2, y2, t) { + var d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), + d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), + fa = t * d01 / (d01 + d12), + fb = t * d12 / (d01 + d12), + p1x = x1 - fa * (x2 - x0), + p1y = y1 - fa * (y2 - y0), + p2x = x1 + fb * (x2 - x0), + p2y = y1 + fb * (y2 - y0); + + return [p1x, p1y, p2x, p2y]; + }, + _expandPoints: function(p, tension) { + var len = p.length, allPoints = [], n, cp; + + for (n = 2; n < len - 2; n += 2) { + cp = Konva.Util._getControlPoints( + p[n - 2], + p[n - 1], + p[n], + p[n + 1], + p[n + 2], + p[n + 3], + tension + ); + allPoints.push(cp[0]); + allPoints.push(cp[1]); + allPoints.push(p[n]); + allPoints.push(p[n + 1]); + allPoints.push(cp[2]); + allPoints.push(cp[3]); + } + + return allPoints; + }, + _removeLastLetter: function(str) { + return str.substring(0, str.length - 1); + }, + each: function(obj, func) { + for (var key in obj) { + func(key, obj[key]); + } + }, + _getProjectionToSegment: function(x1, y1, x2, y2, x3, y3) { + var x, y, dist; + + var pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + if (pd2 == 0) { + x = x1; + y = y1; + dist = (x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2); + } else { + var u = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / pd2; + if (u < 0) { + x = x1; + y = y1; + dist = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3); + } else if (u > 1.0) { + x = x2; + y = y2; + dist = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); + } else { + x = x1 + u * (x2 - x1); + y = y1 + u * (y2 - y1); + dist = (x - x3) * (x - x3) + (y - y3) * (y - y3); + } + } + return [x, y, dist]; + }, + // line as array of points. + // line might be closed + _getProjectionToLine: function(pt, line, isClosed) { + var pc = Konva.Util.cloneObject(pt); + var dist = Number.MAX_VALUE; + line.forEach(function(p1, i) { + if (!isClosed && i === line.length - 1) { + return; + } + var p2 = line[(i + 1) % line.length]; + var proj = Konva.Util._getProjectionToSegment( + p1.x, + p1.y, + p2.x, + p2.y, + pt.x, + pt.y + ); + var px = proj[0], py = proj[1], pdist = proj[2]; + if (pdist < dist) { + pc.x = px; + pc.y = py; + dist = pdist; + } + }); + return pc; + }, + _prepareArrayForTween: function(startArray, endArray, isClosed) { + var n, start = [], end = []; + if (startArray.length > endArray.length) { + var temp = endArray; + endArray = startArray; + startArray = temp; + } + for (n = 0; n < startArray.length; n += 2) { + start.push({ + x: startArray[n], + y: startArray[n + 1] + }); + } + for (n = 0; n < endArray.length; n += 2) { + end.push({ + x: endArray[n], + y: endArray[n + 1] + }); + } + + var newStart = []; + end.forEach(function(point) { + var pr = Konva.Util._getProjectionToLine(point, start, isClosed); + newStart.push(pr.x); + newStart.push(pr.y); + }); + return newStart; + }, + _prepareToStringify: function(obj) { + var desc; + + obj.visitedByCircularReferenceRemoval = true; + + for (var key in obj) { + if ( + !(obj.hasOwnProperty(key) && obj[key] && typeof obj[key] == 'object') + ) { + continue; + } + desc = Object.getOwnPropertyDescriptor(obj, key); + if ( + obj[key].visitedByCircularReferenceRemoval || + Konva.Util._isElement(obj[key]) + ) { + if (desc.configurable) { + delete obj[key]; + } else { + return null; + } + } else if (Konva.Util._prepareToStringify(obj[key]) === null) { + if (desc.configurable) { + delete obj[key]; + } else { + return null; + } + } + } + + delete obj.visitedByCircularReferenceRemoval; + + return obj; } }; })(); - -(function() { - 'use strict'; - /** - * Brighten Filter. - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Brighten]); - * node.brightness(0.8); - */ - Konva.Filters.Brighten = function(imageData) { - var brightness = this.brightness() * 255, - data = imageData.data, - len = data.length, - i; - - for (i = 0; i < len; i += 4) { - // red - data[i] += brightness; - // green - data[i + 1] += brightness; - // blue - data[i + 2] += brightness; - } - }; - - Konva.Factory.addGetterSetter( - Konva.Node, - 'brightness', - 0, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set filter brightness. The brightness is a number between -1 and 1.  Positive values - * brighten the pixels and negative values darken them. Use with {@link Konva.Filters.Brighten} filter. - * @name brightness - * @method - * @memberof Konva.Node.prototype - * @param {Number} brightness value between -1 and 1 - * @returns {Number} - */ -})(); - + (function() { 'use strict'; + // calculate pixel ratio + var canvas = Konva.Util.createCanvasElement(), + context = canvas.getContext('2d'), + _pixelRatio = (function() { + var devicePixelRatio = Konva.window.devicePixelRatio || 1, + backingStoreRatio = context.webkitBackingStorePixelRatio || + context.mozBackingStorePixelRatio || + context.msBackingStorePixelRatio || + context.oBackingStorePixelRatio || + context.backingStorePixelRatio || + 1; + return devicePixelRatio / backingStoreRatio; + })(); + /** - * Invert Filter - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Invert]); - */ - Konva.Filters.Invert = function(imageData) { - var data = imageData.data, len = data.length, i; + * Canvas Renderer constructor + * @constructor + * @abstract + * @memberof Konva + * @param {Object} config + * @param {Number} config.width + * @param {Number} config.height + * @param {Number} config.pixelRatio KonvaJS automatically handles pixel ratio adjustments in order to render crisp drawings + * on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios + * of 1. Some high end tablets and phones, like iPhones and iPads (not the mini) have a device pixel ratio + * of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel + * ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise + * specified, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel + * ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1. + */ + Konva.Canvas = function(config) { + this.init(config); + }; + + Konva.Canvas.prototype = { + init: function(config) { + var conf = config || {}; + + var pixelRatio = conf.pixelRatio || Konva.pixelRatio || _pixelRatio; + + this.pixelRatio = pixelRatio; + this._canvas = Konva.Util.createCanvasElement(); + + // set inline styles + this._canvas.style.padding = 0; + this._canvas.style.margin = 0; + this._canvas.style.border = 0; + this._canvas.style.background = 'transparent'; + this._canvas.style.position = 'absolute'; + this._canvas.style.top = 0; + this._canvas.style.left = 0; + }, + /** + * get canvas context + * @method + * @memberof Konva.Canvas.prototype + * @returns {CanvasContext} context + */ + getContext: function() { + return this.context; + }, + /** + * get pixel ratio + * @method + * @memberof Konva.Canvas.prototype + * @returns {Number} pixel ratio + */ + getPixelRatio: function() { + return this.pixelRatio; + }, + /** + * get pixel ratio + * @method + * @memberof Konva.Canvas.prototype + * @param {Number} pixelRatio KonvaJS automatically handles pixel ratio adustments in order to render crisp drawings + * on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios + * of 1. Some high end tablets and phones, like iPhones and iPads have a device pixel ratio + * of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel + * ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise + * specificed, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel + * ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1. + */ + setPixelRatio: function(pixelRatio) { + var previousRatio = this.pixelRatio; + this.pixelRatio = pixelRatio; + this.setSize( + this.getWidth() / previousRatio, + this.getHeight() / previousRatio + ); + }, + /** + * set width + * @method + * @memberof Konva.Canvas.prototype + * @param {Number} width + */ + setWidth: function(width) { + // take into account pixel ratio + this.width = this._canvas.width = width * this.pixelRatio; + this._canvas.style.width = width + 'px'; + + var pixelRatio = this.pixelRatio, _context = this.getContext()._context; + _context.scale(pixelRatio, pixelRatio); + }, + /** + * set height + * @method + * @memberof Konva.Canvas.prototype + * @param {Number} height + */ + setHeight: function(height) { + // take into account pixel ratio + this.height = this._canvas.height = height * this.pixelRatio; + this._canvas.style.height = height + 'px'; + var pixelRatio = this.pixelRatio, _context = this.getContext()._context; + _context.scale(pixelRatio, pixelRatio); + }, + /** + * get width + * @method + * @memberof Konva.Canvas.prototype + * @returns {Number} width + */ + getWidth: function() { + return this.width; + }, + /** + * get height + * @method + * @memberof Konva.Canvas.prototype + * @returns {Number} height + */ + getHeight: function() { + return this.height; + }, + /** + * set size + * @method + * @memberof Konva.Canvas.prototype + * @param {Number} width + * @param {Number} height + */ + setSize: function(width, height) { + this.setWidth(width); + this.setHeight(height); + }, + /** + * to data url + * @method + * @memberof Konva.Canvas.prototype + * @param {String} mimeType + * @param {Number} quality between 0 and 1 for jpg mime types + * @returns {String} data url string + */ + toDataURL: function(mimeType, quality) { + try { + // If this call fails (due to browser bug, like in Firefox 3.6), + // then revert to previous no-parameter image/png behavior + return this._canvas.toDataURL(mimeType, quality); + } catch (e) { + try { + return this._canvas.toDataURL(); + } catch (err) { + Konva.Util.warn('Unable to get data URL. ' + err.message); + return ''; + } + } + } + }; + + Konva.SceneCanvas = function(config) { + var conf = config || {}; + var width = conf.width || 0, height = conf.height || 0; + + Konva.Canvas.call(this, conf); + this.context = new Konva.SceneContext(this); + this.setSize(width, height); + }; + + Konva.Util.extend(Konva.SceneCanvas, Konva.Canvas); + + Konva.HitCanvas = function(config) { + var conf = config || {}; + var width = conf.width || 0, height = conf.height || 0; + + Konva.Canvas.call(this, conf); + this.context = new Konva.HitContext(this); + this.setSize(width, height); + this.hitCanvas = true; + }; + Konva.Util.extend(Konva.HitCanvas, Konva.Canvas); +})(); + +(function() { + 'use strict'; + var COMMA = ',', + OPEN_PAREN = '(', + CLOSE_PAREN = ')', + OPEN_PAREN_BRACKET = '([', + CLOSE_BRACKET_PAREN = '])', + SEMICOLON = ';', + DOUBLE_PAREN = '()', + // EMPTY_STRING = '', + EQUALS = '=', + // SET = 'set', + CONTEXT_METHODS = [ + 'arc', + 'arcTo', + 'beginPath', + 'bezierCurveTo', + 'clearRect', + 'clip', + 'closePath', + 'createLinearGradient', + 'createPattern', + 'createRadialGradient', + 'drawImage', + 'fill', + 'fillText', + 'getImageData', + 'createImageData', + 'lineTo', + 'moveTo', + 'putImageData', + 'quadraticCurveTo', + 'rect', + 'restore', + 'rotate', + 'save', + 'scale', + 'setLineDash', + 'setTransform', + 'stroke', + 'strokeText', + 'transform', + 'translate' + ]; + + var CONTEXT_PROPERTIES = [ + 'fillStyle', + 'strokeStyle', + 'shadowColor', + 'shadowBlur', + 'shadowOffsetX', + 'shadowOffsetY', + 'lineCap', + 'lineJoin', + 'lineWidth', + 'miterLimit', + 'font', + 'textAlign', + 'textBaseline', + 'globalAlpha', + 'globalCompositeOperation' + ]; + + /** + * Canvas Context constructor + * @constructor + * @abstract + * @memberof Konva + */ + Konva.Context = function(canvas) { + this.init(canvas); + }; + + Konva.Context.prototype = { + init: function(canvas) { + this.canvas = canvas; + this._context = canvas._canvas.getContext('2d'); + + if (Konva.enableTrace) { + this.traceArr = []; + this._enableTrace(); + } + }, + /** + * fill shape + * @method + * @memberof Konva.Context.prototype + * @param {Konva.Shape} shape + */ + fillShape: function(shape) { + if (shape.getFillEnabled()) { + this._fill(shape); + } + }, + /** + * stroke shape + * @method + * @memberof Konva.Context.prototype + * @param {Konva.Shape} shape + */ + strokeShape: function(shape) { + if (shape.getStrokeEnabled()) { + this._stroke(shape); + } + }, + /** + * fill then stroke + * @method + * @memberof Konva.Context.prototype + * @param {Konva.Shape} shape + */ + fillStrokeShape: function(shape) { + var fillEnabled = shape.getFillEnabled(); + if (fillEnabled) { + this._fill(shape); + } + if (shape.getStrokeEnabled()) { + this._stroke(shape); + } + }, + /** + * get context trace if trace is enabled + * @method + * @memberof Konva.Context.prototype + * @param {Boolean} relaxed if false, return strict context trace, which includes method names, method parameters + * properties, and property values. If true, return relaxed context trace, which only returns method names and + * properites. + * @returns {String} + */ + getTrace: function(relaxed) { + var traceArr = this.traceArr, + len = traceArr.length, + str = '', + n, + trace, + method, + args; + + for (n = 0; n < len; n++) { + trace = traceArr[n]; + method = trace.method; + + // methods + if (method) { + args = trace.args; + str += method; + if (relaxed) { + str += DOUBLE_PAREN; + } else { + if (Konva.Util._isArray(args[0])) { + str += OPEN_PAREN_BRACKET + + args.join(COMMA) + + CLOSE_BRACKET_PAREN; + } else { + str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN; + } + } + } else { + // properties + str += trace.property; + if (!relaxed) { + str += EQUALS + trace.val; + } + } + + str += SEMICOLON; + } + + return str; + }, + /** + * clear trace if trace is enabled + * @method + * @memberof Konva.Context.prototype + */ + clearTrace: function() { + this.traceArr = []; + }, + _trace: function(str) { + var traceArr = this.traceArr, len; + + traceArr.push(str); + len = traceArr.length; + + if (len >= Konva.traceArrMax) { + traceArr.shift(); + } + }, + /** + * reset canvas context transform + * @method + * @memberof Konva.Context.prototype + */ + reset: function() { + var pixelRatio = this.getCanvas().getPixelRatio(); + this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); + }, + /** + * get canvas + * @method + * @memberof Konva.Context.prototype + * @returns {Konva.Canvas} + */ + getCanvas: function() { + return this.canvas; + }, + /** + * clear canvas + * @method + * @memberof Konva.Context.prototype + * @param {Object} [bounds] + * @param {Number} [bounds.x] + * @param {Number} [bounds.y] + * @param {Number} [bounds.width] + * @param {Number} [bounds.height] + */ + clear: function(bounds) { + var canvas = this.getCanvas(); + + if (bounds) { + this.clearRect( + bounds.x || 0, + bounds.y || 0, + bounds.width || 0, + bounds.height || 0 + ); + } else { + this.clearRect( + 0, + 0, + canvas.getWidth() / canvas.pixelRatio, + canvas.getHeight() / canvas.pixelRatio + ); + } + }, + _applyLineCap: function(shape) { + var lineCap = shape.getLineCap(); + if (lineCap) { + this.setAttr('lineCap', lineCap); + } + }, + _applyOpacity: function(shape) { + var absOpacity = shape.getAbsoluteOpacity(); + if (absOpacity !== 1) { + this.setAttr('globalAlpha', absOpacity); + } + }, + _applyLineJoin: function(shape) { + var lineJoin = shape.getLineJoin(); + if (lineJoin) { + this.setAttr('lineJoin', lineJoin); + } + }, + setAttr: function(attr, val) { + this._context[attr] = val; + }, + + // context pass through methods + arc: function() { + var a = arguments; + this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + beginPath: function() { + this._context.beginPath(); + }, + bezierCurveTo: function() { + var a = arguments; + this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + clearRect: function() { + var a = arguments; + this._context.clearRect(a[0], a[1], a[2], a[3]); + }, + clip: function() { + this._context.clip(); + }, + closePath: function() { + this._context.closePath(); + }, + createImageData: function() { + var a = arguments; + if (a.length === 2) { + return this._context.createImageData(a[0], a[1]); + } else if (a.length === 1) { + return this._context.createImageData(a[0]); + } + }, + createLinearGradient: function() { + var a = arguments; + return this._context.createLinearGradient(a[0], a[1], a[2], a[3]); + }, + createPattern: function() { + var a = arguments; + return this._context.createPattern(a[0], a[1]); + }, + createRadialGradient: function() { + var a = arguments; + return this._context.createRadialGradient( + a[0], + a[1], + a[2], + a[3], + a[4], + a[5] + ); + }, + drawImage: function() { + var a = arguments, _context = this._context; + + if (a.length === 3) { + _context.drawImage(a[0], a[1], a[2]); + } else if (a.length === 5) { + _context.drawImage(a[0], a[1], a[2], a[3], a[4]); + } else if (a.length === 9) { + _context.drawImage( + a[0], + a[1], + a[2], + a[3], + a[4], + a[5], + a[6], + a[7], + a[8] + ); + } + }, + isPointInPath: function(x, y) { + return this._context.isPointInPath(x, y); + }, + fill: function() { + this._context.fill(); + }, + fillRect: function(x, y, width, height) { + this._context.fillRect(x, y, width, height); + }, + strokeRect: function(x, y, width, height) { + this._context.strokeRect(x, y, width, height); + }, + fillText: function() { + var a = arguments; + this._context.fillText(a[0], a[1], a[2]); + }, + measureText: function(text) { + return this._context.measureText(text); + }, + getImageData: function() { + var a = arguments; + return this._context.getImageData(a[0], a[1], a[2], a[3]); + }, + lineTo: function() { + var a = arguments; + this._context.lineTo(a[0], a[1]); + }, + moveTo: function() { + var a = arguments; + this._context.moveTo(a[0], a[1]); + }, + rect: function() { + var a = arguments; + this._context.rect(a[0], a[1], a[2], a[3]); + }, + putImageData: function() { + var a = arguments; + this._context.putImageData(a[0], a[1], a[2]); + }, + quadraticCurveTo: function() { + var a = arguments; + this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]); + }, + restore: function() { + this._context.restore(); + }, + rotate: function() { + var a = arguments; + this._context.rotate(a[0]); + }, + save: function() { + this._context.save(); + }, + scale: function() { + var a = arguments; + this._context.scale(a[0], a[1]); + }, + setLineDash: function() { + var a = arguments, _context = this._context; + + // works for Chrome and IE11 + if (this._context.setLineDash) { + _context.setLineDash(a[0]); + } else if ('mozDash' in _context) { + // verified that this works in firefox + _context.mozDash = a[0]; + } else if ('webkitLineDash' in _context) { + // does not currently work for Safari + _context.webkitLineDash = a[0]; + } + + // no support for IE9 and IE10 + }, + getLineDash: function() { + return this._context.getLineDash(); + }, + setTransform: function() { + var a = arguments; + this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + stroke: function() { + this._context.stroke(); + }, + strokeText: function() { + var a = arguments; + this._context.strokeText(a[0], a[1], a[2]); + }, + transform: function() { + var a = arguments; + this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + translate: function() { + var a = arguments; + this._context.translate(a[0], a[1]); + }, + _enableTrace: function() { + var that = this, + len = CONTEXT_METHODS.length, + _simplifyArray = Konva.Util._simplifyArray, + origSetter = this.setAttr, + n, + args; + + // to prevent creating scope function at each loop + var func = function(methodName) { + var origMethod = that[methodName], ret; + + that[methodName] = function() { + args = _simplifyArray(Array.prototype.slice.call(arguments, 0)); + ret = origMethod.apply(that, arguments); + + that._trace({ + method: methodName, + args: args + }); + + return ret; + }; + }; + // methods + for (n = 0; n < len; n++) { + func(CONTEXT_METHODS[n]); + } + + // attrs + that.setAttr = function() { + origSetter.apply(that, arguments); + var prop = arguments[0]; + var val = arguments[1]; + if ( + prop === 'shadowOffsetX' || + prop === 'shadowOffsetY' || + prop === 'shadowBlur' + ) { + val = val / this.canvas.getPixelRatio(); + } + that._trace({ + property: prop, + val: val + }); + }; + } + }; + + CONTEXT_PROPERTIES.forEach(function(prop) { + Object.defineProperty(Konva.Context.prototype, prop, { + get: function() { + return this._context[prop]; + }, + set: function(val) { + this._context[prop] = val; + } + }); + }); + + Konva.SceneContext = function(canvas) { + Konva.Context.call(this, canvas); + }; + + Konva.SceneContext.prototype = { + _fillColor: function(shape) { + var fill = shape.fill(); + + this.setAttr('fillStyle', fill); + shape._fillFunc(this); + }, + _fillPattern: function(shape) { + var fillPatternX = shape.getFillPatternX(), + fillPatternY = shape.getFillPatternY(), + fillPatternScale = shape.getFillPatternScale(), + fillPatternRotation = Konva.getAngle(shape.getFillPatternRotation()), + fillPatternOffset = shape.getFillPatternOffset(); + + if (fillPatternX || fillPatternY) { + this.translate(fillPatternX || 0, fillPatternY || 0); + } + if (fillPatternRotation) { + this.rotate(fillPatternRotation); + } + if (fillPatternScale) { + this.scale(fillPatternScale.x, fillPatternScale.y); + } + if (fillPatternOffset) { + this.translate((-1) * fillPatternOffset.x, (-1) * fillPatternOffset.y); + } + + this.setAttr( + 'fillStyle', + this.createPattern( + shape.getFillPatternImage(), + shape.getFillPatternRepeat() || 'repeat' + ) + ); + this.fill(); + }, + _fillLinearGradient: function(shape) { + var start = shape.getFillLinearGradientStartPoint(), + end = shape.getFillLinearGradientEndPoint(), + colorStops = shape.getFillLinearGradientColorStops(), + grd = this.createLinearGradient(start.x, start.y, end.x, end.y); + + if (colorStops) { + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + this.setAttr('fillStyle', grd); + shape._fillFunc(this); + } + }, + _fillRadialGradient: function(shape) { + var start = shape.getFillRadialGradientStartPoint(), + end = shape.getFillRadialGradientEndPoint(), + startRadius = shape.getFillRadialGradientStartRadius(), + endRadius = shape.getFillRadialGradientEndRadius(), + colorStops = shape.getFillRadialGradientColorStops(), + grd = this.createRadialGradient( + start.x, + start.y, + startRadius, + end.x, + end.y, + endRadius + ); + + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + this.setAttr('fillStyle', grd); + this.fill(); + }, + _fill: function(shape) { + var hasColor = shape.fill(), + hasPattern = shape.getFillPatternImage(), + hasLinearGradient = shape.getFillLinearGradientColorStops(), + hasRadialGradient = shape.getFillRadialGradientColorStops(), + fillPriority = shape.getFillPriority(); + + // priority fills + if (hasColor && fillPriority === 'color') { + this._fillColor(shape); + } else if (hasPattern && fillPriority === 'pattern') { + this._fillPattern(shape); + } else if (hasLinearGradient && fillPriority === 'linear-gradient') { + this._fillLinearGradient(shape); + } else if (hasRadialGradient && fillPriority === 'radial-gradient') { + this._fillRadialGradient(shape); + } else if (hasColor) { + // now just try and fill with whatever is available + this._fillColor(shape); + } else if (hasPattern) { + this._fillPattern(shape); + } else if (hasLinearGradient) { + this._fillLinearGradient(shape); + } else if (hasRadialGradient) { + this._fillRadialGradient(shape); + } + }, + _stroke: function(shape) { + var dash = shape.dash(), + // ignore strokeScaleEnabled for Text + strokeScaleEnabled = shape.getStrokeScaleEnabled() || + shape instanceof Konva.Text; + + if (shape.hasStroke()) { + if (!strokeScaleEnabled) { + this.save(); + this.setTransform(1, 0, 0, 1, 0, 0); + } + + this._applyLineCap(shape); + if (dash && shape.dashEnabled()) { + this.setLineDash(dash); + } + + this.setAttr('lineWidth', shape.strokeWidth()); + this.setAttr('strokeStyle', shape.stroke()); + + if (!shape.getShadowForStrokeEnabled()) { + this.setAttr('shadowColor', 'rgba(0,0,0,0)'); + } + shape._strokeFunc(this); + + if (!strokeScaleEnabled) { + this.restore(); + } + } + }, + _applyShadow: function(shape) { + var util = Konva.Util, + color = util.get(shape.getShadowRGBA(), 'black'), + blur = util.get(shape.getShadowBlur(), 5), + offset = util.get(shape.getShadowOffset(), { + x: 0, + y: 0 + }), + // TODO: get this info from transform?? + scale = shape.getAbsoluteScale(), + ratio = this.canvas.getPixelRatio(), + scaleX = scale.x * ratio, + scaleY = scale.y * ratio; + + this.setAttr('shadowColor', color); + this.setAttr('shadowBlur', blur * ratio * Math.min(scaleX, scaleY)); + this.setAttr('shadowOffsetX', offset.x * scaleX); + this.setAttr('shadowOffsetY', offset.y * scaleY); + } + }; + Konva.Util.extend(Konva.SceneContext, Konva.Context); + + Konva.HitContext = function(canvas) { + Konva.Context.call(this, canvas); + }; + + Konva.HitContext.prototype = { + _fill: function(shape) { + this.save(); + this.setAttr('fillStyle', shape.colorKey); + shape._fillFuncHit(this); + this.restore(); + }, + _stroke: function(shape) { + if (shape.hasStroke() && shape.strokeHitEnabled()) { + // ignore strokeScaleEnabled for Text + var strokeScaleEnabled = shape.getStrokeScaleEnabled() || + shape instanceof Konva.Text; + if (!strokeScaleEnabled) { + this.save(); + this.setTransform(1, 0, 0, 1, 0, 0); + } + this._applyLineCap(shape); + this.setAttr('lineWidth', shape.strokeWidth()); + this.setAttr('strokeStyle', shape.colorKey); + shape._strokeFuncHit(this); + if (!strokeScaleEnabled) { + this.restore(); + } + } + } + }; + Konva.Util.extend(Konva.HitContext, Konva.Context); +})(); + +(function() { + 'use strict'; + // CONSTANTS + var GET = 'get', SET = 'set'; + + Konva.Factory = { + addGetterSetter: function(constructor, attr, def, validator, after) { + this.addGetter(constructor, attr, def); + this.addSetter(constructor, attr, validator, after); + this.addOverloadedGetterSetter(constructor, attr); + }, + addGetter: function(constructor, attr, def) { + var method = GET + Konva.Util._capitalize(attr); + + constructor.prototype[method] = function() { + var val = this.attrs[attr]; + return val === undefined ? def : val; + }; + }, + addSetter: function(constructor, attr, validator, after) { + var method = SET + Konva.Util._capitalize(attr); + + constructor.prototype[method] = function(val) { + if (validator) { + val = validator.call(this, val); + } + + this._setAttr(attr, val); + + if (after) { + after.call(this); + } + + return this; + }; + }, + addComponentsGetterSetter: function( + constructor, + attr, + components, + validator, + after + ) { + var len = components.length, + capitalize = Konva.Util._capitalize, + getter = GET + capitalize(attr), + setter = SET + capitalize(attr), + n, + component; + + // getter + constructor.prototype[getter] = function() { + var ret = {}; + + for (n = 0; n < len; n++) { + component = components[n]; + ret[component] = this.getAttr(attr + capitalize(component)); + } + + return ret; + }; + + // setter + constructor.prototype[setter] = function(val) { + var oldVal = this.attrs[attr], key; + + if (validator) { + val = validator.call(this, val); + } + + for (key in val) { + if (!val.hasOwnProperty(key)) { + continue; + } + this._setAttr(attr + capitalize(key), val[key]); + } + + this._fireChangeEvent(attr, oldVal, val); + + if (after) { + after.call(this); + } + + return this; + }; + + this.addOverloadedGetterSetter(constructor, attr); + }, + addOverloadedGetterSetter: function(constructor, attr) { + var capitalizedAttr = Konva.Util._capitalize(attr), + setter = SET + capitalizedAttr, + getter = GET + capitalizedAttr; + + constructor.prototype[attr] = function() { + // setting + if (arguments.length) { + this[setter](arguments[0]); + return this; + } + // getting + return this[getter](); + }; + }, + addDeprecatedGetterSetter: function(constructor, attr, def, validator) { + var method = GET + Konva.Util._capitalize(attr); + var message = attr + + ' property is deprecated and will be removed soon. Look at Konva change log for more information.'; + constructor.prototype[method] = function() { + Konva.Util.error(message); + var val = this.attrs[attr]; + return val === undefined ? def : val; + }; + this.addSetter(constructor, attr, validator, function() { + Konva.Util.error(message); + }); + this.addOverloadedGetterSetter(constructor, attr); + }, + backCompat: function(constructor, methods) { + Konva.Util.each(methods, function(oldMethodName, newMethodName) { + var method = constructor.prototype[newMethodName]; + constructor.prototype[oldMethodName] = function() { + method.apply(this, arguments); + Konva.Util.error( + oldMethodName + + ' method is deprecated and will be removed soon. Use ' + + newMethodName + + ' instead' + ); + }; + }); + }, + afterSetFilter: function() { + this._filterUpToDate = false; + } + }; + + Konva.Validators = { + /** + * @return {number} + */ + RGBComponent: function(val) { + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } + return Math.round(val); + }, + alphaComponent: function(val) { + if (val > 1) { + return 1; + } else if (val < 0.0001) { + // chrome does not honor alpha values of 0 + return 0.0001; + } + + return val; + } + }; +})(); + +(function(Konva) { + 'use strict'; + // CONSTANTS + var ABSOLUTE_OPACITY = 'absoluteOpacity', + ABSOLUTE_TRANSFORM = 'absoluteTransform', + ABSOLUTE_SCALE = 'absoluteScale', + CHANGE = 'Change', + CHILDREN = 'children', + DOT = '.', + EMPTY_STRING = '', + GET = 'get', + ID = 'id', + KONVA = 'konva', + LISTENING = 'listening', + MOUSEENTER = 'mouseenter', + MOUSELEAVE = 'mouseleave', + NAME = 'name', + SET = 'set', + SHAPE = 'Shape', + SPACE = ' ', + STAGE = 'stage', + TRANSFORM = 'transform', + UPPER_STAGE = 'Stage', + VISIBLE = 'visible', + CLONE_BLACK_LIST = ['id'], + TRANSFORM_CHANGE_STR = [ + 'xChange.konva', + 'yChange.konva', + 'scaleXChange.konva', + 'scaleYChange.konva', + 'skewXChange.konva', + 'skewYChange.konva', + 'rotationChange.konva', + 'offsetXChange.konva', + 'offsetYChange.konva', + 'transformsEnabledChange.konva' + ].join(SPACE), + SCALE_CHANGE_STR = ['scaleXChange.konva', 'scaleYChange.konva'].join(SPACE); + + /** + * Node constructor. Nodes are entities that can be transformed, layered, + * and have bound events. The stage, layers, groups, and shapes all extend Node. + * @constructor + * @memberof Konva + * @abstract + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + */ + Konva.Node = function(config) { + this._init(config); + }; + + Konva.Util.addMethods(Konva.Node, { + _init: function(config) { + var that = this; + this._id = Konva.idCounter++; + this.eventListeners = {}; + this.attrs = {}; + this._cache = {}; + this._filterUpToDate = false; + this._isUnderCache = false; + this.setAttrs(config); + + // event bindings for cache handling + this.on(TRANSFORM_CHANGE_STR, function() { + this._clearCache(TRANSFORM); + that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }); + + this.on(SCALE_CHANGE_STR, function() { + that._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + }); + + this.on('visibleChange.konva', function() { + that._clearSelfAndDescendantCache(VISIBLE); + }); + this.on('listeningChange.konva', function() { + that._clearSelfAndDescendantCache(LISTENING); + }); + this.on('opacityChange.konva', function() { + that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + }); + }, + _clearCache: function(attr) { + if (attr) { + delete this._cache[attr]; + } else { + this._cache = {}; + } + }, + _getCache: function(attr, privateGetter) { + var cache = this._cache[attr]; + + // if not cached, we need to set it using the private getter method. + if (cache === undefined) { + this._cache[attr] = privateGetter.call(this); + } + + return this._cache[attr]; + }, + /* + * when the logic for a cached result depends on ancestor propagation, use this + * method to clear self and children cache + */ + _clearSelfAndDescendantCache: function(attr) { + this._clearCache(attr); + + if (this.children) { + this.getChildren().each(function(node) { + node._clearSelfAndDescendantCache(attr); + }); + } + }, + /** + * clear cached canvas + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Node} + * @example + * node.clearCache(); + */ + clearCache: function() { + delete this._cache.canvas; + this._filterUpToDate = false; + return this; + }, + /** + * cache node to improve drawing performance, apply filters, or create more accurate + * hit regions. For all basic shapes size of cache canvas will be automatically detected. + * If you need to cache your custom `Konva.Shape` instance you have to pass shape's bounding box + * properties. Look at [link to demo page](link to demo page) for more information. + * @method + * @memberof Konva.Node.prototype + * @param {Object} [config] + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Number} [config.offset] increase canvas size by `offset` pixel in all directions. + * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached + * region for debugging purposes + * @param {Number} [config.pixelRatio] change quality (or pixel ratio) of cached image. pixelRatio = 2 will produce 2x sized cache. + * @returns {Konva.Node} + * @example + * // cache a shape with the x,y position of the bounding box at the center and + * // the width and height of the bounding box equal to the width and height of + * // the shape obtained from shape.width() and shape.height() + * image.cache(); + * + * // cache a node and define the bounding box position and size + * node.cache({ + * x: -30, + * y: -30, + * width: 100, + * height: 200 + * }); + * + * // cache a node and draw a red border around the bounding box + * // for debugging purposes + * node.cache({ + * x: -30, + * y: -30, + * width: 100, + * height: 200, + * offset : 10, + * drawBorder: true + * }); + */ + cache: function(config) { + var conf = config || {}, + rect = this.getClientRect(true), + width = conf.width || rect.width, + height = conf.height || rect.height, + pixelRatio = conf.pixelRatio, + x = conf.x || rect.x, + y = conf.y || rect.y, + offset = conf.offset || 0, + drawBorder = conf.drawBorder || false; + + if (!width || !height) { + throw new Error('Width or height of caching configuration equals 0.'); + } + + width += offset * 2; + height += offset * 2; + + x -= offset; + y -= offset; + + var cachedSceneCanvas = new Konva.SceneCanvas({ + pixelRatio: pixelRatio, + width: width, + height: height + }), + cachedFilterCanvas = new Konva.SceneCanvas({ + pixelRatio: pixelRatio, + width: width, + height: height + }), + cachedHitCanvas = new Konva.HitCanvas({ + pixelRatio: 1, + width: width, + height: height + }), + sceneContext = cachedSceneCanvas.getContext(), + hitContext = cachedHitCanvas.getContext(); + + cachedHitCanvas.isCache = true; + + this.clearCache(); + + sceneContext.save(); + hitContext.save(); + + sceneContext.translate(-x, -y); + hitContext.translate(-x, -y); + + // extra flag to skip on getAbsolute opacity calc + this._isUnderCache = true; + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + + this.drawScene(cachedSceneCanvas, this, true); + this.drawHit(cachedHitCanvas, this, true); + this._isUnderCache = false; + + sceneContext.restore(); + hitContext.restore(); + + // this will draw a red border around the cached box for + // debugging purposes + if (drawBorder) { + sceneContext.save(); + sceneContext.beginPath(); + sceneContext.rect(0, 0, width, height); + sceneContext.closePath(); + sceneContext.setAttr('strokeStyle', 'red'); + sceneContext.setAttr('lineWidth', 5); + sceneContext.stroke(); + sceneContext.restore(); + } + + this._cache.canvas = { + scene: cachedSceneCanvas, + filter: cachedFilterCanvas, + hit: cachedHitCanvas, + x: x, + y: y + }; + + return this; + }, + /** + * Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). + * The rectangle position is relative to parent container. + * @method + * @memberof Konva.Node.prototype + * @param {Boolean} [skipTransform] flag should we skip transformation to rectangle + * @returns {Object} rect with {x, y, width, height} properties + * @example + * var rect = new Konva.Rect({ + * width : 100, + * height : 100, + * x : 50, + * y : 50, + * strokeWidth : 4, + * stroke : 'black', + * offsetX : 50, + * scaleY : 2 + * }); + * + * // get client rect without think off transformations (position, rotation, scale, offset, etc) + * rect.getClientRect(true); + * // returns { + * // x : -2, // two pixels for stroke / 2 + * // y : -2, + * // width : 104, // increased by 4 for stroke + * // height : 104 + * //} + * + * // get client rect with transformation applied + * rect.getClientRect(); + * // returns Object {x: -2, y: 46, width: 104, height: 208} + */ + getClientRect: function() { + // abstract method + // redefine in Container and Shape + throw new Error('abstract "getClientRect" method call'); + }, + _transformedRect: function(rect) { + var points = [ + { x: rect.x, y: rect.y }, + { x: rect.x + rect.width, y: rect.y }, + { x: rect.x + rect.width, y: rect.y + rect.height }, + { x: rect.x, y: rect.y + rect.height } + ]; + var minX, minY, maxX, maxY; + var trans = this.getTransform(); + points.forEach(function(point) { + var transformed = trans.point(point); + if (minX === undefined) { + minX = maxX = transformed.x; + minY = maxY = transformed.y; + } + minX = Math.min(minX, transformed.x); + minY = Math.min(minY, transformed.y); + maxX = Math.max(maxX, transformed.x); + maxY = Math.max(maxY, transformed.y); + }); + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY + }; + }, + _drawCachedSceneCanvas: function(context) { + context.save(); + context._applyOpacity(this); + context.translate(this._cache.canvas.x, this._cache.canvas.y); + + var cacheCanvas = this._getCachedSceneCanvas(); + var ratio = cacheCanvas.pixelRatio; + + context.drawImage( + cacheCanvas._canvas, + 0, + 0, + cacheCanvas.width / ratio, + cacheCanvas.height / ratio + ); + context.restore(); + }, + _drawCachedHitCanvas: function(context) { + var cachedCanvas = this._cache.canvas, hitCanvas = cachedCanvas.hit; + context.save(); + context.translate(this._cache.canvas.x, this._cache.canvas.y); + context.drawImage(hitCanvas._canvas, 0, 0); + context.restore(); + }, + _getCachedSceneCanvas: function() { + var filters = this.filters(), + cachedCanvas = this._cache.canvas, + sceneCanvas = cachedCanvas.scene, + filterCanvas = cachedCanvas.filter, + filterContext = filterCanvas.getContext(), + len, + imageData, + n, + filter; + + if (filters) { + if (!this._filterUpToDate) { + var ratio = sceneCanvas.pixelRatio; + + try { + len = filters.length; + filterContext.clear(); + + // copy cached canvas onto filter context + filterContext.drawImage( + sceneCanvas._canvas, + 0, + 0, + sceneCanvas.getWidth() / ratio, + sceneCanvas.getHeight() / ratio + ); + imageData = filterContext.getImageData( + 0, + 0, + filterCanvas.getWidth(), + filterCanvas.getHeight() + ); + + // apply filters to filter context + for (n = 0; n < len; n++) { + filter = filters[n]; + if (typeof filter !== 'function') { + Konva.Util.error( + 'Filter should be type of function, but got ' + + typeof filter + + ' insted. Please check correct filters' + ); + continue; + } + filter.call(this, imageData); + filterContext.putImageData(imageData, 0, 0); + } + } catch (e) { + Konva.Util.error('Unable to apply filter. ' + e.message); + } + + this._filterUpToDate = true; + } + + return filterCanvas; + } + return sceneCanvas; + }, + /** + * bind events to the node. KonvaJS supports mouseover, mousemove, + * mouseout, mouseenter, mouseleave, mousedown, mouseup, wheel, click, dblclick, touchstart, touchmove, + * touchend, tap, dbltap, dragstart, dragmove, and dragend events. The Konva Stage supports + * contentMouseover, contentMousemove, contentMouseout, contentMousedown, contentMouseup, contentWheel, contentContextmenu + * contentClick, contentDblclick, contentTouchstart, contentTouchmove, contentTouchend, contentTap, + * and contentDblTap. Pass in a string of events delimmited by a space to bind multiple events at once + * such as 'mousedown mouseup mousemove'. Include a namespace to bind an + * event by name such as 'click.foobar'. + * @method + * @memberof Konva.Node.prototype + * @param {String} evtStr e.g. 'click', 'mousedown touchstart', 'mousedown.foo touchstart.foo' + * @param {Function} handler The handler function is passed an event object + * @returns {Konva.Node} + * @example + * // add click listener + * node.on('click', function() { + * console.log('you clicked me!'); + * }); + * + * // get the target node + * node.on('click', function(evt) { + * console.log(evt.target); + * }); + * + * // stop event propagation + * node.on('click', function(evt) { + * evt.cancelBubble = true; + * }); + * + * // bind multiple listeners + * node.on('click touchstart', function() { + * console.log('you clicked/touched me!'); + * }); + * + * // namespace listener + * node.on('click.foo', function() { + * console.log('you clicked/touched me!'); + * }); + * + * // get the event type + * node.on('click tap', function(evt) { + * var eventType = evt.type; + * }); + * + * // get native event object + * node.on('click tap', function(evt) { + * var nativeEvent = evt.evt; + * }); + * + * // for change events, get the old and new val + * node.on('xChange', function(evt) { + * var oldVal = evt.oldVal; + * var newVal = evt.newVal; + * }); + * + * // get event targets + * // with event delegations + * layer.on('click', 'Group', function(evt) { + * var shape = evt.target; + * var group = evtn.currentTarger; + * }); + */ + on: function(evtStr, handler) { + if (arguments.length === 3) { + return this._delegate.apply(this, arguments); + } + var events = evtStr.split(SPACE), + len = events.length, + n, + event, + parts, + baseEvent, + name; + + /* + * loop through types and attach event listeners to + * each one. eg. 'click mouseover.namespace mouseout' + * will create three event bindings + */ + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1] || EMPTY_STRING; + + // create events array if it doesn't exist + if (!this.eventListeners[baseEvent]) { + this.eventListeners[baseEvent] = []; + } + + this.eventListeners[baseEvent].push({ + name: name, + handler: handler + }); + } + + return this; + }, + /** + * remove event bindings from the node. Pass in a string of + * event types delimmited by a space to remove multiple event + * bindings at once such as 'mousedown mouseup mousemove'. + * include a namespace to remove an event binding by name + * such as 'click.foobar'. If you only give a name like '.foobar', + * all events in that namespace will be removed. + * @method + * @memberof Konva.Node.prototype + * @param {String} evtStr e.g. 'click', 'mousedown touchstart', '.foobar' + * @returns {Konva.Node} + * @example + * // remove listener + * node.off('click'); + * + * // remove multiple listeners + * node.off('click touchstart'); + * + * // remove listener by name + * node.off('click.foo'); + */ + off: function(evtStr) { + var events = (evtStr || '').split(SPACE), + len = events.length, + n, + t, + event, + parts, + baseEvent, + name; + + if (!evtStr) { + // remove all events + for (t in this.eventListeners) { + this._off(t); + } + } + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1]; + + if (baseEvent) { + if (this.eventListeners[baseEvent]) { + this._off(baseEvent, name); + } + } else { + for (t in this.eventListeners) { + this._off(t, name); + } + } + } + return this; + }, + // some event aliases for third party integration like HammerJS + dispatchEvent: function(evt) { + var e = { + target: this, + type: evt.type, + evt: evt + }; + this.fire(evt.type, e); + return this; + }, + addEventListener: function(type, handler) { + // we have to pass native event to handler + this.on(type, function(evt) { + handler.call(this, evt.evt); + }); + return this; + }, + removeEventListener: function(type) { + this.off(type); + return this; + }, + // like node.on + _delegate: function(event, selector, handler) { + var stopNode = this; + this.on(event, function(evt) { + var targets = evt.target.findAncestors(selector, true, stopNode); + for (var i = 0; i < targets.length; i++) { + evt = Konva.Util.cloneObject(evt); + evt.currentTarget = targets[i]; + handler.call(targets[i], evt); + } + }); + }, + /** + * remove self from parent, but don't destroy + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Node} + * @example + * node.remove(); + */ + remove: function() { + var parent = this.getParent(); + + if (parent && parent.children) { + parent.children.splice(this.index, 1); + parent._setChildrenIndices(); + delete this.parent; + } + + // every cached attr that is calculated via node tree + // traversal must be cleared when removing a node + this._clearSelfAndDescendantCache(STAGE); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + this._clearSelfAndDescendantCache(VISIBLE); + this._clearSelfAndDescendantCache(LISTENING); + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + + return this; + }, + /** + * remove and destroy self + * @method + * @memberof Konva.Node.prototype + * @example + * node.destroy(); + */ + destroy: function() { + // remove from ids and names hashes + Konva._removeId(this.getId()); + + // remove all names + var names = (this.getName() || '').split(/\s/g); + for (var i = 0; i < names.length; i++) { + var subname = names[i]; + Konva._removeName(subname, this._id); + } + + this.remove(); + return this; + }, + /** + * get attr + * @method + * @memberof Konva.Node.prototype + * @param {String} attr + * @returns {Integer|String|Object|Array} + * @example + * var x = node.getAttr('x'); + */ + getAttr: function(attr) { + var method = GET + Konva.Util._capitalize(attr); + if (Konva.Util._isFunction(this[method])) { + return this[method](); + } + // otherwise get directly + return this.attrs[attr]; + }, + /** + * get ancestors + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Collection} + * @example + * shape.getAncestors().each(function(node) { + * console.log(node.getId()); + * }) + */ + getAncestors: function() { + var parent = this.getParent(), ancestors = new Konva.Collection(); + + while (parent) { + ancestors.push(parent); + parent = parent.getParent(); + } + + return ancestors; + }, + /** + * get attrs object literal + * @method + * @memberof Konva.Node.prototype + * @returns {Object} + */ + getAttrs: function() { + return this.attrs || {}; + }, + /** + * set multiple attrs at once using an object literal + * @method + * @memberof Konva.Node.prototype + * @param {Object} config object containing key value pairs + * @returns {Konva.Node} + * @example + * node.setAttrs({ + * x: 5, + * fill: 'red' + * }); + */ + setAttrs: function(config) { + var key, method; + + if (!config) { + return this; + } + for (key in config) { + if (key === CHILDREN) { + continue; + } + method = SET + Konva.Util._capitalize(key); + // use setter if available + if (Konva.Util._isFunction(this[method])) { + this[method](config[key]); + } else { + // otherwise set directly + this._setAttr(key, config[key]); + } + } + return this; + }, + /** + * determine if node is listening for events by taking into account ancestors. + * + * Parent | Self | isListening + * listening | listening | + * ----------+-----------+------------ + * T | T | T + * T | F | F + * F | T | T + * F | F | F + * ----------+-----------+------------ + * T | I | T + * F | I | F + * I | I | T + * + * @method + * @memberof Konva.Node.prototype + * @returns {Boolean} + */ + isListening: function() { + return this._getCache(LISTENING, this._isListening); + }, + _isListening: function() { + var listening = this.getListening(), parent = this.getParent(); + + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (listening === 'inherit') { + if (parent) { + return parent.isListening(); + } else { + return true; + } + } else { + return listening; + } + }, + /** + * determine if node is visible by taking into account ancestors. + * + * Parent | Self | isVisible + * visible | visible | + * ----------+-----------+------------ + * T | T | T + * T | F | F + * F | T | T + * F | F | F + * ----------+-----------+------------ + * T | I | T + * F | I | F + * I | I | T + + * @method + * @memberof Konva.Node.prototype + * @returns {Boolean} + */ + isVisible: function() { + return this._getCache(VISIBLE, this._isVisible); + }, + _isVisible: function() { + var visible = this.getVisible(), parent = this.getParent(); + + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (visible === 'inherit') { + if (parent) { + return parent.isVisible(); + } else { + return true; + } + } else { + return visible; + } + }, + /** + * determine if listening is enabled by taking into account descendants. If self or any children + * have _isListeningEnabled set to true, then self also has listening enabled. + * @method + * @memberof Konva.Node.prototype + * @returns {Boolean} + */ + shouldDrawHit: function(canvas) { + var layer = this.getLayer(); + return (canvas && canvas.isCache) || + (layer && + layer.hitGraphEnabled() && + this.isListening() && + this.isVisible()); + }, + /** + * show node + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Node} + */ + show: function() { + this.setVisible(true); + return this; + }, + /** + * hide node. Hidden nodes are no longer detectable + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Node} + */ + hide: function() { + this.setVisible(false); + return this; + }, + /** + * get zIndex relative to the node's siblings who share the same parent + * @method + * @memberof Konva.Node.prototype + * @returns {Integer} + */ + getZIndex: function() { + return this.index || 0; + }, + /** + * get absolute z-index which takes into account sibling + * and ancestor indices + * @method + * @memberof Konva.Node.prototype + * @returns {Integer} + */ + getAbsoluteZIndex: function() { + var depth = this.getDepth(), that = this, index = 0, nodes, len, n, child; + + function addChildren(children) { + nodes = []; + len = children.length; + for (n = 0; n < len; n++) { + child = children[n]; + index++; + + if (child.nodeType !== SHAPE) { + nodes = nodes.concat(child.getChildren().toArray()); + } + + if (child._id === that._id) { + n = len; + } + } + + if (nodes.length > 0 && nodes[0].getDepth() <= depth) { + addChildren(nodes); + } + } + if (that.nodeType !== UPPER_STAGE) { + addChildren(that.getStage().getChildren()); + } + + return index; + }, + /** + * get node depth in node tree. Returns an integer. + * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always + * be >= 2 + * @method + * @memberof Konva.Node.prototype + * @returns {Integer} + */ + getDepth: function() { + var depth = 0, parent = this.parent; + + while (parent) { + depth++; + parent = parent.parent; + } + return depth; + }, + setPosition: function(pos) { + this.setX(pos.x); + this.setY(pos.y); + return this; + }, + getPosition: function() { + return { + x: this.getX(), + y: this.getY() + }; + }, + /** + * get absolute position relative to the top left corner of the stage container div + * or relative to passed node + * @method + * @param {Object} [top] optional parent node + * @memberof Konva.Node.prototype + * @returns {Object} + */ + getAbsolutePosition: function(top) { + var absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), + absoluteTransform = new Konva.Transform(), + offset = this.offset(); + + // clone the matrix array + absoluteTransform.m = absoluteMatrix.slice(); + absoluteTransform.translate(offset.x, offset.y); + + return absoluteTransform.getTranslation(); + }, + /** + * set absolute position + * @method + * @memberof Konva.Node.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Konva.Node} + */ + setAbsolutePosition: function(pos) { + var origTrans = this._clearTransform(), it; + + // don't clear translation + this.attrs.x = origTrans.x; + this.attrs.y = origTrans.y; + delete origTrans.x; + delete origTrans.y; + + // unravel transform + it = this.getAbsoluteTransform(); + + it.invert(); + it.translate(pos.x, pos.y); + pos = { + x: this.attrs.x + it.getTranslation().x, + y: this.attrs.y + it.getTranslation().y + }; + + this.setPosition({ x: pos.x, y: pos.y }); + this._setTransform(origTrans); + + return this; + }, + _setTransform: function(trans) { + var key; + + for (key in trans) { + this.attrs[key] = trans[key]; + } + + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }, + _clearTransform: function() { + var trans = { + x: this.getX(), + y: this.getY(), + rotation: this.getRotation(), + scaleX: this.getScaleX(), + scaleY: this.getScaleY(), + offsetX: this.getOffsetX(), + offsetY: this.getOffsetY(), + skewX: this.getSkewX(), + skewY: this.getSkewY() + }; + + this.attrs.x = 0; + this.attrs.y = 0; + this.attrs.rotation = 0; + this.attrs.scaleX = 1; + this.attrs.scaleY = 1; + this.attrs.offsetX = 0; + this.attrs.offsetY = 0; + this.attrs.skewX = 0; + this.attrs.skewY = 0; + + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + + // return original transform + return trans; + }, + /** + * move node by an amount relative to its current position + * @method + * @memberof Konva.Node.prototype + * @param {Object} change + * @param {Number} change.x + * @param {Number} change.y + * @returns {Konva.Node} + * @example + * // move node in x direction by 1px and y direction by 2px + * node.move({ + * x: 1, + * y: 2) + * }); + */ + move: function(change) { + var changeX = change.x, + changeY = change.y, + x = this.getX(), + y = this.getY(); + + if (changeX !== undefined) { + x += changeX; + } + + if (changeY !== undefined) { + y += changeY; + } + + this.setPosition({ x: x, y: y }); + return this; + }, + _eachAncestorReverse: function(func, top) { + var family = [], parent = this.getParent(), len, n; + + // if top node is defined, and this node is top node, + // there's no need to build a family tree. just execute + // func with this because it will be the only node + if (top && top._id === this._id) { + func(this); + return true; + } + + family.unshift(this); + + while (parent && (!top || parent._id !== top._id)) { + family.unshift(parent); + parent = parent.parent; + } + + len = family.length; + for (n = 0; n < len; n++) { + func(family[n]); + } + }, + /** + * rotate node by an amount in degrees relative to its current rotation + * @method + * @memberof Konva.Node.prototype + * @param {Number} theta + * @returns {Konva.Node} + */ + rotate: function(theta) { + this.setRotation(this.getRotation() + theta); + return this; + }, + /** + * move node to the top of its siblings + * @method + * @memberof Konva.Node.prototype + * @returns {Boolean} + */ + moveToTop: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveToTop function is ignored.'); + return false; + } + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.push(this); + this.parent._setChildrenIndices(); + return true; + }, + /** + * move node up + * @method + * @memberof Konva.Node.prototype + * @returns {Boolean} flag is moved or not + */ + moveUp: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveUp function is ignored.'); + return false; + } + var index = this.index, len = this.parent.getChildren().length; + if (index < len - 1) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index + 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** + * move node down + * @method + * @memberof Konva.Node.prototype + * @returns {Boolean} + */ + moveDown: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveDown function is ignored.'); + return false; + } + var index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index - 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** + * move node to the bottom of its siblings + * @method + * @memberof Konva.Node.prototype + * @returns {Boolean} + */ + moveToBottom: function() { + if (!this.parent) { + Konva.Util.warn( + 'Node has no parent. moveToBottom function is ignored.' + ); + return false; + } + var index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.unshift(this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** + * set zIndex relative to siblings + * @method + * @memberof Konva.Node.prototype + * @param {Integer} zIndex + * @returns {Konva.Node} + */ + setZIndex: function(zIndex) { + if (!this.parent) { + Konva.Util.warn('Node has no parent. zIndex parameter is ignored.'); + return false; + } + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.splice(zIndex, 0, this); + this.parent._setChildrenIndices(); + return this; + }, + /** + * get absolute opacity + * @method + * @memberof Konva.Node.prototype + * @returns {Number} + */ + getAbsoluteOpacity: function() { + return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); + }, + _getAbsoluteOpacity: function() { + var absOpacity = this.getOpacity(); + var parent = this.getParent(); + if (parent && !parent._isUnderCache) { + absOpacity *= this.getParent().getAbsoluteOpacity(); + } + return absOpacity; + }, + /** + * move node to another container + * @method + * @memberof Konva.Node.prototype + * @param {Container} newContainer + * @returns {Konva.Node} + * @example + * // move node from current layer into layer2 + * node.moveTo(layer2); + */ + moveTo: function(newContainer) { + // do nothing if new container is already parent + if (this.getParent() !== newContainer) { + // this.remove my be overrided by drag and drop + // buy we need original + (this.__originalRemove || this.remove).call(this); + newContainer.add(this); + } + return this; + }, + /** + * convert Node into an object for serialization. Returns an object. + * @method + * @memberof Konva.Node.prototype + * @returns {Object} + */ + toObject: function() { + var obj = {}, attrs = this.getAttrs(), key, val, getter, defaultValue; + + obj.attrs = {}; + + for (key in attrs) { + val = attrs[key]; + getter = this[key]; + // remove attr value so that we can extract the default value from the getter + delete attrs[key]; + defaultValue = getter ? getter.call(this) : null; + // restore attr value + attrs[key] = val; + if (defaultValue !== val) { + obj.attrs[key] = val; + } + } + + obj.className = this.getClassName(); + return Konva.Util._prepareToStringify(obj); + }, + /** + * convert Node into a JSON string. Returns a JSON string. + * @method + * @memberof Konva.Node.prototype + * @returns {String}} + */ + toJSON: function() { + return JSON.stringify(this.toObject()); + }, + /** + * get parent container + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Node} + */ + getParent: function() { + return this.parent; + }, + /** + * get all ancestros (parent then parent of the parent, etc) of the node + * @method + * @memberof Konva.Node.prototype + * @param {String} [selector] selector for search + * @param {Boolean} [includeSelf] show we think that node is ancestro itself? + * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) + * @returns {Array} [ancestors] + * @example + * // get one of the parent group + * var parentGroups = node.findAncestors('Group'); + */ + findAncestors: function(selector, includeSelf, stopNode) { + var res = []; + + if (includeSelf && this._isMatch(selector)) { + res.push(this); + } + var ancestor = this.parent; + while (ancestor) { + if (ancestor === stopNode) { + return res; + } + if (ancestor._isMatch(selector)) { + res.push(ancestor); + } + ancestor = ancestor.parent; + } + return res; + }, + /** + * get ancestor (parent or parent of the parent, etc) of the node that match passed selector + * @method + * @memberof Konva.Node.prototype + * @param {String} [selector] selector for search + * @param {Boolean} [includeSelf] show we think that node is ancestro itself? + * @param {Konva.Node} [stopNode] optional node where we need to stop searching (one of ancestors) + * @returns {Konva.Node} ancestor + * @example + * // get one of the parent group + * var group = node.findAncestors('.mygroup'); + */ + findAncestor: function(selector, includeSelf, stopNode) { + return this.findAncestors(selector, includeSelf, stopNode)[0]; + }, + // is current node match passed selector? + _isMatch: function(selector) { + if (!selector) { + return false; + } + var selectorArr = selector.replace(/ /g, '').split(','), + len = selectorArr.length, + n, + sel; + + for (n = 0; n < len; n++) { + sel = selectorArr[n]; + if (!Konva.Util.isValidSelector(sel)) { + Konva.Util.warn( + 'Selector "' + + sel + + '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' + ); + Konva.Util.warn( + 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' + ); + Konva.Util.warn('Konva is awesome, right?'); + } + // id selector + if (sel.charAt(0) === '#') { + if (this.id() === sel.slice(1)) { + return true; + } + } else if (sel.charAt(0) === '.') { + // name selector + if (this.hasName(sel.slice(1))) { + return true; + } + } else if (this._get(sel).length !== 0) { + return true; + } + } + return false; + }, + /** + * get layer ancestor + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Layer} + */ + getLayer: function() { + var parent = this.getParent(); + return parent ? parent.getLayer() : null; + }, + /** + * get stage ancestor + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Stage} + */ + getStage: function() { + return this._getCache(STAGE, this._getStage); + }, + _getStage: function() { + var parent = this.getParent(); + if (parent) { + return parent.getStage(); + } else { + return undefined; + } + }, + /** + * fire event + * @method + * @memberof Konva.Node.prototype + * @param {String} eventType event type. can be a regular event, like click, mouseover, or mouseout, or it can be a custom event, like myCustomEvent + * @param {Event} [evt] event object + * @param {Boolean} [bubble] setting the value to false, or leaving it undefined, will result in the event + * not bubbling. Setting the value to true will result in the event bubbling. + * @returns {Konva.Node} + * @example + * // manually fire click event + * node.fire('click'); + * + * // fire custom event + * node.fire('foo'); + * + * // fire custom event with custom event object + * node.fire('foo', { + * bar: 10 + * }); + * + * // fire click event that bubbles + * node.fire('click', null, true); + */ + fire: function(eventType, evt, bubble) { + evt = evt || {}; + evt.target = evt.target || this; + // bubble + if (bubble) { + this._fireAndBubble(eventType, evt); + } else { + // no bubble + this._fire(eventType, evt); + } + return this; + }, + /** + * get absolute transform of the node which takes into + * account its ancestor transforms + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Transform} + */ + getAbsoluteTransform: function(top) { + // if using an argument, we can't cache the result. + if (top) { + return this._getAbsoluteTransform(top); + } else { + // if no argument, we can cache the result + return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); + } + }, + _getAbsoluteTransform: function(top) { + var at = new Konva.Transform(), transformsEnabled, trans; + + // start with stage and traverse downwards to self + this._eachAncestorReverse( + function(node) { + transformsEnabled = node.transformsEnabled(); + trans = node.getTransform(); + + if (transformsEnabled === 'all') { + at.multiply(trans); + } else if (transformsEnabled === 'position') { + at.translate(node.x(), node.y()); + } + }, + top + ); + return at; + }, + /** + * get absolute scale of the node which takes into + * account its ancestor scales + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Transform} + */ + getAbsoluteScale: function(top) { + // if using an argument, we can't cache the result. + if (top) { + return this._getAbsoluteTransform(top); + } else { + // if no argument, we can cache the result + return this._getCache(ABSOLUTE_SCALE, this._getAbsoluteScale); + } + }, + _getAbsoluteScale: function(top) { + // this is special logic for caching with some shapes with shadow + var parent = this; + while (parent) { + if (parent._isUnderCache) { + top = parent; + } + parent = parent.getParent(); + } + + var scaleX = 1, scaleY = 1; + + // start with stage and traverse downwards to self + this._eachAncestorReverse( + function(node) { + scaleX *= node.scaleX(); + scaleY *= node.scaleY(); + }, + top + ); + return { + x: scaleX, + y: scaleY + }; + }, + /** + * get transform of the node + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Transform} + */ + getTransform: function() { + return this._getCache(TRANSFORM, this._getTransform); + }, + _getTransform: function() { + var m = new Konva.Transform(), + x = this.getX(), + y = this.getY(), + rotation = Konva.getAngle(this.getRotation()), + scaleX = this.getScaleX(), + scaleY = this.getScaleY(), + skewX = this.getSkewX(), + skewY = this.getSkewY(), + offsetX = this.getOffsetX(), + offsetY = this.getOffsetY(); + + if (x !== 0 || y !== 0) { + m.translate(x, y); + } + if (rotation !== 0) { + m.rotate(rotation); + } + if (skewX !== 0 || skewY !== 0) { + m.skew(skewX, skewY); + } + if (scaleX !== 1 || scaleY !== 1) { + m.scale(scaleX, scaleY); + } + if (offsetX !== 0 || offsetY !== 0) { + m.translate((-1) * offsetX, (-1) * offsetY); + } + + return m; + }, + /** + * clone node. Returns a new Node instance with identical attributes. You can also override + * the node properties with an object literal, enabling you to use an existing node as a template + * for another node + * @method + * @memberof Konva.Node.prototype + * @param {Object} obj override attrs + * @returns {Konva.Node} + * @example + * // simple clone + * var clone = node.clone(); + * + * // clone a node and override the x position + * var clone = rect.clone({ + * x: 5 + * }); + */ + clone: function(obj) { + // instantiate new node + var attrs = Konva.Util.cloneObject(this.attrs), + key, + allListeners, + len, + n, + listener; + // filter black attrs + for (var i in CLONE_BLACK_LIST) { + var blockAttr = CLONE_BLACK_LIST[i]; + delete attrs[blockAttr]; + } + // apply attr overrides + for (key in obj) { + attrs[key] = obj[key]; + } + + var node = new this.constructor(attrs); + // copy over listeners + for (key in this.eventListeners) { + allListeners = this.eventListeners[key]; + len = allListeners.length; + for (n = 0; n < len; n++) { + listener = allListeners[n]; + /* + * don't include konva namespaced listeners because + * these are generated by the constructors + */ + if (listener.name.indexOf(KONVA) < 0) { + // if listeners array doesn't exist, then create it + if (!node.eventListeners[key]) { + node.eventListeners[key] = []; + } + node.eventListeners[key].push(listener); + } + } + } + return node; + }, + _toKonvaCanvas: function(config) { + config = config || {}; + + var stage = this.getStage(), + x = config.x || 0, + y = config.y || 0, + pixelRatio = config.pixelRatio || 1, + canvas = new Konva.SceneCanvas({ + width: config.width || + this.getWidth() || + (stage ? stage.getWidth() : 0), + height: config.height || + this.getHeight() || + (stage ? stage.getHeight() : 0), + pixelRatio: pixelRatio + }), + context = canvas.getContext(); + + context.save(); + + if (x || y) { + context.translate((-1) * x, (-1) * y); + } + + this.drawScene(canvas); + context.restore(); + + return canvas; + }, + /** + * converts node into an canvas element. + * @method + * @memberof Konva.Node.prototype + * @param {Object} config + * @param {Function} config.callback function executed when the composite has completed + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image. Default is 1. + * @example + * var canvas = node.toCanvas(); + */ + toCanvas: function(config) { + return this._toKonvaCanvas(config)._canvas; + }, + /** + * Creates a composite data URL. If MIME type is not + * specified, then "image/png" will result. For "image/jpeg", specify a quality + * level as quality (range 0.0 - 1.0) + * @method + * @memberof Konva.Node.prototype + * @param {Object} config + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image url. Default is 1 + * @returns {String} + */ + toDataURL: function(config) { + config = config || {}; + var mimeType = config.mimeType || null, quality = config.quality || null; + return this._toKonvaCanvas(config).toDataURL(mimeType, quality); + }, + /** + * converts node into an image. Since the toImage + * method is asynchronous, a callback is required. toImage is most commonly used + * to cache complex drawings as an image so that they don't have to constantly be redrawn + * @method + * @memberof Konva.Node.prototype + * @param {Object} config + * @param {Function} config.callback function executed when the composite has completed + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image. Default is 1. + * @example + * var image = node.toImage({ + * callback: function(img) { + * // do stuff with img + * } + * }); + */ + toImage: function(config) { + if (!config || !config.callback) { + throw 'callback required for toImage method config argument'; + } + Konva.Util._getImage(this.toDataURL(config), function(img) { + config.callback(img); + }); + }, + setSize: function(size) { + this.setWidth(size.width); + this.setHeight(size.height); + return this; + }, + getSize: function() { + return { + width: this.getWidth(), + height: this.getHeight() + }; + }, + getWidth: function() { + return this.attrs.width || 0; + }, + getHeight: function() { + return this.attrs.height || 0; + }, + /** + * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. + * @method + * @memberof Konva.Node.prototype + * @returns {String} + */ + getClassName: function() { + return this.className || this.nodeType; + }, + /** + * get the node type, which may return Stage, Layer, Group, or Node + * @method + * @memberof Konva.Node.prototype + * @returns {String} + */ + getType: function() { + return this.nodeType; + }, + getDragDistance: function() { + // compare with undefined because we need to track 0 value + if (this.attrs.dragDistance !== undefined) { + return this.attrs.dragDistance; + } else if (this.parent) { + return this.parent.getDragDistance(); + } else { + return Konva.dragDistance; + } + }, + _get: function(selector) { + return this.className === selector || this.nodeType === selector + ? [this] + : []; + }, + _off: function(type, name) { + var evtListeners = this.eventListeners[type], i, evtName; + + for (i = 0; i < evtListeners.length; i++) { + evtName = evtListeners[i].name; + // the following two conditions must be true in order to remove a handler: + // 1) the current event name cannot be konva unless the event name is konva + // this enables developers to force remove a konva specific listener for whatever reason + // 2) an event name is not specified, or if one is specified, it matches the current event name + if ( + (evtName !== 'konva' || name === 'konva') && + (!name || evtName === name) + ) { + evtListeners.splice(i, 1); + if (evtListeners.length === 0) { + delete this.eventListeners[type]; + break; + } + i--; + } + } + }, + _fireChangeEvent: function(attr, oldVal, newVal) { + this._fire(attr + CHANGE, { + oldVal: oldVal, + newVal: newVal + }); + }, + setId: function(id) { + var oldId = this.getId(); + + Konva._removeId(oldId); + Konva._addId(this, id); + this._setAttr(ID, id); + return this; + }, + setName: function(name) { + var oldNames = (this.getName() || '').split(/\s/g); + var newNames = (name || '').split(/\s/g); + var subname, i; + // remove all subnames + for (i = 0; i < oldNames.length; i++) { + subname = oldNames[i]; + if (newNames.indexOf(subname) === -1 && subname) { + Konva._removeName(subname, this._id); + } + } + + // add new names + for (i = 0; i < newNames.length; i++) { + subname = newNames[i]; + if (oldNames.indexOf(subname) === -1 && subname) { + Konva._addName(this, subname); + } + } + + this._setAttr(NAME, name); + return this; + }, + // naming methods + /** + * add name to node + * @method + * @memberof Konva.Node.prototype + * @param {String} name + * @returns {Konva.Node} + * @example + * node.name('red'); + * node.addName('selected'); + * node.name(); // return 'red selected' + */ + addName: function(name) { + if (!this.hasName(name)) { + var oldName = this.name(); + var newName = oldName ? oldName + ' ' + name : name; + this.setName(newName); + } + return this; + }, + /** + * check is node has name + * @method + * @memberof Konva.Node.prototype + * @param {String} name + * @returns {Boolean} + * @example + * node.name('red'); + * node.hasName('red'); // return true + * node.hasName('selected'); // return false + */ + hasName: function(name) { + var names = (this.name() || '').split(/\s/g); + return names.indexOf(name) !== -1; + }, + /** + * remove name from node + * @method + * @memberof Konva.Node.prototype + * @param {String} name + * @returns {Konva.Node} + * @example + * node.name('red selected'); + * node.removeName('selected'); + * node.hasName('selected'); // return false + * node.name(); // return 'red' + */ + removeName: function(name) { + var names = (this.name() || '').split(/\s/g); + var index = names.indexOf(name); + if (index !== -1) { + names.splice(index, 1); + this.setName(names.join(' ')); + } + return this; + }, + /** + * set attr + * @method + * @memberof Konva.Node.prototype + * @param {String} attr + * @param {*} val + * @returns {Konva.Node} + * @example + * node.setAttr('x', 5); + */ + setAttr: function(attr, val) { + var method = SET + Konva.Util._capitalize(attr), func = this[method]; + + if (Konva.Util._isFunction(func)) { + func.call(this, val); + } else { + // otherwise set directly + this._setAttr(attr, val); + } + return this; + }, + _setAttr: function(key, val) { + var oldVal; + oldVal = this.attrs[key]; + if (oldVal === val) { + return; + } + if (val === undefined || val === null) { + delete this.attrs[key]; + } else { + this.attrs[key] = val; + } + this._fireChangeEvent(key, oldVal, val); + }, + _setComponentAttr: function(key, component, val) { + var oldVal; + if (val !== undefined) { + oldVal = this.attrs[key]; + + if (!oldVal) { + // set value to default value using getAttr + this.attrs[key] = this.getAttr(key); + } + + this.attrs[key][component] = val; + this._fireChangeEvent(key, oldVal, val); + } + }, + _fireAndBubble: function(eventType, evt, compareShape) { + var okayToRun = true; + + if (evt && this.nodeType === SHAPE) { + evt.target = this; + } + + if ( + eventType === MOUSEENTER && + compareShape && + (this._id === compareShape._id || + (this.isAncestorOf && this.isAncestorOf(compareShape))) + ) { + okayToRun = false; + } else if ( + eventType === MOUSELEAVE && + compareShape && + (this._id === compareShape._id || + (this.isAncestorOf && this.isAncestorOf(compareShape))) + ) { + okayToRun = false; + } + if (okayToRun) { + this._fire(eventType, evt); + + // simulate event bubbling + var stopBubble = (eventType === MOUSEENTER || + eventType === MOUSELEAVE) && + (compareShape && + compareShape.isAncestorOf && + compareShape.isAncestorOf(this) && + !compareShape.isAncestorOf(this.parent)); + if ( + ((evt && !evt.cancelBubble) || !evt) && + this.parent && + this.parent.isListening() && + !stopBubble + ) { + if (compareShape && compareShape.parent) { + this._fireAndBubble.call( + this.parent, + eventType, + evt, + compareShape.parent + ); + } else { + this._fireAndBubble.call(this.parent, eventType, evt); + } + } + } + }, + _fire: function(eventType, evt) { + var events = this.eventListeners[eventType], i; + + evt = evt || {}; + evt.currentTarget = this; + evt.type = eventType; + + if (events) { + for (i = 0; i < events.length; i++) { + events[i].handler.call(this, evt); + } + } + }, + /** + * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn + * @method + * @memberof Konva.Node.prototype + * @returns {Konva.Node} + */ + draw: function() { + this.drawScene(); + this.drawHit(); + return this; + } + }); + + /** + * create node with JSON string or an Object. De-serializtion does not generate custom + * shape drawing functions, images, or event handlers (this would make the + * serialized object huge). If your app uses custom shapes, images, and + * event handlers (it probably does), then you need to select the appropriate + * shapes after loading the stage and set these properties via on(), setDrawFunc(), + * and setImage() methods + * @method + * @memberof Konva.Node + * @param {String|Object} json string or object + * @param {Element} [container] optional container dom element used only if you're + * creating a stage node + */ + Konva.Node.create = function(data, container) { + if (Konva.Util._isString(data)) { + data = JSON.parse(data); + } + return this._createNode(data, container); + }; + Konva.Node._createNode = function(obj, container) { + var className = Konva.Node.prototype.getClassName.call(obj), + children = obj.children, + no, + len, + n; + + // if container was passed in, add it to attrs + if (container) { + obj.attrs.container = container; + } + + no = new Konva[className](obj.attrs); + if (children) { + len = children.length; + for (n = 0; n < len; n++) { + no.add(this._createNode(children[n])); + } + } + + return no; + }; + + // =========================== add getters setters =========================== + + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'position'); + /** + * get/set node position relative to parent + * @name position + * @method + * @memberof Konva.Node.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Object} + * @example + * // get position + * var position = node.position(); + * + * // set position + * node.position({ + * x: 5 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'x', 0); + + /** + * get/set x position + * @name x + * @method + * @memberof Konva.Node.prototype + * @param {Number} x + * @returns {Object} + * @example + * // get x + * var x = node.x(); + * + * // set x + * node.x(5); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'y', 0); + + /** + * get/set y position + * @name y + * @method + * @memberof Konva.Node.prototype + * @param {Number} y + * @returns {Integer} + * @example + * // get y + * var y = node.y(); + * + * // set y + * node.y(5); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'opacity', 1); + + /** + * get/set opacity. Opacity values range from 0 to 1. + * A node with an opacity of 0 is fully transparent, and a node + * with an opacity of 1 is fully opaque + * @name opacity + * @method + * @memberof Konva.Node.prototype + * @param {Object} opacity + * @returns {Number} + * @example + * // get opacity + * var opacity = node.opacity(); + * + * // set opacity + * node.opacity(0.5); + */ + + Konva.Factory.addGetter(Konva.Node, 'name'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'name'); + + /** + * get/set name + * @name name + * @method + * @memberof Konva.Node.prototype + * @param {String} name + * @returns {String} + * @example + * // get name + * var name = node.name(); + * + * // set name + * node.name('foo'); + * + * // also node may have multiple names (as css classes) + * node.name('foo bar'); + */ + + Konva.Factory.addGetter(Konva.Node, 'id'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'id'); + + /** + * get/set id. Id is global for whole page. + * @name id + * @method + * @memberof Konva.Node.prototype + * @param {String} id + * @returns {String} + * @example + * // get id + * var name = node.id(); + * + * // set id + * node.id('foo'); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'rotation', 0); + + /** + * get/set rotation in degrees + * @name rotation + * @method + * @memberof Konva.Node.prototype + * @param {Number} rotation + * @returns {Number} + * @example + * // get rotation in degrees + * var rotation = node.rotation(); + * + * // set rotation in degrees + * node.rotation(45); + */ + + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'scale', ['x', 'y']); + + /** + * get/set scale + * @name scale + * @param {Object} scale + * @param {Number} scale.x + * @param {Number} scale.y + * @method + * @memberof Konva.Node.prototype + * @returns {Object} + * @example + * // get scale + * var scale = node.scale(); + * + * // set scale + * shape.scale({ + * x: 2 + * y: 3 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'scaleX', 1); + + /** + * get/set scale x + * @name scaleX + * @param {Number} x + * @method + * @memberof Konva.Node.prototype + * @returns {Number} + * @example + * // get scale x + * var scaleX = node.scaleX(); + * + * // set scale x + * node.scaleX(2); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'scaleY', 1); + + /** + * get/set scale y + * @name scaleY + * @param {Number} y + * @method + * @memberof Konva.Node.prototype + * @returns {Number} + * @example + * // get scale y + * var scaleY = node.scaleY(); + * + * // set scale y + * node.scaleY(2); + */ + + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'skew', ['x', 'y']); + + /** + * get/set skew + * @name skew + * @param {Object} skew + * @param {Number} skew.x + * @param {Number} skew.y + * @method + * @memberof Konva.Node.prototype + * @returns {Object} + * @example + * // get skew + * var skew = node.skew(); + * + * // set skew + * node.skew({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'skewX', 0); + + /** + * get/set skew x + * @name skewX + * @param {Number} x + * @method + * @memberof Konva.Node.prototype + * @returns {Number} + * @example + * // get skew x + * var skewX = node.skewX(); + * + * // set skew x + * node.skewX(3); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'skewY', 0); + + /** + * get/set skew y + * @name skewY + * @param {Number} y + * @method + * @memberof Konva.Node.prototype + * @returns {Number} + * @example + * // get skew y + * var skewY = node.skewY(); + * + * // set skew y + * node.skewY(3); + */ + + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'offset', ['x', 'y']); + + /** + * get/set offset. Offsets the default position and rotation point + * @method + * @memberof Konva.Node.prototype + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get offset + * var offset = node.offset(); + * + * // set offset + * node.offset({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'offsetX', 0); + + /** + * get/set offset x + * @name offsetX + * @method + * @memberof Konva.Node.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get offset x + * var offsetX = node.offsetX(); + * + * // set offset x + * node.offsetX(3); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'offsetY', 0); + + /** + * get/set offset y + * @name offsetY + * @method + * @memberof Konva.Node.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get offset y + * var offsetY = node.offsetY(); + * + * // set offset y + * node.offsetY(3); + */ + + Konva.Factory.addSetter(Konva.Node, 'dragDistance'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'dragDistance'); + + /** + * get/set drag distance + * @name dragDistance + * @method + * @memberof Konva.Node.prototype + * @param {Number} distance + * @returns {Number} + * @example + * // get drag distance + * var dragDistance = node.dragDistance(); + * + * // set distance + * // node starts dragging only if pointer moved more then 3 pixels + * node.dragDistance(3); + * // or set globally + * Konva.dragDistance = 3; + */ + + Konva.Factory.addSetter(Konva.Node, 'width', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'width'); + /** + * get/set width + * @name width + * @method + * @memberof Konva.Node.prototype + * @param {Number} width + * @returns {Number} + * @example + * // get width + * var width = node.width(); + * + * // set width + * node.width(100); + */ + + Konva.Factory.addSetter(Konva.Node, 'height', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'height'); + /** + * get/set height + * @name height + * @method + * @memberof Konva.Node.prototype + * @param {Number} height + * @returns {Number} + * @example + * // get height + * var height = node.height(); + * + * // set height + * node.height(100); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'listening', 'inherit'); + /** + * get/set listenig attr. If you need to determine if a node is listening or not + * by taking into account its parents, use the isListening() method + * @name listening + * @method + * @memberof Konva.Node.prototype + * @param {Boolean|String} listening Can be "inherit", true, or false. The default is "inherit". + * @returns {Boolean|String} + * @example + * // get listening attr + * var listening = node.listening(); + * + * // stop listening for events + * node.listening(false); + * + * // listen for events + * node.listening(true); + * + * // listen to events according to the parent + * node.listening('inherit'); + */ + + /** + * get/set preventDefault + * By default all shapes will prevent default behaviour + * of a browser on a pointer move or tap. + * that will prevent native scrolling when you are trying to drag&drop a node + * but sometimes you may need to enable default actions + * in that case you can set the property to false + * @name preventDefault + * @method + * @memberof Konva.Node.prototype + * @param {Number} preventDefault + * @returns {Number} + * @example + * // get preventDefault + * var shouldPrevent = shape.preventDefault(); + * + * // set preventDefault + * shape.preventDefault(false); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'preventDefault', true); + + Konva.Factory.addGetterSetter(Konva.Node, 'filters', undefined, function( + val + ) { + this._filterUpToDate = false; + return val; + }); + /** + * get/set filters. Filters are applied to cached canvases + * @name filters + * @method + * @memberof Konva.Node.prototype + * @param {Array} filters array of filters + * @returns {Array} + * @example + * // get filters + * var filters = node.filters(); + * + * // set a single filter + * node.cache(); + * node.filters([Konva.Filters.Blur]); + * + * // set multiple filters + * node.cache(); + * node.filters([ + * Konva.Filters.Blur, + * Konva.Filters.Sepia, + * Konva.Filters.Invert + * ]); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'visible', 'inherit'); + /** + * get/set visible attr. Can be "inherit", true, or false. The default is "inherit". + * If you need to determine if a node is visible or not + * by taking into account its parents, use the isVisible() method + * @name visible + * @method + * @memberof Konva.Node.prototype + * @param {Boolean|String} visible + * @returns {Boolean|String} + * @example + * // get visible attr + * var visible = node.visible(); + * + * // make invisible + * node.visible(false); + * + * // make visible + * node.visible(true); + * + * // make visible according to the parent + * node.visible('inherit'); + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'transformsEnabled', 'all'); + + /** + * get/set transforms that are enabled. Can be "all", "none", or "position". The default + * is "all" + * @name transformsEnabled + * @method + * @memberof Konva.Node.prototype + * @param {String} enabled + * @returns {String} + * @example + * // enable position transform only to improve draw performance + * node.transformsEnabled('position'); + * + * // enable all transforms + * node.transformsEnabled('all'); + */ + + /** + * get/set node size + * @name size + * @method + * @memberof Konva.Node.prototype + * @param {Object} size + * @param {Number} size.width + * @param {Number} size.height + * @returns {Object} + * @example + * // get node size + * var size = node.size(); + * var x = size.x; + * var y = size.y; + * + * // set size + * node.size({ + * width: 100, + * height: 200 + * }); + */ + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'size'); + + Konva.Factory.backCompat(Konva.Node, { + rotateDeg: 'rotate', + setRotationDeg: 'setRotation', + getRotationDeg: 'getRotation' + }); + + Konva.Collection.mapMethods(Konva.Node); +})(Konva); + +(function() { + 'use strict'; + /** + * Grayscale Filter + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Grayscale]); + */ + Konva.Filters.Grayscale = function(imageData) { + var data = imageData.data, len = data.length, i, brightness; + + for (i = 0; i < len; i += 4) { + brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; + // red + data[i] = brightness; + // green + data[i + 1] = brightness; + // blue + data[i + 2] = brightness; + } + }; +})(); + +(function() { + 'use strict'; + /** + * Brighten Filter. + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Brighten]); + * node.brightness(0.8); + */ + Konva.Filters.Brighten = function(imageData) { + var brightness = this.brightness() * 255, + data = imageData.data, + len = data.length, + i; + + for (i = 0; i < len; i += 4) { + // red + data[i] += brightness; + // green + data[i + 1] += brightness; + // blue + data[i + 2] += brightness; + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'brightness', + 0, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set filter brightness. The brightness is a number between -1 and 1.  Positive values + * brighten the pixels and negative values darken them. Use with {@link Konva.Filters.Brighten} filter. + * @name brightness + * @method + * @memberof Konva.Node.prototype + * @param {Number} brightness value between -1 and 1 + * @returns {Number} + */ +})(); + +(function() { + 'use strict'; + /** + * Invert Filter + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Invert]); + */ + Konva.Filters.Invert = function(imageData) { + var data = imageData.data, len = data.length, i; + + for (i = 0; i < len; i += 4) { + // red + data[i] = 255 - data[i]; + // green + data[i + 1] = 255 - data[i + 1]; + // blue + data[i + 2] = 255 - data[i + 2]; + } + }; +})(); + +/* + the Gauss filter + master repo: https://github.com/pavelpower/kineticjsGaussFilter +*/ +(function() { + 'use strict'; + /* + + StackBlur - a fast almost Gaussian Blur For Canvas + + Version: 0.5 + Author: Mario Klingemann + Contact: mario@quasimondo.com + Website: http://www.quasimondo.com/StackBlurForCanvas + Twitter: @quasimondo + + In case you find this class useful - especially in commercial projects - + I am not totally unhappy for a small donation to my PayPal account + mario@quasimondo.de + + Or support me on flattr: + https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript + + Copyright (c) 2010 Mario Klingemann + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + */ + + function BlurStack() { + this.r = 0; + this.g = 0; + this.b = 0; + this.a = 0; + this.next = null; + } + + var mul_table = [ + 512, + 512, + 456, + 512, + 328, + 456, + 335, + 512, + 405, + 328, + 271, + 456, + 388, + 335, + 292, + 512, + 454, + 405, + 364, + 328, + 298, + 271, + 496, + 456, + 420, + 388, + 360, + 335, + 312, + 292, + 273, + 512, + 482, + 454, + 428, + 405, + 383, + 364, + 345, + 328, + 312, + 298, + 284, + 271, + 259, + 496, + 475, + 456, + 437, + 420, + 404, + 388, + 374, + 360, + 347, + 335, + 323, + 312, + 302, + 292, + 282, + 273, + 265, + 512, + 497, + 482, + 468, + 454, + 441, + 428, + 417, + 405, + 394, + 383, + 373, + 364, + 354, + 345, + 337, + 328, + 320, + 312, + 305, + 298, + 291, + 284, + 278, + 271, + 265, + 259, + 507, + 496, + 485, + 475, + 465, + 456, + 446, + 437, + 428, + 420, + 412, + 404, + 396, + 388, + 381, + 374, + 367, + 360, + 354, + 347, + 341, + 335, + 329, + 323, + 318, + 312, + 307, + 302, + 297, + 292, + 287, + 282, + 278, + 273, + 269, + 265, + 261, + 512, + 505, + 497, + 489, + 482, + 475, + 468, + 461, + 454, + 447, + 441, + 435, + 428, + 422, + 417, + 411, + 405, + 399, + 394, + 389, + 383, + 378, + 373, + 368, + 364, + 359, + 354, + 350, + 345, + 341, + 337, + 332, + 328, + 324, + 320, + 316, + 312, + 309, + 305, + 301, + 298, + 294, + 291, + 287, + 284, + 281, + 278, + 274, + 271, + 268, + 265, + 262, + 259, + 257, + 507, + 501, + 496, + 491, + 485, + 480, + 475, + 470, + 465, + 460, + 456, + 451, + 446, + 442, + 437, + 433, + 428, + 424, + 420, + 416, + 412, + 408, + 404, + 400, + 396, + 392, + 388, + 385, + 381, + 377, + 374, + 370, + 367, + 363, + 360, + 357, + 354, + 350, + 347, + 344, + 341, + 338, + 335, + 332, + 329, + 326, + 323, + 320, + 318, + 315, + 312, + 310, + 307, + 304, + 302, + 299, + 297, + 294, + 292, + 289, + 287, + 285, + 282, + 280, + 278, + 275, + 273, + 271, + 269, + 267, + 265, + 263, + 261, + 259 + ]; + + var shg_table = [ + 9, + 11, + 12, + 13, + 13, + 14, + 14, + 15, + 15, + 15, + 15, + 16, + 16, + 16, + 16, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 18, + 18, + 18, + 18, + 18, + 18, + 18, + 18, + 18, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24 + ]; + + function filterGaussBlurRGBA(imageData, radius) { + var pixels = imageData.data, + width = imageData.width, + height = imageData.height; + + var x, + y, + i, + p, + yp, + yi, + yw, + r_sum, + g_sum, + b_sum, + a_sum, + r_out_sum, + g_out_sum, + b_out_sum, + a_out_sum, + r_in_sum, + g_in_sum, + b_in_sum, + a_in_sum, + pr, + pg, + pb, + pa, + rbs; + + var div = radius + radius + 1, + widthMinus1 = width - 1, + heightMinus1 = height - 1, + radiusPlus1 = radius + 1, + sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2, + stackStart = new BlurStack(), + stackEnd = null, + stack = stackStart, + stackIn = null, + stackOut = null, + mul_sum = mul_table[radius], + shg_sum = shg_table[radius]; + + for (i = 1; i < div; i++) { + stack = stack.next = new BlurStack(); + if (i === radiusPlus1) { + stackEnd = stack; + } + } + + stack.next = stackStart; + + yw = yi = 0; + + for (y = 0; y < height; y++) { + r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0; + + r_out_sum = radiusPlus1 * (pr = pixels[yi]); + g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); + b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); + a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); + + r_sum += sumFactor * pr; + g_sum += sumFactor * pg; + b_sum += sumFactor * pb; + a_sum += sumFactor * pa; + + stack = stackStart; + + for (i = 0; i < radiusPlus1; i++) { + stack.r = pr; + stack.g = pg; + stack.b = pb; + stack.a = pa; + stack = stack.next; + } + + for (i = 1; i < radiusPlus1; i++) { + p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); + r_sum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i); + g_sum += (stack.g = pg = pixels[p + 1]) * rbs; + b_sum += (stack.b = pb = pixels[p + 2]) * rbs; + a_sum += (stack.a = pa = pixels[p + 3]) * rbs; + + r_in_sum += pr; + g_in_sum += pg; + b_in_sum += pb; + a_in_sum += pa; + + stack = stack.next; + } + + stackIn = stackStart; + stackOut = stackEnd; + for (x = 0; x < width; x++) { + pixels[yi + 3] = pa = a_sum * mul_sum >> shg_sum; + if (pa !== 0) { + pa = 255 / pa; + pixels[yi] = (r_sum * mul_sum >> shg_sum) * pa; + pixels[yi + 1] = (g_sum * mul_sum >> shg_sum) * pa; + pixels[yi + 2] = (b_sum * mul_sum >> shg_sum) * pa; + } else { + pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0; + } + + r_sum -= r_out_sum; + g_sum -= g_out_sum; + b_sum -= b_out_sum; + a_sum -= a_out_sum; + + r_out_sum -= stackIn.r; + g_out_sum -= stackIn.g; + b_out_sum -= stackIn.b; + a_out_sum -= stackIn.a; + + p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2; + + r_in_sum += stackIn.r = pixels[p]; + g_in_sum += stackIn.g = pixels[p + 1]; + b_in_sum += stackIn.b = pixels[p + 2]; + a_in_sum += stackIn.a = pixels[p + 3]; + + r_sum += r_in_sum; + g_sum += g_in_sum; + b_sum += b_in_sum; + a_sum += a_in_sum; + + stackIn = stackIn.next; + + r_out_sum += pr = stackOut.r; + g_out_sum += pg = stackOut.g; + b_out_sum += pb = stackOut.b; + a_out_sum += pa = stackOut.a; + + r_in_sum -= pr; + g_in_sum -= pg; + b_in_sum -= pb; + a_in_sum -= pa; + + stackOut = stackOut.next; + + yi += 4; + } + yw += width; + } + + for (x = 0; x < width; x++) { + g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0; + + yi = x << 2; + r_out_sum = radiusPlus1 * (pr = pixels[yi]); + g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); + b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); + a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); + + r_sum += sumFactor * pr; + g_sum += sumFactor * pg; + b_sum += sumFactor * pb; + a_sum += sumFactor * pa; + + stack = stackStart; + + for (i = 0; i < radiusPlus1; i++) { + stack.r = pr; + stack.g = pg; + stack.b = pb; + stack.a = pa; + stack = stack.next; + } + + yp = width; + + for (i = 1; i <= radius; i++) { + yi = yp + x << 2; + + r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i); + g_sum += (stack.g = pg = pixels[yi + 1]) * rbs; + b_sum += (stack.b = pb = pixels[yi + 2]) * rbs; + a_sum += (stack.a = pa = pixels[yi + 3]) * rbs; + + r_in_sum += pr; + g_in_sum += pg; + b_in_sum += pb; + a_in_sum += pa; + + stack = stack.next; + + if (i < heightMinus1) { + yp += width; + } + } + + yi = x; + stackIn = stackStart; + stackOut = stackEnd; + for (y = 0; y < height; y++) { + p = yi << 2; + pixels[p + 3] = pa = a_sum * mul_sum >> shg_sum; + if (pa > 0) { + pa = 255 / pa; + pixels[p] = (r_sum * mul_sum >> shg_sum) * pa; + pixels[p + 1] = (g_sum * mul_sum >> shg_sum) * pa; + pixels[p + 2] = (b_sum * mul_sum >> shg_sum) * pa; + } else { + pixels[p] = pixels[p + 1] = pixels[p + 2] = 0; + } + + r_sum -= r_out_sum; + g_sum -= g_out_sum; + b_sum -= b_out_sum; + a_sum -= a_out_sum; + + r_out_sum -= stackIn.r; + g_out_sum -= stackIn.g; + b_out_sum -= stackIn.b; + a_out_sum -= stackIn.a; + + p = x + + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << + 2; + + r_sum += r_in_sum += stackIn.r = pixels[p]; + g_sum += g_in_sum += stackIn.g = pixels[p + 1]; + b_sum += b_in_sum += stackIn.b = pixels[p + 2]; + a_sum += a_in_sum += stackIn.a = pixels[p + 3]; + + stackIn = stackIn.next; + + r_out_sum += pr = stackOut.r; + g_out_sum += pg = stackOut.g; + b_out_sum += pb = stackOut.b; + a_out_sum += pa = stackOut.a; + + r_in_sum -= pr; + g_in_sum -= pg; + b_in_sum -= pb; + a_in_sum -= pa; + + stackOut = stackOut.next; + + yi += width; + } + } + } + + /** + * Blur Filter + * @function + * @name Blur + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Blur]); + * node.blurRadius(10); + */ + Konva.Filters.Blur = function Blur(imageData) { + var radius = Math.round(this.blurRadius()); + + if (radius > 0) { + filterGaussBlurRGBA(imageData, radius); + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'blurRadius', + 0, + null, + Konva.Factory.afterSetFilter + ); + + /** + * get/set blur radius. Use with {@link Konva.Filters.Blur} filter + * @name blurRadius + * @method + * @memberof Konva.Node.prototype + * @param {Integer} radius + * @returns {Integer} + */ +})(); + +/*eslint-disable max-depth */ +(function() { + 'use strict'; + function pixelAt(idata, x, y) { + var idx = (y * idata.width + x) * 4; + var d = []; + d.push( + idata.data[idx++], + idata.data[idx++], + idata.data[idx++], + idata.data[idx++] + ); + return d; + } + + function rgbDistance(p1, p2) { + return Math.sqrt( + Math.pow(p1[0] - p2[0], 2) + + Math.pow(p1[1] - p2[1], 2) + + Math.pow(p1[2] - p2[2], 2) + ); + } + + function rgbMean(pTab) { + var m = [0, 0, 0]; + + for (var i = 0; i < pTab.length; i++) { + m[0] += pTab[i][0]; + m[1] += pTab[i][1]; + m[2] += pTab[i][2]; + } + + m[0] /= pTab.length; + m[1] /= pTab.length; + m[2] /= pTab.length; + + return m; + } + + function backgroundMask(idata, threshold) { + var rgbv_no = pixelAt(idata, 0, 0); + var rgbv_ne = pixelAt(idata, idata.width - 1, 0); + var rgbv_so = pixelAt(idata, 0, idata.height - 1); + var rgbv_se = pixelAt(idata, idata.width - 1, idata.height - 1); + + var thres = threshold || 10; + if ( + rgbDistance(rgbv_no, rgbv_ne) < thres && + rgbDistance(rgbv_ne, rgbv_se) < thres && + rgbDistance(rgbv_se, rgbv_so) < thres && + rgbDistance(rgbv_so, rgbv_no) < thres + ) { + // Mean color + var mean = rgbMean([rgbv_ne, rgbv_no, rgbv_se, rgbv_so]); + + // Mask based on color distance + var mask = []; + for (var i = 0; i < idata.width * idata.height; i++) { + var d = rgbDistance(mean, [ + idata.data[i * 4], + idata.data[i * 4 + 1], + idata.data[i * 4 + 2] + ]); + mask[i] = d < thres ? 0 : 255; + } + + return mask; + } + } + + function applyMask(idata, mask) { + for (var i = 0; i < idata.width * idata.height; i++) { + idata.data[4 * i + 3] = mask[i]; + } + } + + function erodeMask(mask, sw, sh) { + var weights = [1, 1, 1, 1, 0, 1, 1, 1, 1]; + var side = Math.round(Math.sqrt(weights.length)); + var halfSide = Math.floor(side / 2); + + var maskResult = []; + for (var y = 0; y < sh; y++) { + for (var x = 0; x < sw; x++) { + var so = y * sw + x; + var a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = y + cy - halfSide; + var scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + var srcOff = scy * sw + scx; + var wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = a === 255 * 8 ? 255 : 0; + } + } + + return maskResult; + } + + function dilateMask(mask, sw, sh) { + var weights = [1, 1, 1, 1, 1, 1, 1, 1, 1]; + var side = Math.round(Math.sqrt(weights.length)); + var halfSide = Math.floor(side / 2); + + var maskResult = []; + for (var y = 0; y < sh; y++) { + for (var x = 0; x < sw; x++) { + var so = y * sw + x; + var a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = y + cy - halfSide; + var scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + var srcOff = scy * sw + scx; + var wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = a >= 255 * 4 ? 255 : 0; + } + } + + return maskResult; + } + + function smoothEdgeMask(mask, sw, sh) { + var weights = [ + 1 / 9, + 1 / 9, + 1 / 9, + 1 / 9, + 1 / 9, + 1 / 9, + 1 / 9, + 1 / 9, + 1 / 9 + ]; + var side = Math.round(Math.sqrt(weights.length)); + var halfSide = Math.floor(side / 2); + + var maskResult = []; + for (var y = 0; y < sh; y++) { + for (var x = 0; x < sw; x++) { + var so = y * sw + x; + var a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = y + cy - halfSide; + var scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + var srcOff = scy * sw + scx; + var wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = a; + } + } + + return maskResult; + } + + /** + * Mask Filter + * @function + * @name Mask + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Mask]); + * node.threshold(200); + */ + Konva.Filters.Mask = function(imageData) { + // Detect pixels close to the background color + var threshold = this.threshold(), + mask = backgroundMask(imageData, threshold); + if (mask) { + // Erode + mask = erodeMask(mask, imageData.width, imageData.height); + + // Dilate + mask = dilateMask(mask, imageData.width, imageData.height); + + // Gradient + mask = smoothEdgeMask(mask, imageData.width, imageData.height); + + // Apply mask + applyMask(imageData, mask); + + // todo : Update hit region function according to mask + } + + return imageData; + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'threshold', + 0, + null, + Konva.Factory.afterSetFilter + ); +})(); + +(function() { + 'use strict'; + /** + * RGB Filter + * @function + * @name RGB + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.RGB]); + * node.blue(120); + * node.green(200); + */ + Konva.Filters.RGB = function(imageData) { + var data = imageData.data, + nPixels = data.length, + red = this.red(), + green = this.green(), + blue = this.blue(), + i, + brightness; + + for (i = 0; i < nPixels; i += 4) { + brightness = (0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]) / + 255; + data[i] = brightness * red; // r + data[i + 1] = brightness * green; // g + data[i + 2] = brightness * blue; // b + data[i + 3] = data[i + 3]; // alpha + } + }; + + Konva.Factory.addGetterSetter(Konva.Node, 'red', 0, function(val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } + }); + /** + * get/set filter red value. Use with {@link Konva.Filters.RGB} filter. + * @name red + * @method + * @memberof Konva.Node.prototype + * @param {Integer} red value between 0 and 255 + * @returns {Integer} + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'green', 0, function(val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } + }); + /** + * get/set filter green value. Use with {@link Konva.Filters.RGB} filter. + * @name green + * @method + * @memberof Konva.Node.prototype + * @param {Integer} green value between 0 and 255 + * @returns {Integer} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'blue', + 0, + Konva.Validators.RGBComponent, + Konva.Factory.afterSetFilter + ); + /** + * get/set filter blue value. Use with {@link Konva.Filters.RGB} filter. + * @name blue + * @method + * @memberof Konva.Node.prototype + * @param {Integer} blue value between 0 and 255 + * @returns {Integer} + */ +})(); + +(function() { + 'use strict'; + /** + * RGBA Filter + * @function + * @name RGBA + * @memberof Konva.Filters + * @param {Object} imageData + * @author codefo + * @example + * node.cache(); + * node.filters([Konva.Filters.RGBA]); + * node.blue(120); + * node.green(200); + * node.alpha(0.3); + */ + Konva.Filters.RGBA = function(imageData) { + var data = imageData.data, + nPixels = data.length, + red = this.red(), + green = this.green(), + blue = this.blue(), + alpha = this.alpha(), + i, + ia; + + for (i = 0; i < nPixels; i += 4) { + ia = 1 - alpha; + + data[i] = red * alpha + data[i] * ia; // r + data[i + 1] = green * alpha + data[i + 1] * ia; // g + data[i + 2] = blue * alpha + data[i + 2] * ia; // b + } + }; + + Konva.Factory.addGetterSetter(Konva.Node, 'red', 0, function(val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } + }); + /** + * get/set filter red value. Use with {@link Konva.Filters.RGBA} filter. + * @name red + * @method + * @memberof Konva.Node.prototype + * @param {Integer} red value between 0 and 255 + * @returns {Integer} + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'green', 0, function(val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } else if (val < 0) { + return 0; + } else { + return Math.round(val); + } + }); + /** + * get/set filter green value. Use with {@link Konva.Filters.RGBA} filter. + * @name green + * @method + * @memberof Konva.Node.prototype + * @param {Integer} green value between 0 and 255 + * @returns {Integer} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'blue', + 0, + Konva.Validators.RGBComponent, + Konva.Factory.afterSetFilter + ); + /** + * get/set filter blue value. Use with {@link Konva.Filters.RGBA} filter. + * @name blue + * @method + * @memberof Konva.Node.prototype + * @param {Integer} blue value between 0 and 255 + * @returns {Integer} + */ + + Konva.Factory.addGetterSetter(Konva.Node, 'alpha', 1, function(val) { + this._filterUpToDate = false; + if (val > 1) { + return 1; + } else if (val < 0) { + return 0; + } else { + return val; + } + }); + /** + * get/set filter alpha value. Use with {@link Konva.Filters.RGBA} filter. + * @name alpha + * @method + * @memberof Konva.Node.prototype + * @param {Float} alpha value between 0 and 1 + * @returns {Float} + */ +})(); + +(function() { + 'use strict'; + /** + * HSV Filter. Adjusts the hue, saturation and value + * @function + * @name HSV + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * image.filters([Konva.Filters.HSV]); + * image.value(200); + */ + + Konva.Filters.HSV = function(imageData) { + var data = imageData.data, + nPixels = data.length, + v = Math.pow(2, this.value()), + s = Math.pow(2, this.saturation()), + h = Math.abs(this.hue() + 360) % 360, + i; + + // Basis for the technique used: + // http://beesbuzz.biz/code/hsv_color_transforms.php + // V is the value multiplier (1 for none, 2 for double, 0.5 for half) + // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) + // H is the hue shift in degrees (0 to 360) + // vsu = V*S*cos(H*PI/180); + // vsw = V*S*sin(H*PI/180); + //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] + //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] + //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] + + // Precompute the values in the matrix: + var vsu = v * s * Math.cos(h * Math.PI / 180), + vsw = v * s * Math.sin(h * Math.PI / 180); + // (result spot)(source spot) + var rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, + rg = 0.587 * v - 0.587 * vsu + 0.330 * vsw, + rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; + var gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, + gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, + gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; + var br = 0.299 * v - 0.300 * vsu + 1.250 * vsw, + bg = 0.587 * v - 0.586 * vsu - 1.050 * vsw, + bb = 0.114 * v + 0.886 * vsu - 0.200 * vsw; + + var r, g, b, a; + + for (i = 0; i < nPixels; i += 4) { + r = data[i + 0]; + g = data[i + 1]; + b = data[i + 2]; + a = data[i + 3]; + + data[i + 0] = rr * r + rg * g + rb * b; + data[i + 1] = gr * r + gg * g + gb * b; + data[i + 2] = br * r + bg * g + bb * b; + data[i + 3] = a; // alpha + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'hue', + 0, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name hue + * @method + * @memberof Konva.Node.prototype + * @param {Number} hue value between 0 and 359 + * @returns {Number} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'saturation', + 0, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name saturation + * @method + * @memberof Konva.Node.prototype + * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. + * @returns {Number} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'value', + 0, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set hsv value. Use with {@link Konva.Filters.HSV} filter. + * @name value + * @method + * @memberof Konva.Node.prototype + * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. + * @returns {Number} + */ +})(); + +(function() { + 'use strict'; + Konva.Factory.addGetterSetter( + Konva.Node, + 'hue', + 0, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name hue + * @method + * @memberof Konva.Node.prototype + * @param {Number} hue value between 0 and 359 + * @returns {Number} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'saturation', + 0, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. + * @name saturation + * @method + * @memberof Konva.Node.prototype + * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. + * @returns {Number} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'luminance', + 0, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set hsl luminance. Use with {@link Konva.Filters.HSL} filter. + * @name value + * @method + * @memberof Konva.Node.prototype + * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. + * @returns {Number} + */ + + /** + * HSL Filter. Adjusts the hue, saturation and luminance (or lightness) + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * image.filters([Konva.Filters.HSL]); + * image.luminance(200); + */ + + Konva.Filters.HSL = function(imageData) { + var data = imageData.data, + nPixels = data.length, + v = 1, + s = Math.pow(2, this.saturation()), + h = Math.abs(this.hue() + 360) % 360, + l = this.luminance() * 127, + i; + + // Basis for the technique used: + // http://beesbuzz.biz/code/hsv_color_transforms.php + // V is the value multiplier (1 for none, 2 for double, 0.5 for half) + // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) + // H is the hue shift in degrees (0 to 360) + // vsu = V*S*cos(H*PI/180); + // vsw = V*S*sin(H*PI/180); + //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] + //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] + //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] + + // Precompute the values in the matrix: + var vsu = v * s * Math.cos(h * Math.PI / 180), + vsw = v * s * Math.sin(h * Math.PI / 180); + // (result spot)(source spot) + var rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, + rg = 0.587 * v - 0.587 * vsu + 0.330 * vsw, + rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; + var gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, + gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, + gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; + var br = 0.299 * v - 0.300 * vsu + 1.250 * vsw, + bg = 0.587 * v - 0.586 * vsu - 1.050 * vsw, + bb = 0.114 * v + 0.886 * vsu - 0.200 * vsw; + + var r, g, b, a; + + for (i = 0; i < nPixels; i += 4) { + r = data[i + 0]; + g = data[i + 1]; + b = data[i + 2]; + a = data[i + 3]; + + data[i + 0] = rr * r + rg * g + rb * b + l; + data[i + 1] = gr * r + gg * g + gb * b + l; + data[i + 2] = br * r + bg * g + bb * b + l; + data[i + 3] = a; // alpha + } + }; +})(); + +(function() { + 'use strict'; + /** + * Emboss Filter. + * Pixastic Lib - Emboss filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * License: [http://www.pixastic.com/lib/license.txt] + * @function + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Emboss]); + * node.embossStrength(0.8); + * node.embossWhiteLevel(0.3); + * node.embossDirection('right'); + * node.embossBlend(true); + */ + Konva.Filters.Emboss = function(imageData) { + // pixastic strength is between 0 and 10. I want it between 0 and 1 + // pixastic greyLevel is between 0 and 255. I want it between 0 and 1. Also, + // a max value of greyLevel yields a white emboss, and the min value yields a black + // emboss. Therefore, I changed greyLevel to whiteLevel + var strength = this.embossStrength() * 10, + greyLevel = this.embossWhiteLevel() * 255, + direction = this.embossDirection(), + blend = this.embossBlend(), + dirY = 0, + dirX = 0, + data = imageData.data, + w = imageData.width, + h = imageData.height, + w4 = w * 4, + y = h; + + switch (direction) { + case 'top-left': + dirY = -1; + dirX = -1; + break; + case 'top': + dirY = -1; + dirX = 0; + break; + case 'top-right': + dirY = -1; + dirX = 1; + break; + case 'right': + dirY = 0; + dirX = 1; + break; + case 'bottom-right': + dirY = 1; + dirX = 1; + break; + case 'bottom': + dirY = 1; + dirX = 0; + break; + case 'bottom-left': + dirY = 1; + dirX = -1; + break; + case 'left': + dirY = 0; + dirX = -1; + break; + default: + Konva.Util.error('Unknown emboss direction: ' + direction); + } + + do { + var offsetY = (y - 1) * w4; + + var otherY = dirY; + if (y + otherY < 1) { + otherY = 0; + } + if (y + otherY > h) { + otherY = 0; + } + + var offsetYOther = (y - 1 + otherY) * w * 4; + + var x = w; + do { + var offset = offsetY + (x - 1) * 4; + + var otherX = dirX; + if (x + otherX < 1) { + otherX = 0; + } + if (x + otherX > w) { + otherX = 0; + } + + var offsetOther = offsetYOther + (x - 1 + otherX) * 4; + + var dR = data[offset] - data[offsetOther]; + var dG = data[offset + 1] - data[offsetOther + 1]; + var dB = data[offset + 2] - data[offsetOther + 2]; + + var dif = dR; + var absDif = dif > 0 ? dif : -dif; + + var absG = dG > 0 ? dG : -dG; + var absB = dB > 0 ? dB : -dB; + + if (absG > absDif) { + dif = dG; + } + if (absB > absDif) { + dif = dB; + } + + dif *= strength; + + if (blend) { + var r = data[offset] + dif; + var g = data[offset + 1] + dif; + var b = data[offset + 2] + dif; + + data[offset] = r > 255 ? 255 : r < 0 ? 0 : r; + data[offset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; + data[offset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; + } else { + var grey = greyLevel - dif; + if (grey < 0) { + grey = 0; + } else if (grey > 255) { + grey = 255; + } + + data[offset] = data[offset + 1] = data[offset + 2] = grey; + } + } while (--x); + } while (--y); + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'embossStrength', + 0.5, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set emboss strength. Use with {@link Konva.Filters.Emboss} filter. + * @name embossStrength + * @method + * @memberof Konva.Node.prototype + * @param {Number} level between 0 and 1. Default is 0.5 + * @returns {Number} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'embossWhiteLevel', + 0.5, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set emboss white level. Use with {@link Konva.Filters.Emboss} filter. + * @name embossWhiteLevel + * @method + * @memberof Konva.Node.prototype + * @param {Number} embossWhiteLevel between 0 and 1. Default is 0.5 + * @returns {Number} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'embossDirection', + 'top-left', + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set emboss direction. Use with {@link Konva.Filters.Emboss} filter. + * @name embossDirection + * @method + * @memberof Konva.Node.prototype + * @param {String} embossDirection can be top-left, top, top-right, right, bottom-right, bottom, bottom-left or left + * The default is top-left + * @returns {String} + */ + + Konva.Factory.addGetterSetter( + Konva.Node, + 'embossBlend', + false, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set emboss blend. Use with {@link Konva.Filters.Emboss} filter. + * @name embossBlend + * @method + * @memberof Konva.Node.prototype + * @param {Boolean} embossBlend + * @returns {Boolean} + */ +})(); + +(function() { + 'use strict'; + function remap(fromValue, fromMin, fromMax, toMin, toMax) { + // Compute the range of the data + var fromRange = fromMax - fromMin, toRange = toMax - toMin, toValue; + + // If either range is 0, then the value can only be mapped to 1 value + if (fromRange === 0) { + return toMin + toRange / 2; + } + if (toRange === 0) { + return toMin; + } + + // (1) untranslate, (2) unscale, (3) rescale, (4) retranslate + toValue = (fromValue - fromMin) / fromRange; + toValue = toRange * toValue + toMin; + + return toValue; + } + + /** + * Enhance Filter. Adjusts the colors so that they span the widest + * possible range (ie 0-255). Performs w*h pixel reads and w*h pixel + * writes. + * @function + * @name Enhance + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Enhance]); + * node.enhance(0.4); + */ + Konva.Filters.Enhance = function(imageData) { + var data = imageData.data, + nSubPixels = data.length, + rMin = data[0], + rMax = rMin, + r, + gMin = data[1], + gMax = gMin, + g, + bMin = data[2], + bMax = bMin, + b, + i; + + // If we are not enhancing anything - don't do any computation + var enhanceAmount = this.enhance(); + if (enhanceAmount === 0) { + return; + } + + // 1st Pass - find the min and max for each channel: + for (i = 0; i < nSubPixels; i += 4) { + r = data[i + 0]; + if (r < rMin) { + rMin = r; + } else if (r > rMax) { + rMax = r; + } + g = data[i + 1]; + if (g < gMin) { + gMin = g; + } else if (g > gMax) { + gMax = g; + } + b = data[i + 2]; + if (b < bMin) { + bMin = b; + } else if (b > bMax) { + bMax = b; + } + //a = data[i + 3]; + //if (a < aMin) { aMin = a; } else + //if (a > aMax) { aMax = a; } + } + + // If there is only 1 level - don't remap + if (rMax === rMin) { + rMax = 255; + rMin = 0; + } + if (gMax === gMin) { + gMax = 255; + gMin = 0; + } + if (bMax === bMin) { + bMax = 255; + bMin = 0; + } + + var rMid, + rGoalMax, + rGoalMin, + gMid, + gGoalMax, + gGoalMin, + bMid, + bGoalMax, + bGoalMin; + + // If the enhancement is positive - stretch the histogram + if (enhanceAmount > 0) { + rGoalMax = rMax + enhanceAmount * (255 - rMax); + rGoalMin = rMin - enhanceAmount * (rMin - 0); + gGoalMax = gMax + enhanceAmount * (255 - gMax); + gGoalMin = gMin - enhanceAmount * (gMin - 0); + bGoalMax = bMax + enhanceAmount * (255 - bMax); + bGoalMin = bMin - enhanceAmount * (bMin - 0); + // If the enhancement is negative - compress the histogram + } else { + rMid = (rMax + rMin) * 0.5; + rGoalMax = rMax + enhanceAmount * (rMax - rMid); + rGoalMin = rMin + enhanceAmount * (rMin - rMid); + gMid = (gMax + gMin) * 0.5; + gGoalMax = gMax + enhanceAmount * (gMax - gMid); + gGoalMin = gMin + enhanceAmount * (gMin - gMid); + bMid = (bMax + bMin) * 0.5; + bGoalMax = bMax + enhanceAmount * (bMax - bMid); + bGoalMin = bMin + enhanceAmount * (bMin - bMid); + } + + // Pass 2 - remap everything, except the alpha + for (i = 0; i < nSubPixels; i += 4) { + data[i + 0] = remap(data[i + 0], rMin, rMax, rGoalMin, rGoalMax); + data[i + 1] = remap(data[i + 1], gMin, gMax, gGoalMin, gGoalMax); + data[i + 2] = remap(data[i + 2], bMin, bMax, bGoalMin, bGoalMax); + //data[i + 3] = remap(data[i + 3], aMin, aMax, aGoalMin, aGoalMax); + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'enhance', + 0, + null, + Konva.Factory.afterSetFilter + ); + + /** + * get/set enhance. Use with {@link Konva.Filters.Enhance} filter. + * @name enhance + * @method + * @memberof Konva.Node.prototype + * @param {Float} amount + * @returns {Float} + */ +})(); + +(function() { + 'use strict'; + /** + * Posterize Filter. Adjusts the channels so that there are no more + * than n different values for that channel. This is also applied + * to the alpha channel. + * @function + * @name Posterize + * @author ippo615 + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Posterize]); + * node.levels(0.8); // between 0 and 1 + */ + + Konva.Filters.Posterize = function(imageData) { + // level must be between 1 and 255 + var levels = Math.round(this.levels() * 254) + 1, + data = imageData.data, + len = data.length, + scale = 255 / levels, + i; + + for (i = 0; i < len; i += 1) { + data[i] = Math.floor(data[i] / scale) * scale; + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'levels', + 0.5, + null, + Konva.Factory.afterSetFilter + ); + + /** + * get/set levels. Must be a number between 0 and 1. Use with {@link Konva.Filters.Posterize} filter. + * @name levels + * @method + * @memberof Konva.Node.prototype + * @param {Number} level between 0 and 1 + * @returns {Number} + */ +})(); + +(function() { + 'use strict'; + /** + * Noise Filter. Randomly adds or substracts to the color channels + * @function + * @name Noise + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Noise]); + * node.noise(0.8); + */ + Konva.Filters.Noise = function(imageData) { + var amount = this.noise() * 255, + data = imageData.data, + nPixels = data.length, + half = amount / 2, + i; + + for (i = 0; i < nPixels; i += 4) { + data[i + 0] += half - 2 * half * Math.random(); + data[i + 1] += half - 2 * half * Math.random(); + data[i + 2] += half - 2 * half * Math.random(); + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'noise', + 0.2, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set noise amount. Must be a value between 0 and 1. Use with {@link Konva.Filters.Noise} filter. + * @name noise + * @method + * @memberof Konva.Node.prototype + * @param {Number} noise + * @returns {Number} + */ +})(); + +/*eslint-disable max-depth */ +(function() { + 'use strict'; + /** + * Pixelate Filter. Averages groups of pixels and redraws + * them as larger pixels + * @function + * @name Pixelate + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Pixelate]); + * node.pixelSize(10); + */ + + Konva.Filters.Pixelate = function(imageData) { + var pixelSize = Math.ceil(this.pixelSize()), + width = imageData.width, + height = imageData.height, + x, + y, + i, + //pixelsPerBin = pixelSize * pixelSize, + red, + green, + blue, + alpha, + nBinsX = Math.ceil(width / pixelSize), + nBinsY = Math.ceil(height / pixelSize), + xBinStart, + xBinEnd, + yBinStart, + yBinEnd, + xBin, + yBin, + pixelsInBin; + imageData = imageData.data; + + if (pixelSize <= 0) { + Konva.Util.error('pixelSize value can not be <= 0'); + return; + } + + for (xBin = 0; xBin < nBinsX; xBin += 1) { + for (yBin = 0; yBin < nBinsY; yBin += 1) { + // Initialize the color accumlators to 0 + red = 0; + green = 0; + blue = 0; + alpha = 0; + + // Determine which pixels are included in this bin + xBinStart = xBin * pixelSize; + xBinEnd = xBinStart + pixelSize; + yBinStart = yBin * pixelSize; + yBinEnd = yBinStart + pixelSize; + + // Add all of the pixels to this bin! + pixelsInBin = 0; + for (x = xBinStart; x < xBinEnd; x += 1) { + if (x >= width) { + continue; + } + for (y = yBinStart; y < yBinEnd; y += 1) { + if (y >= height) { + continue; + } + i = (width * y + x) * 4; + red += imageData[i + 0]; + green += imageData[i + 1]; + blue += imageData[i + 2]; + alpha += imageData[i + 3]; + pixelsInBin += 1; + } + } + + // Make sure the channels are between 0-255 + red = red / pixelsInBin; + green = green / pixelsInBin; + blue = blue / pixelsInBin; + + // Draw this bin + for (x = xBinStart; x < xBinEnd; x += 1) { + if (x >= width) { + continue; + } + for (y = yBinStart; y < yBinEnd; y += 1) { + if (y >= height) { + continue; + } + i = (width * y + x) * 4; + imageData[i + 0] = red; + imageData[i + 1] = green; + imageData[i + 2] = blue; + imageData[i + 3] = alpha; + } + } + } + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'pixelSize', + 8, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set pixel size. Use with {@link Konva.Filters.Pixelate} filter. + * @name pixelSize + * @method + * @memberof Konva.Node.prototype + * @param {Integer} pixelSize + * @returns {Integer} + */ +})(); + +(function() { + 'use strict'; + /** + * Threshold Filter. Pushes any value above the mid point to + * the max and any value below the mid point to the min. + * This affects the alpha channel. + * @function + * @name Threshold + * @memberof Konva.Filters + * @param {Object} imageData + * @author ippo615 + * @example + * node.cache(); + * node.filters([Konva.Filters.Threshold]); + * node.threshold(0.1); + */ + + Konva.Filters.Threshold = function(imageData) { + var level = this.threshold() * 255, + data = imageData.data, + len = data.length, + i; + + for (i = 0; i < len; i += 1) { + data[i] = data[i] < level ? 0 : 255; + } + }; + + Konva.Factory.addGetterSetter( + Konva.Node, + 'threshold', + 0.5, + null, + Konva.Factory.afterSetFilter + ); + /** + * get/set threshold. Must be a value between 0 and 1. Use with {@link Konva.Filters.Threshold} or {@link Konva.Filters.Mask} filter. + * @name threshold + * @method + * @memberof Konva.Node.prototype + * @param {Number} threshold + * @returns {Number} + */ +})(); + +(function() { + 'use strict'; + /** + * Sepia Filter + * Based on: Pixastic Lib - Sepia filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * @function + * @name Sepia + * @memberof Konva.Filters + * @param {Object} imageData + * @author Jacob Seidelin + * @license MPL v1.1 [http://www.pixastic.com/lib/license.txt] + * @example + * node.cache(); + * node.filters([Konva.Filters.Sepia]); + */ + Konva.Filters.Sepia = function(imageData) { + var data = imageData.data, + w = imageData.width, + y = imageData.height, + w4 = w * 4, + offsetY, + x, + offset, + or, + og, + ob, + r, + g, + b; + + do { + offsetY = (y - 1) * w4; + x = w; + do { + offset = offsetY + (x - 1) * 4; + + or = data[offset]; + og = data[offset + 1]; + ob = data[offset + 2]; + + r = or * 0.393 + og * 0.769 + ob * 0.189; + g = or * 0.349 + og * 0.686 + ob * 0.168; + b = or * 0.272 + og * 0.534 + ob * 0.131; + + data[offset] = r > 255 ? 255 : r; + data[offset + 1] = g > 255 ? 255 : g; + data[offset + 2] = b > 255 ? 255 : b; + data[offset + 3] = data[offset + 3]; + } while (--x); + } while (--y); + }; +})(); + +(function() { + 'use strict'; + /** + * Solarize Filter + * Pixastic Lib - Solarize filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * License: [http://www.pixastic.com/lib/license.txt] + * @function + * @name Solarize + * @memberof Konva.Filters + * @param {Object} imageData + * @example + * node.cache(); + * node.filters([Konva.Filters.Solarize]); + */ + Konva.Filters.Solarize = function(imageData) { + var data = imageData.data, + w = imageData.width, + h = imageData.height, + w4 = w * 4, + y = h; + + do { + var offsetY = (y - 1) * w4; + var x = w; + do { + var offset = offsetY + (x - 1) * 4; + var r = data[offset]; + var g = data[offset + 1]; + var b = data[offset + 2]; + + if (r > 127) { + r = 255 - r; + } + if (g > 127) { + g = 255 - g; + } + if (b > 127) { + b = 255 - b; + } + + data[offset] = r; + data[offset + 1] = g; + data[offset + 2] = b; + } while (--x); + } while (--y); + }; +})(); + +(function() { + 'use strict'; + /* + * ToPolar Filter. Converts image data to polar coordinates. Performs + * w*h*4 pixel reads and w*h pixel writes. The r axis is placed along + * what would be the y axis and the theta axis along the x axis. + * @function + * @author ippo615 + * @memberof Konva.Filters + * @param {ImageData} src, the source image data (what will be transformed) + * @param {ImageData} dst, the destination image data (where it will be saved) + * @param {Object} opt + * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, + * default is in the middle + */ + + var ToPolar = function(src, dst, opt) { + var srcPixels = src.data, + dstPixels = dst.data, + xSize = src.width, + ySize = src.height, + xMid = opt.polarCenterX || xSize / 2, + yMid = opt.polarCenterY || ySize / 2, + i, + x, + y, + r = 0, + g = 0, + b = 0, + a = 0; + + // Find the largest radius + var rad, rMax = Math.sqrt(xMid * xMid + yMid * yMid); + x = xSize - xMid; + y = ySize - yMid; + rad = Math.sqrt(x * x + y * y); + rMax = rad > rMax ? rad : rMax; + + // We'll be uisng y as the radius, and x as the angle (theta=t) + var rSize = ySize, tSize = xSize, radius, theta; + + // We want to cover all angles (0-360) and we need to convert to + // radians (*PI/180) + var conversion = 360 / tSize * Math.PI / 180, sin, cos; + + // var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale; + + for (theta = 0; theta < tSize; theta += 1) { + sin = Math.sin(theta * conversion); + cos = Math.cos(theta * conversion); + for (radius = 0; radius < rSize; radius += 1) { + x = Math.floor(xMid + rMax * radius / rSize * cos); + y = Math.floor(yMid + rMax * radius / rSize * sin); + i = (y * xSize + x) * 4; + r = srcPixels[i + 0]; + g = srcPixels[i + 1]; + b = srcPixels[i + 2]; + a = srcPixels[i + 3]; + + // Store it + //i = (theta * xSize + radius) * 4; + i = (theta + radius * xSize) * 4; + dstPixels[i + 0] = r; + dstPixels[i + 1] = g; + dstPixels[i + 2] = b; + dstPixels[i + 3] = a; + } + } + }; + + /* + * FromPolar Filter. Converts image data from polar coordinates back to rectangular. + * Performs w*h*4 pixel reads and w*h pixel writes. + * @function + * @author ippo615 + * @memberof Konva.Filters + * @param {ImageData} src, the source image data (what will be transformed) + * @param {ImageData} dst, the destination image data (where it will be saved) + * @param {Object} opt + * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarRotation] amount to rotate the image counterclockwis, + * 0 is no rotation, 360 degrees is a full rotation + */ + + var FromPolar = function(src, dst, opt) { + var srcPixels = src.data, + dstPixels = dst.data, + xSize = src.width, + ySize = src.height, + xMid = opt.polarCenterX || xSize / 2, + yMid = opt.polarCenterY || ySize / 2, + i, + x, + y, + dx, + dy, + r = 0, + g = 0, + b = 0, + a = 0; + + // Find the largest radius + var rad, rMax = Math.sqrt(xMid * xMid + yMid * yMid); + x = xSize - xMid; + y = ySize - yMid; + rad = Math.sqrt(x * x + y * y); + rMax = rad > rMax ? rad : rMax; + + // We'll be uisng x as the radius, and y as the angle (theta=t) + var rSize = ySize, + tSize = xSize, + radius, + theta, + phaseShift = opt.polarRotation || 0; + + // We need to convert to degrees and we need to make sure + // it's between (0-360) + // var conversion = tSize/360*180/Math.PI; + //var conversion = tSize/360*180/Math.PI; + + var x1, y1; + + for (x = 0; x < xSize; x += 1) { + for (y = 0; y < ySize; y += 1) { + dx = x - xMid; + dy = y - yMid; + radius = Math.sqrt(dx * dx + dy * dy) * rSize / rMax; + theta = (Math.atan2(dy, dx) * 180 / Math.PI + 360 + phaseShift) % 360; + theta = theta * tSize / 360; + x1 = Math.floor(theta); + y1 = Math.floor(radius); + i = (y1 * xSize + x1) * 4; + r = srcPixels[i + 0]; + g = srcPixels[i + 1]; + b = srcPixels[i + 2]; + a = srcPixels[i + 3]; + + // Store it + i = (y * xSize + x) * 4; + dstPixels[i + 0] = r; + dstPixels[i + 1] = g; + dstPixels[i + 2] = b; + dstPixels[i + 3] = a; + } + } + }; + + //Konva.Filters.ToPolar = Konva.Util._FilterWrapDoubleBuffer(ToPolar); + //Konva.Filters.FromPolar = Konva.Util._FilterWrapDoubleBuffer(FromPolar); + + // create a temporary canvas for working - shared between multiple calls + var tempCanvas = Konva.Util.createCanvasElement(); + + /* + * Kaleidoscope Filter. + * @function + * @name Kaleidoscope + * @author ippo615 + * @memberof Konva.Filters + * @example + * node.cache(); + * node.filters([Konva.Filters.Kaleidoscope]); + * node.kaleidoscopePower(3); + * node.kaleidoscopeAngle(45); + */ + Konva.Filters.Kaleidoscope = function(imageData) { + var xSize = imageData.width, ySize = imageData.height; + + var x, y, xoff, i, r, g, b, a, srcPos, dstPos; + var power = Math.round(this.kaleidoscopePower()); + var angle = Math.round(this.kaleidoscopeAngle()); + var offset = Math.floor(xSize * (angle % 360) / 360); + + if (power < 1) { + return; + } + + // Work with our shared buffer canvas + tempCanvas.width = xSize; + tempCanvas.height = ySize; + var scratchData = tempCanvas + .getContext('2d') + .getImageData(0, 0, xSize, ySize); + + // Convert thhe original to polar coordinates + ToPolar(imageData, scratchData, { + polarCenterX: xSize / 2, + polarCenterY: ySize / 2 + }); + + // Determine how big each section will be, if it's too small + // make it bigger + var minSectionSize = xSize / Math.pow(2, power); + while (minSectionSize <= 8) { + minSectionSize = minSectionSize * 2; + power -= 1; + } + minSectionSize = Math.ceil(minSectionSize); + var sectionSize = minSectionSize; + + // Copy the offset region to 0 + // Depending on the size of filter and location of the offset we may need + // to copy the section backwards to prevent it from rewriting itself + var xStart = 0, xEnd = sectionSize, xDelta = 1; + if (offset + minSectionSize > xSize) { + xStart = sectionSize; + xEnd = 0; + xDelta = -1; + } + for (y = 0; y < ySize; y += 1) { + for (x = xStart; x !== xEnd; x += xDelta) { + xoff = Math.round(x + offset) % xSize; + srcPos = (xSize * y + xoff) * 4; + r = scratchData.data[srcPos + 0]; + g = scratchData.data[srcPos + 1]; + b = scratchData.data[srcPos + 2]; + a = scratchData.data[srcPos + 3]; + dstPos = (xSize * y + x) * 4; + scratchData.data[dstPos + 0] = r; + scratchData.data[dstPos + 1] = g; + scratchData.data[dstPos + 2] = b; + scratchData.data[dstPos + 3] = a; + } + } + + // Perform the actual effect + for (y = 0; y < ySize; y += 1) { + sectionSize = Math.floor(minSectionSize); + for (i = 0; i < power; i += 1) { + for (x = 0; x < sectionSize + 1; x += 1) { + srcPos = (xSize * y + x) * 4; + r = scratchData.data[srcPos + 0]; + g = scratchData.data[srcPos + 1]; + b = scratchData.data[srcPos + 2]; + a = scratchData.data[srcPos + 3]; + dstPos = (xSize * y + sectionSize * 2 - x - 1) * 4; + scratchData.data[dstPos + 0] = r; + scratchData.data[dstPos + 1] = g; + scratchData.data[dstPos + 2] = b; + scratchData.data[dstPos + 3] = a; + } + sectionSize *= 2; + } + } + + // Convert back from polar coordinates + FromPolar(scratchData, imageData, { polarRotation: 0 }); + }; + + /** + * get/set kaleidoscope power. Use with {@link Konva.Filters.Kaleidoscope} filter. + * @name kaleidoscopePower + * @method + * @memberof Konva.Node.prototype + * @param {Integer} power of kaleidoscope + * @returns {Integer} + */ + Konva.Factory.addGetterSetter( + Konva.Node, + 'kaleidoscopePower', + 2, + null, + Konva.Factory.afterSetFilter + ); + + /** + * get/set kaleidoscope angle. Use with {@link Konva.Filters.Kaleidoscope} filter. + * @name kaleidoscopeAngle + * @method + * @memberof Konva.Node.prototype + * @param {Integer} degrees + * @returns {Integer} + */ + Konva.Factory.addGetterSetter( + Konva.Node, + 'kaleidoscopeAngle', + 0, + null, + Konva.Factory.afterSetFilter + ); +})(); + +(function() { + 'use strict'; + /** + * Container constructor.  Containers are used to contain nodes or other containers + * @constructor + * @memberof Konva + * @augments Konva.Node + * @abstract + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * * @param {Object} [config.clip] set clip + * @param {Number} [config.clipX] set clip x + * @param {Number} [config.clipY] set clip y + * @param {Number} [config.clipWidth] set clip width + * @param {Number} [config.clipHeight] set clip height + * @param {Function} [config.clipFunc] set clip func + + */ + Konva.Container = function(config) { + this.__init(config); + }; + + Konva.Util.addMethods(Konva.Container, { + __init: function(config) { + this.children = new Konva.Collection(); + Konva.Node.call(this, config); + }, + /** + * returns a {@link Konva.Collection} of direct descendant nodes + * @method + * @memberof Konva.Container.prototype + * @param {Function} [filterFunc] filter function + * @returns {Konva.Collection} + * @example + * // get all children + * var children = layer.getChildren(); + * + * // get only circles + * var circles = layer.getChildren(function(node){ + * return node.getClassName() === 'Circle'; + * }); + */ + getChildren: function(filterFunc) { + if (!filterFunc) { + return this.children; + } + + var results = new Konva.Collection(); + this.children.each(function(child) { + if (filterFunc(child)) { + results.push(child); + } + }); + return results; + }, + /** + * determine if node has children + * @method + * @memberof Konva.Container.prototype + * @returns {Boolean} + */ + hasChildren: function() { + return this.getChildren().length > 0; + }, + /** + * remove all children + * @method + * @memberof Konva.Container.prototype + */ + removeChildren: function() { + var children = Konva.Collection.toCollection(this.children); + var child; + for (var i = 0; i < children.length; i++) { + child = children[i]; + // reset parent to prevent many _setChildrenIndices calls + delete child.parent; + child.index = 0; + child.remove(); + } + children = null; + this.children = new Konva.Collection(); + return this; + }, + /** + * destroy all children + * @method + * @memberof Konva.Container.prototype + */ + destroyChildren: function() { + var children = Konva.Collection.toCollection(this.children); + var child; + for (var i = 0; i < children.length; i++) { + child = children[i]; + // reset parent to prevent many _setChildrenIndices calls + delete child.parent; + child.index = 0; + child.destroy(); + } + children = null; + this.children = new Konva.Collection(); + return this; + }, + /** + * Add node or nodes to container. + * @method + * @memberof Konva.Container.prototype + * @param {...Konva.Node} child + * @returns {Container} + * @example + * layer.add(shape1, shape2, shape3); + */ + add: function(child) { + if (arguments.length > 1) { + for (var i = 0; i < arguments.length; i++) { + this.add(arguments[i]); + } + return this; + } + if (child.getParent()) { + child.moveTo(this); + return this; + } + var children = this.children; + this._validateAdd(child); + child.index = children.length; + child.parent = this; + children.push(child); + this._fire('add', { + child: child + }); + + // if node under drag we need to update drag animation + if (Konva.DD && child.isDragging()) { + Konva.DD.anim.setLayers(child.getLayer()); + } + + // chainable + return this; + }, + destroy: function() { + // destroy children + if (this.hasChildren()) { + this.destroyChildren(); + } + // then destroy self + Konva.Node.prototype.destroy.call(this); + return this; + }, + /** + * return a {@link Konva.Collection} of nodes that match the selector. Use '#' for id selections + * and '.' for name selections. You can also select by type or class name. Pass multiple selectors + * separated by a space. + * @method + * @memberof Konva.Container.prototype + * @param {String} selector + * @returns {Collection} + * @example + * // select node with id foo + * var node = stage.find('#foo'); + * + * // select nodes with name bar inside layer + * var nodes = layer.find('.bar'); + * + * // select all groups inside layer + * var nodes = layer.find('Group'); + * + * // select all rectangles inside layer + * var nodes = layer.find('Rect'); + * + * // select node with an id of foo or a name of bar inside layer + * var nodes = layer.find('#foo, .bar'); + */ + find: function(selector) { + var retArr = [], + selectorArr = selector.replace(/ /g, '').split(','), + len = selectorArr.length, + n, + i, + sel, + arr, + node, + children, + clen; + + for (n = 0; n < len; n++) { + sel = selectorArr[n]; + if (!Konva.Util.isValidSelector(sel)) { + Konva.Util.warn( + 'Selector "' + + sel + + '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' + ); + Konva.Util.warn( + 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' + ); + Konva.Util.warn('Konva is awesome, right?'); + } + // id selector + if (sel.charAt(0) === '#') { + node = this._getNodeById(sel.slice(1)); + if (node) { + retArr.push(node); + } + } else if (sel.charAt(0) === '.') { + // name selector + arr = this._getNodesByName(sel.slice(1)); + retArr = retArr.concat(arr); + } else { + // unrecognized selector, pass to children + children = this.getChildren(); + clen = children.length; + for (i = 0; i < clen; i++) { + retArr = retArr.concat(children[i]._get(sel)); + } + } + } + + return Konva.Collection.toCollection(retArr); + }, + /** + * return a first node from `find` method + * @method + * @memberof Konva.Container.prototype + * @param {String} selector + * @returns {Konva.Node} + * @example + * // select node with id foo + * var node = stage.findOne('#foo'); + * + * // select node with name bar inside layer + * var nodes = layer.findOne('.bar'); + */ + findOne: function(selector) { + return this.find(selector)[0]; + }, + _getNodeById: function(key) { + var node = Konva.ids[key]; + + if (node !== undefined && this.isAncestorOf(node)) { + return node; + } + return null; + }, + _getNodesByName: function(key) { + var arr = Konva.names[key] || []; + return this._getDescendants(arr); + }, + _get: function(selector) { + var retArr = Konva.Node.prototype._get.call(this, selector); + var children = this.getChildren(); + var len = children.length; + for (var n = 0; n < len; n++) { + retArr = retArr.concat(children[n]._get(selector)); + } + return retArr; + }, + // extenders + toObject: function() { + var obj = Konva.Node.prototype.toObject.call(this); + + obj.children = []; + + var children = this.getChildren(); + var len = children.length; + for (var n = 0; n < len; n++) { + var child = children[n]; + obj.children.push(child.toObject()); + } + + return obj; + }, + _getDescendants: function(arr) { + var retArr = []; + var len = arr.length; + for (var n = 0; n < len; n++) { + var node = arr[n]; + if (this.isAncestorOf(node)) { + retArr.push(node); + } + } + + return retArr; + }, + /** + * determine if node is an ancestor + * of descendant + * @method + * @memberof Konva.Container.prototype + * @param {Konva.Node} node + */ + isAncestorOf: function(node) { + var parent = node.getParent(); + while (parent) { + if (parent._id === this._id) { + return true; + } + parent = parent.getParent(); + } + + return false; + }, + clone: function(obj) { + // call super method + var node = Konva.Node.prototype.clone.call(this, obj); + + this.getChildren().each(function(no) { + node.add(no.clone()); + }); + return node; + }, + /** + * get all shapes that intersect a point. Note: because this method must clear a temporary + * canvas and redraw every shape inside the container, it should only be used for special sitations + * because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible + * because it performs much better + * @method + * @memberof Konva.Container.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Array} array of shapes + */ + getAllIntersections: function(pos) { + var arr = []; + + this.find('Shape').each(function(shape) { + if (shape.isVisible() && shape.intersects(pos)) { + arr.push(shape); + } + }); + + return arr; + }, + _setChildrenIndices: function() { + this.children.each(function(child, n) { + child.index = n; + }); + }, + drawScene: function(can, top, caching) { + var layer = this.getLayer(), + canvas = can || (layer && layer.getCanvas()), + context = canvas && canvas.getContext(), + cachedCanvas = this._cache.canvas, + cachedSceneCanvas = cachedCanvas && cachedCanvas.scene; + + if (this.isVisible()) { + if (!caching && cachedSceneCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedSceneCanvas(context); + context.restore(); + } else { + this._drawChildren(canvas, 'drawScene', top, false, caching); + } + } + return this; + }, + drawHit: function(can, top, caching) { + var layer = this.getLayer(), + canvas = can || (layer && layer.hitCanvas), + context = canvas && canvas.getContext(), + cachedCanvas = this._cache.canvas, + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if (this.shouldDrawHit(canvas)) { + if (layer) { + layer.clearHitCache(); + } + if (!caching && cachedHitCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedHitCanvas(context); + context.restore(); + } else { + this._drawChildren(canvas, 'drawHit', top); + } + } + return this; + }, + _drawChildren: function(canvas, drawMethod, top, caching, skipBuffer) { + var layer = this.getLayer(), + context = canvas && canvas.getContext(), + clipWidth = this.getClipWidth(), + clipHeight = this.getClipHeight(), + clipFunc = this.getClipFunc(), + hasClip = (clipWidth && clipHeight) || clipFunc, + clipX, + clipY; + + if (hasClip && layer) { + context.save(); + var transform = this.getAbsoluteTransform(top); + var m = transform.getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + context.beginPath(); + if (clipFunc) { + clipFunc.call(this, context, this); + } else { + clipX = this.getClipX(); + clipY = this.getClipY(); + context.rect(clipX, clipY, clipWidth, clipHeight); + } + context.clip(); + m = transform.copy().invert().getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + + this.children.each(function(child) { + child[drawMethod](canvas, top, caching, skipBuffer); + }); + + if (hasClip) { + context.restore(); + } + }, + shouldDrawHit: function(canvas) { + var layer = this.getLayer(); + var dd = Konva.DD; + var layerUnderDrag = dd && + Konva.isDragging() && + Konva.DD.anim.getLayers().indexOf(layer) !== -1; + return (canvas && canvas.isCache) || + (layer && + layer.hitGraphEnabled() && + this.isVisible() && + !layerUnderDrag); + }, + getClientRect: function(skipTransform) { + var minX, minY, maxX, maxY; + var selfRect = { + x: 0, + y: 0, + width: 0, + height: 0 + }; + this.children.each(function(child) { + var rect = child.getClientRect(); + + // skip invisible children (like empty groups) + // or don't skip... hmmm... + // if (rect.width === 0 && rect.height === 0) { + // return; + // } + + if (minX === undefined) { + // initial value for first child + minX = rect.x; + minY = rect.y; + maxX = rect.x + rect.width; + maxY = rect.y + rect.height; + } else { + minX = Math.min(minX, rect.x); + minY = Math.min(minY, rect.y); + maxX = Math.max(maxX, rect.x + rect.width); + maxY = Math.max(maxY, rect.y + rect.height); + } + }); + + if (this.children.length !== 0) { + selfRect = { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY + }; + } + + if (!skipTransform) { + return this._transformedRect(selfRect); + } + return selfRect; + } + }); + + Konva.Util.extend(Konva.Container, Konva.Node); + // deprecated methods + Konva.Container.prototype.get = Konva.Container.prototype.find; + + // add getters setters + Konva.Factory.addComponentsGetterSetter(Konva.Container, 'clip', [ + 'x', + 'y', + 'width', + 'height' + ]); + /** + * get/set clip + * @method + * @name clip + * @memberof Konva.Container.prototype + * @param {Object} clip + * @param {Number} clip.x + * @param {Number} clip.y + * @param {Number} clip.width + * @param {Number} clip.height + * @returns {Object} + * @example + * // get clip + * var clip = container.clip(); + * + * // set clip + * container.setClip({ + * x: 20, + * y: 20, + * width: 20, + * height: 20 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Container, 'clipX'); + /** + * get/set clip x + * @name clipX + * @method + * @memberof Konva.Container.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get clip x + * var clipX = container.clipX(); + * + * // set clip x + * container.clipX(10); + */ + + Konva.Factory.addGetterSetter(Konva.Container, 'clipY'); + /** + * get/set clip y + * @name clipY + * @method + * @memberof Konva.Container.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get clip y + * var clipY = container.clipY(); + * + * // set clip y + * container.clipY(10); + */ + + Konva.Factory.addGetterSetter(Konva.Container, 'clipWidth'); + /** + * get/set clip width + * @name clipWidth + * @method + * @memberof Konva.Container.prototype + * @param {Number} width + * @returns {Number} + * @example + * // get clip width + * var clipWidth = container.clipWidth(); + * + * // set clip width + * container.clipWidth(100); + */ + + Konva.Factory.addGetterSetter(Konva.Container, 'clipHeight'); + /** + * get/set clip height + * @name clipHeight + * @method + * @memberof Konva.Container.prototype + * @param {Number} height + * @returns {Number} + * @example + * // get clip height + * var clipHeight = container.clipHeight(); + * + * // set clip height + * container.clipHeight(100); + */ + + Konva.Factory.addGetterSetter(Konva.Container, 'clipFunc'); + /** + * get/set clip function + * @name clipFunc + * @method + * @memberof Konva.Container.prototype + * @param {Function} function + * @returns {Function} + * @example + * // get clip function + * var clipFunction = container.clipFunc(); + * + * // set clip height + * container.clipFunc(function(ctx) { + * ctx.rect(0, 0, 100, 100); + * }); + */ + + Konva.Collection.mapMethods(Konva.Container); +})(); + +(function(Konva) { + 'use strict'; + var HAS_SHADOW = 'hasShadow'; + var SHADOW_RGBA = 'shadowRGBA'; + + function _fillFunc(context) { + context.fill(); + } + function _strokeFunc(context) { + context.stroke(); + } + function _fillFuncHit(context) { + context.fill(); + } + function _strokeFuncHit(context) { + context.stroke(); + } + + function _clearHasShadowCache() { + this._clearCache(HAS_SHADOW); + } + + function _clearGetShadowRGBACache() { + this._clearCache(SHADOW_RGBA); + } + + /** + * Shape constructor. Shapes are primitive objects such as rectangles, + * circles, text, lines, etc. + * @constructor + * @memberof Konva + * @augments Konva.Node + * @param {Object} config + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var customShape = new Konva.Shape({ + * x: 5, + * y: 10, + * fill: 'red', + * // a Konva.Canvas renderer is passed into the drawFunc function + * drawFunc: function(context) { + * context.beginPath(); + * context.moveTo(200, 50); + * context.lineTo(420, 80); + * context.quadraticCurveTo(300, 100, 260, 170); + * context.closePath(); + * context.fillStrokeShape(this); + * } + *}); + */ + Konva.Shape = function(config) { + this.__init(config); + }; + + Konva.Util.addMethods(Konva.Shape, { + __init: function(config) { + this.nodeType = 'Shape'; + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this._fillFuncHit = _fillFuncHit; + this._strokeFuncHit = _strokeFuncHit; + + // set colorKey + var shapes = Konva.shapes; + var key; + + while (true) { + key = Konva.Util.getRandomColor(); + if (key && !(key in shapes)) { + break; + } + } + + this.colorKey = key; + shapes[key] = this; + + // call super constructor + Konva.Node.call(this, config); + + this.on( + 'shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearHasShadowCache + ); + + this.on( + 'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearGetShadowRGBACache + ); + }, + hasChildren: function() { + return false; + }, + getChildren: function() { + return []; + }, + /** + * get canvas context tied to the layer + * @method + * @memberof Konva.Shape.prototype + * @returns {Konva.Context} + */ + getContext: function() { + return this.getLayer().getContext(); + }, + /** + * get canvas renderer tied to the layer. Note that this returns a canvas renderer, not a canvas element + * @method + * @memberof Konva.Shape.prototype + * @returns {Konva.Canvas} + */ + getCanvas: function() { + return this.getLayer().getCanvas(); + }, + /** + * returns whether or not a shadow will be rendered + * @method + * @memberof Konva.Shape.prototype + * @returns {Boolean} + */ + hasShadow: function() { + return this._getCache(HAS_SHADOW, this._hasShadow); + }, + _hasShadow: function() { + return this.getShadowEnabled() && + (this.getShadowOpacity() !== 0 && + !!(this.getShadowColor() || + this.getShadowBlur() || + this.getShadowOffsetX() || + this.getShadowOffsetY())); + }, + getShadowRGBA: function() { + return this._getCache(SHADOW_RGBA, this._getShadowRGBA); + }, + _getShadowRGBA: function() { + if (this.hasShadow()) { + var rgba = Konva.Util.colorToRGBA(this.shadowColor()); + return 'rgba(' + + rgba.r + + ',' + + rgba.g + + ',' + + rgba.b + + ',' + + rgba.a * (this.getShadowOpacity() || 1) + + ')'; + } + }, + /** + * returns whether or not the shape will be filled + * @method + * @memberof Konva.Shape.prototype + * @returns {Boolean} + */ + hasFill: function() { + return !!(this.getFill() || + this.getFillPatternImage() || + this.getFillLinearGradientColorStops() || + this.getFillRadialGradientColorStops()); + }, + /** + * returns whether or not the shape will be stroked + * @method + * @memberof Konva.Shape.prototype + * @returns {Boolean} + */ + hasStroke: function() { + return this.strokeEnabled() && !!this.stroke(); + }, + /** + * determines if point is in the shape, regardless if other shapes are on top of it. Note: because + * this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times + * consecutively. Please use the {@link Konva.Stage#getIntersection} method if at all possible + * because it performs much better + * @method + * @memberof Konva.Shape.prototype + * @param {Object} point + * @param {Number} point.x + * @param {Number} point.y + * @returns {Boolean} + */ + intersects: function(point) { + var stage = this.getStage(), bufferHitCanvas = stage.bufferHitCanvas, p; + + bufferHitCanvas.getContext().clear(); + this.drawScene(bufferHitCanvas); + p = bufferHitCanvas.context.getImageData( + Math.round(point.x), + Math.round(point.y), + 1, + 1 + ).data; + return p[3] > 0; + }, + // extends Node.prototype.destroy + destroy: function() { + Konva.Node.prototype.destroy.call(this); + delete Konva.shapes[this.colorKey]; + return this; + }, + _useBufferCanvas: function(caching) { + return (!caching && + (this.perfectDrawEnabled() && + this.getAbsoluteOpacity() !== 1 && + this.hasFill() && + this.hasStroke() && + this.getStage())) || + (this.perfectDrawEnabled() && + this.hasShadow() && + this.getAbsoluteOpacity() !== 1 && + this.hasFill() && + this.hasStroke() && + this.getStage()); + }, + /** + * return self rectangle (x, y, width, height) of shape. + * This method are not taken into account transformation and styles. + * @method + * @memberof Konva.Shape.prototype + * @returns {Object} rect with {x, y, width, height} properties + * @example + * + * rect.getSelfRect(); // return {x:0, y:0, width:rect.width(), height:rect.height()} + * circle.getSelfRect(); // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()} + * + */ + getSelfRect: function() { + var size = this.getSize(); + return { + x: this._centroid ? Math.round((-size.width) / 2) : 0, + y: this._centroid ? Math.round((-size.height) / 2) : 0, + width: size.width, + height: size.height + }; + }, + getClientRect: function(skipTransform) { + var fillRect = this.getSelfRect(); + + var strokeWidth = (this.hasStroke() && this.strokeWidth()) || 0; + var fillAndStrokeWidth = fillRect.width + strokeWidth; + var fillAndStrokeHeight = fillRect.height + strokeWidth; + + var shadowOffsetX = this.hasShadow() ? this.shadowOffsetX() : 0; + var shadowOffsetY = this.hasShadow() ? this.shadowOffsetY() : 0; + + var preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); + var preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); + + var blurRadius = (this.hasShadow() && this.shadowBlur()) || 0; + + var width = preWidth + blurRadius * 2; + var height = preHeight + blurRadius * 2; + + // if stroke, for example = 3 + // we need to set x to 1.5, but after Math.round it will be 2 + // as we have additional offset we need to increase width and height by 1 pixel + var roundingOffset = 0; + if (Math.round(strokeWidth / 2) !== strokeWidth / 2) { + roundingOffset = 1; + } + var rect = { + width: width + roundingOffset, + height: height + roundingOffset, + x: -Math.round(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetX, 0) + + fillRect.x, + y: -Math.round(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetY, 0) + + fillRect.y + }; + if (!skipTransform) { + return this._transformedRect(rect); + } + return rect; + }, + drawScene: function(can, top, caching, skipBuffer) { + var layer = this.getLayer(), + canvas = can || layer.getCanvas(), + context = canvas.getContext(), + cachedCanvas = this._cache.canvas, + drawFunc = this.sceneFunc(), + hasShadow = this.hasShadow(), + hasStroke = this.hasStroke(), + stage, + bufferCanvas, + bufferContext; + + if (!this.isVisible()) { + return this; + } + if (cachedCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedSceneCanvas(context); + context.restore(); + return this; + } + if (!drawFunc) { + return this; + } + context.save(); + // if buffer canvas is needed + if (this._useBufferCanvas(caching) && !skipBuffer) { + stage = this.getStage(); + bufferCanvas = stage.bufferCanvas; + bufferContext = bufferCanvas.getContext(); + bufferContext.clear(); + bufferContext.save(); + bufferContext._applyLineJoin(this); + // layer might be undefined if we are using cache before adding to layer + if (!caching) { + if (layer) { + layer._applyTransform(this, bufferContext, top); + } else { + var m = this.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + } + + drawFunc.call(this, bufferContext); + bufferContext.restore(); + + var ratio = bufferCanvas.pixelRatio; + if (hasShadow && !canvas.hitCanvas) { + context.save(); + context._applyShadow(this); + context._applyOpacity(this); + context.drawImage( + bufferCanvas._canvas, + 0, + 0, + bufferCanvas.width / ratio, + bufferCanvas.height / ratio + ); + context.restore(); + } else { + context._applyOpacity(this); + context.drawImage( + bufferCanvas._canvas, + 0, + 0, + bufferCanvas.width / ratio, + bufferCanvas.height / ratio + ); + } + } else { + // if buffer canvas is not needed + context._applyLineJoin(this); + // layer might be undefined if we are using cache before adding to layer + if (!caching) { + if (layer) { + layer._applyTransform(this, context, top); + } else { + var o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + } + } + + if (hasShadow && hasStroke && !canvas.hitCanvas) { + context.save(); + // apply shadow + if (!caching) { + context._applyOpacity(this); + } + context._applyShadow(this); + drawFunc.call(this, context); + context.restore(); + // if shape has stroke we need to redraw shape + // otherwise we will see a shadow under stroke (and over fill) + // but I think this is unexpected behavior + if (this.hasFill() && this.getShadowForStrokeEnabled()) { + drawFunc.call(this, context); + } + } else if (hasShadow && !canvas.hitCanvas) { + context.save(); + if (!caching) { + context._applyOpacity(this); + } + context._applyShadow(this); + drawFunc.call(this, context); + context.restore(); + } else { + if (!caching) { + context._applyOpacity(this); + } + drawFunc.call(this, context); + } + } + context.restore(); + return this; + }, + drawHit: function(can, top, caching) { + var layer = this.getLayer(), + canvas = can || layer.hitCanvas, + context = canvas.getContext(), + drawFunc = this.hitFunc() || this.sceneFunc(), + cachedCanvas = this._cache.canvas, + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if (!this.shouldDrawHit(canvas)) { + return this; + } + if (layer) { + layer.clearHitCache(); + } + if (cachedHitCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedHitCanvas(context); + context.restore(); + return this; + } + if (!drawFunc) { + return this; + } + context.save(); + context._applyLineJoin(this); + if (!caching) { + if (layer) { + layer._applyTransform(this, context, top); + } else { + var o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + } + } + drawFunc.call(this, context); + context.restore(); + return this; + }, + /** + * draw hit graph using the cached scene canvas + * @method + * @memberof Konva.Shape.prototype + * @param {Integer} alphaThreshold alpha channel threshold that determines whether or not + * a pixel should be drawn onto the hit graph. Must be a value between 0 and 255. + * The default is 0 + * @returns {Konva.Shape} + * @example + * shape.cache(); + * shape.drawHitFromCache(); + */ + drawHitFromCache: function(alphaThreshold) { + var threshold = alphaThreshold || 0, + cachedCanvas = this._cache.canvas, + sceneCanvas = this._getCachedSceneCanvas(), + hitCanvas = cachedCanvas.hit, + hitContext = hitCanvas.getContext(), + hitWidth = hitCanvas.getWidth(), + hitHeight = hitCanvas.getHeight(), + hitImageData, + hitData, + len, + rgbColorKey, + i, + alpha; + + hitContext.clear(); + hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); + + try { + hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); + hitData = hitImageData.data; + len = hitData.length; + rgbColorKey = Konva.Util._hexToRgb(this.colorKey); + + // replace non transparent pixels with color key + for (i = 0; i < len; i += 4) { + alpha = hitData[i + 3]; + if (alpha > threshold) { + hitData[i] = rgbColorKey.r; + hitData[i + 1] = rgbColorKey.g; + hitData[i + 2] = rgbColorKey.b; + hitData[i + 3] = 255; + } else { + hitData[i + 3] = 0; + } + } + hitContext.putImageData(hitImageData, 0, 0); + } catch (e) { + Konva.Util.error( + 'Unable to draw hit graph from cached scene canvas. ' + e.message + ); + } + + return this; + } + }); + Konva.Util.extend(Konva.Shape, Konva.Node); + + // add getters and setters + Konva.Factory.addGetterSetter(Konva.Shape, 'stroke'); + + /** + * get/set stroke color + * @name stroke + * @method + * @memberof Konva.Shape.prototype + * @param {String} color + * @returns {String} + * @example + * // get stroke color + * var stroke = shape.stroke(); + * + * // set stroke color with color string + * shape.stroke('green'); + * + * // set stroke color with hex + * shape.stroke('#00ff00'); + * + * // set stroke color with rgb + * shape.stroke('rgb(0,255,0)'); + * + * // set stroke color with rgba and make it 50% opaque + * shape.stroke('rgba(0,255,0,0.5'); + */ + + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeAlpha', + 1, + Konva.Validators.alphaComponent + ); + + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeWidth', 2); + + /** + * get/set stroke width + * @name strokeWidth + * @method + * @memberof Konva.Shape.prototype + * @param {Number} strokeWidth + * @returns {Number} + * @example + * // get stroke width + * var strokeWidth = shape.strokeWidth(); + * + * // set stroke width + * shape.strokeWidth(); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeHitEnabled', true); + + /** + * get/set strokeHitEnabled property. Useful for performance optimization. + * You may set `shape.strokeHitEnabled(false)`. In this case stroke will be no draw on hit canvas, so hit area + * of shape will be decreased (by lineWidth / 2). Remember that non closed line with `strokeHitEnabled = false` + * will be not drawn on hit canvas, that is mean line will no trigger pointer events (like mouseover) + * Default value is true + * @name strokeHitEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} strokeHitEnabled + * @returns {Boolean} + * @example + * // get strokeHitEnabled + * var strokeHitEnabled = shape.strokeHitEnabled(); + * + * // set strokeHitEnabled + * shape.strokeHitEnabled(); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'perfectDrawEnabled', true); + + /** + * get/set perfectDrawEnabled. If a shape has fill, stroke and opacity you may set `perfectDrawEnabled` to false to improve performance. + * See http://konvajs.github.io/docs/performance/Disable_Perfect_Draw.html for more information. + * Default value is true + * @name perfectDrawEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} perfectDrawEnabled + * @returns {Boolean} + * @example + * // get perfectDrawEnabled + * var perfectDrawEnabled = shape.perfectDrawEnabled(); + * + * // set perfectDrawEnabled + * shape.perfectDrawEnabled(); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowForStrokeEnabled', true); + + /** + * get/set shadowForStrokeEnabled. Useful for performance optimization. + * You may set `shape.shadowForStrokeEnabled(false)`. In this case stroke will be no draw shadow for stroke. + * Remember if you set `shadowForStrokeEnabled = false` for non closed line - that line with have no shadow!. + * Default value is true + * @name shadowForStrokeEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} shadowForStrokeEnabled + * @returns {Boolean} + * @example + * // get shadowForStrokeEnabled + * var shadowForStrokeEnabled = shape.shadowForStrokeEnabled(); + * + * // set shadowForStrokeEnabled + * shape.shadowForStrokeEnabled(); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'lineJoin'); + + /** + * get/set line join. Can be miter, round, or bevel. The + * default is miter + * @name lineJoin + * @method + * @memberof Konva.Shape.prototype + * @param {String} lineJoin + * @returns {String} + * @example + * // get line join + * var lineJoin = shape.lineJoin(); + * + * // set line join + * shape.lineJoin('round'); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'lineCap'); + + /** + * get/set line cap. Can be butt, round, or square + * @name lineCap + * @method + * @memberof Konva.Shape.prototype + * @param {String} lineCap + * @returns {String} + * @example + * // get line cap + * var lineCap = shape.lineCap(); + * + * // set line cap + * shape.lineCap('round'); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'sceneFunc'); + + /** + * get/set scene draw function + * @name sceneFunc + * @method + * @memberof Konva.Shape.prototype + * @param {Function} drawFunc drawing function + * @returns {Function} + * @example + * // get scene draw function + * var sceneFunc = shape.sceneFunc(); + * + * // set scene draw function + * shape.sceneFunc(function(context) { + * context.beginPath(); + * context.rect(0, 0, this.width(), this.height()); + * context.closePath(); + * context.fillStrokeShape(this); + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'hitFunc'); + + /** + * get/set hit draw function + * @name hitFunc + * @method + * @memberof Konva.Shape.prototype + * @param {Function} drawFunc drawing function + * @returns {Function} + * @example + * // get hit draw function + * var hitFunc = shape.hitFunc(); + * + * // set hit draw function + * shape.hitFunc(function(context) { + * context.beginPath(); + * context.rect(0, 0, this.width(), this.height()); + * context.closePath(); + * context.fillStrokeShape(this); + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'dash'); + + /** + * get/set dash array for stroke. + * @name dash + * @method + * @memberof Konva.Shape.prototype + * @param {Array} dash + * @returns {Array} + * @example + * // apply dashed stroke that is 10px long and 5 pixels apart + * line.dash([10, 5]); + * // apply dashed stroke that is made up of alternating dashed + * // lines that are 10px long and 20px apart, and dots that have + * // a radius of 5px and are 20px apart + * line.dash([10, 20, 0.001, 20]); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowColor'); + + /** + * get/set shadow color + * @name shadowColor + * @method + * @memberof Konva.Shape.prototype + * @param {String} color + * @returns {String} + * @example + * // get shadow color + * var shadow = shape.shadowColor(); + * + * // set shadow color with color string + * shape.shadowColor('green'); + * + * // set shadow color with hex + * shape.shadowColor('#00ff00'); + * + * // set shadow color with rgb + * shape.shadowColor('rgb(0,255,0)'); + * + * // set shadow color with rgba and make it 50% opaque + * shape.shadowColor('rgba(0,255,0,0.5'); + */ + + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowAlpha', + 1, + Konva.Validators.alphaComponent + ); + + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowBlur'); + + /** + * get/set shadow blur + * @name shadowBlur + * @method + * @memberof Konva.Shape.prototype + * @param {Number} blur + * @returns {Number} + * @example + * // get shadow blur + * var shadowBlur = shape.shadowBlur(); + * + * // set shadow blur + * shape.shadowBlur(10); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOpacity'); + + /** + * get/set shadow opacity. must be a value between 0 and 1 + * @name shadowOpacity + * @method + * @memberof Konva.Shape.prototype + * @param {Number} opacity + * @returns {Number} + * @example + * // get shadow opacity + * var shadowOpacity = shape.shadowOpacity(); + * + * // set shadow opacity + * shape.shadowOpacity(0.5); + */ + + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'shadowOffset', [ + 'x', + 'y' + ]); + + /** + * get/set shadow offset + * @name shadowOffset + * @method + * @memberof Konva.Shape.prototype + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get shadow offset + * var shadowOffset = shape.shadowOffset(); + * + * // set shadow offset + * shape.shadowOffset({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetX', 0); + + /** + * get/set shadow offset x + * @name shadowOffsetX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get shadow offset x + * var shadowOffsetX = shape.shadowOffsetX(); + * + * // set shadow offset x + * shape.shadowOffsetX(5); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetY', 0); + + /** + * get/set shadow offset y + * @name shadowOffsetY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get shadow offset y + * var shadowOffsetY = shape.shadowOffsetY(); + * + * // set shadow offset y + * shape.shadowOffsetY(5); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternImage'); + + /** + * get/set fill pattern image + * @name fillPatternImage + * @method + * @memberof Konva.Shape.prototype + * @param {Image} image object + * @returns {Image} + * @example + * // get fill pattern image + * var fillPatternImage = shape.fillPatternImage(); + * + * // set fill pattern image + * var imageObj = new Image(); + * imageObj.onload = function() { + * shape.fillPatternImage(imageObj); + * }; + * imageObj.src = 'path/to/image/jpg'; + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fill'); + + /** + * get/set fill color + * @name fill + * @method + * @memberof Konva.Shape.prototype + * @param {String} color + * @returns {String} + * @example + * // get fill color + * var fill = shape.fill(); + * + * // set fill color with color string + * shape.fill('green'); + * + * // set fill color with hex + * shape.fill('#00ff00'); + * + * // set fill color with rgb + * shape.fill('rgb(0,255,0)'); + * + * // set fill color with rgba and make it 50% opaque + * shape.fill('rgba(0,255,0,0.5'); + * + * // shape without fill + * shape.fill(null); + */ + + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillAlpha', + 1, + Konva.Validators.alphaComponent + ); + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternX', 0); + + /** + * get/set fill pattern x + * @name fillPatternX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern x + * var fillPatternX = shape.fillPatternX(); + * // set fill pattern x + * shape.fillPatternX(20); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternY', 0); + + /** + * get/set fill pattern y + * @name fillPatternY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern y + * var fillPatternY = shape.fillPatternY(); + * // set fill pattern y + * shape.fillPatternY(20); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientColorStops'); + + /** + * get/set fill linear gradient color stops + * @name fillLinearGradientColorStops + * @method + * @memberof Konva.Shape.prototype + * @param {Array} colorStops + * @returns {Array} colorStops + * @example + * // get fill linear gradient color stops + * var colorStops = shape.fillLinearGradientColorStops(); + * + * // create a linear gradient that starts with red, changes to blue + * // halfway through, and then changes to green + * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); + */ + + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartRadius', + 0 + ); + + /** + * get/set fill radial gradient start radius + * @name fillRadialGradientStartRadius + * @method + * @memberof Konva.Shape.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radial gradient start radius + * var startRadius = shape.fillRadialGradientStartRadius(); + * + * // set radial gradient start radius + * shape.fillRadialGradientStartRadius(0); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndRadius', 0); + + /** + * get/set fill radial gradient end radius + * @name fillRadialGradientEndRadius + * @method + * @memberof Konva.Shape.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radial gradient end radius + * var endRadius = shape.fillRadialGradientEndRadius(); + * + * // set radial gradient end radius + * shape.fillRadialGradientEndRadius(100); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientColorStops'); + + /** + * get/set fill radial gradient color stops + * @name fillRadialGradientColorStops + * @method + * @memberof Konva.Shape.prototype + * @param {Number} colorStops + * @returns {Array} + * @example + * // get fill radial gradient color stops + * var colorStops = shape.fillRadialGradientColorStops(); + * + * // create a radial gradient that starts with red, changes to blue + * // halfway through, and then changes to green + * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRepeat', 'repeat'); + + /** + * get/set fill pattern repeat. Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'. The default is 'repeat' + * @name fillPatternRepeat + * @method + * @memberof Konva.Shape.prototype + * @param {String} repeat + * @returns {String} + * @example + * // get fill pattern repeat + * var repeat = shape.fillPatternRepeat(); + * + * // repeat pattern in x direction only + * shape.fillPatternRepeat('repeat-x'); + * + * // do not repeat the pattern + * shape.fillPatternRepeat('no repeat'); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillEnabled', true); + + /** + * get/set fill enabled flag + * @name fillEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get fill enabled flag + * var fillEnabled = shape.fillEnabled(); + * + * // disable fill + * shape.fillEnabled(false); + * + * // enable fill + * shape.fillEnabled(true); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeEnabled', true); + + /** + * get/set stroke enabled flag + * @name strokeEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get stroke enabled flag + * var strokeEnabled = shape.strokeEnabled(); + * + * // disable stroke + * shape.strokeEnabled(false); + * + * // enable stroke + * shape.strokeEnabled(true); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowEnabled', true); + + /** + * get/set shadow enabled flag + * @name shadowEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get shadow enabled flag + * var shadowEnabled = shape.shadowEnabled(); + * + * // disable shadow + * shape.shadowEnabled(false); + * + * // enable shadow + * shape.shadowEnabled(true); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'dashEnabled', true); + + /** + * get/set dash enabled flag + * @name dashEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get dash enabled flag + * var dashEnabled = shape.dashEnabled(); + * + * // disable dash + * shape.dashEnabled(false); + * + * // enable dash + * shape.dashEnabled(true); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeScaleEnabled', true); + + /** + * get/set strokeScale enabled flag + * @name strokeScaleEnabled + * @method + * @memberof Konva.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get stroke scale enabled flag + * var strokeScaleEnabled = shape.strokeScaleEnabled(); + * + * // disable stroke scale + * shape.strokeScaleEnabled(false); + * + * // enable stroke scale + * shape.strokeScaleEnabled(true); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPriority', 'color'); + + /** + * get/set fill priority. can be color, pattern, linear-gradient, or radial-gradient. The default is color. + * This is handy if you want to toggle between different fill types. + * @name fillPriority + * @method + * @memberof Konva.Shape.prototype + * @param {String} priority + * @returns {String} + * @example + * // get fill priority + * var fillPriority = shape.fillPriority(); + * + * // set fill priority + * shape.fillPriority('linear-gradient'); + */ + + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternOffset', [ + 'x', + 'y' + ]); + + /** + * get/set fill pattern offset + * @name fillPatternOffset + * @method + * @memberof Konva.Shape.prototype + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get fill pattern offset + * var patternOffset = shape.fillPatternOffset(); + * + * // set fill pattern offset + * shape.fillPatternOffset({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetX', 0); + /** + * get/set fill pattern offset x + * @name fillPatternOffsetX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern offset x + * var patternOffsetX = shape.fillPatternOffsetX(); + * + * // set fill pattern offset x + * shape.fillPatternOffsetX(20); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetY', 0); + /** + * get/set fill pattern offset y + * @name fillPatternOffsetY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern offset y + * var patternOffsetY = shape.fillPatternOffsetY(); + * + * // set fill pattern offset y + * shape.fillPatternOffsetY(10); + */ + + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternScale', [ + 'x', + 'y' + ]); + + /** + * get/set fill pattern scale + * @name fillPatternScale + * @method + * @memberof Konva.Shape.prototype + * @param {Object} scale + * @param {Number} scale.x + * @param {Number} scale.y + * @returns {Object} + * @example + * // get fill pattern scale + * var patternScale = shape.fillPatternScale(); + * + * // set fill pattern scale + * shape.fillPatternScale({ + * x: 2 + * y: 2 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleX', 1); + /** + * get/set fill pattern scale x + * @name fillPatternScaleX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern scale x + * var patternScaleX = shape.fillPatternScaleX(); + * + * // set fill pattern scale x + * shape.fillPatternScaleX(2); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleY', 1); + /** + * get/set fill pattern scale y + * @name fillPatternScaleY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern scale y + * var patternScaleY = shape.fillPatternScaleY(); + * + * // set fill pattern scale y + * shape.fillPatternScaleY(2); + */ + + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPoint', + ['x', 'y'] + ); + + /** + * get/set fill linear gradient start point + * @name fillLinearGradientStartPoint + * @method + * @memberof Konva.Shape.prototype + * @param {Object} startPoint + * @param {Number} startPoint.x + * @param {Number} startPoint.y + * @returns {Object} + * @example + * // get fill linear gradient start point + * var startPoint = shape.fillLinearGradientStartPoint(); + * + * // set fill linear gradient start point + * shape.fillLinearGradientStartPoint({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPointX', + 0 + ); + /** + * get/set fill linear gradient start point x + * @name fillLinearGradientStartPointX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill linear gradient start point x + * var startPointX = shape.fillLinearGradientStartPointX(); + * + * // set fill linear gradient start point x + * shape.fillLinearGradientStartPointX(20); + */ + + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPointY', + 0 + ); + /** + * get/set fill linear gradient start point y + * @name fillLinearGradientStartPointY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill linear gradient start point y + * var startPointY = shape.fillLinearGradientStartPointY(); + * + * // set fill linear gradient start point y + * shape.fillLinearGradientStartPointY(20); + */ + + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillLinearGradientEndPoint', + ['x', 'y'] + ); + + /** + * get/set fill linear gradient end point + * @name fillLinearGradientEndPoint + * @method + * @memberof Konva.Shape.prototype + * @param {Object} endPoint + * @param {Number} endPoint.x + * @param {Number} endPoint.y + * @returns {Object} + * @example + * // get fill linear gradient end point + * var endPoint = shape.fillLinearGradientEndPoint(); + * + * // set fill linear gradient end point + * shape.fillLinearGradientEndPoint({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointX', 0); + /** + * get/set fill linear gradient end point x + * @name fillLinearGradientEndPointX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill linear gradient end point x + * var endPointX = shape.fillLinearGradientEndPointX(); + * + * // set fill linear gradient end point x + * shape.fillLinearGradientEndPointX(20); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointY', 0); + /** + * get/set fill linear gradient end point y + * @name fillLinearGradientEndPointY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill linear gradient end point y + * var endPointY = shape.fillLinearGradientEndPointY(); + * + * // set fill linear gradient end point y + * shape.fillLinearGradientEndPointY(20); + */ + + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPoint', + ['x', 'y'] + ); + + /** + * get/set fill radial gradient start point + * @name fillRadialGradientStartPoint + * @method + * @memberof Konva.Shape.prototype + * @param {Object} startPoint + * @param {Number} startPoint.x + * @param {Number} startPoint.y + * @returns {Object} + * @example + * // get fill radial gradient start point + * var startPoint = shape.fillRadialGradientStartPoint(); + * + * // set fill radial gradient start point + * shape.fillRadialGradientStartPoint({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPointX', + 0 + ); + /** + * get/set fill radial gradient start point x + * @name fillRadialGradientStartPointX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill radial gradient start point x + * var startPointX = shape.fillRadialGradientStartPointX(); + * + * // set fill radial gradient start point x + * shape.fillRadialGradientStartPointX(20); + */ + + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPointY', + 0 + ); + /** + * get/set fill radial gradient start point y + * @name fillRadialGradientStartPointY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill radial gradient start point y + * var startPointY = shape.fillRadialGradientStartPointY(); + * + * // set fill radial gradient start point y + * shape.fillRadialGradientStartPointY(20); + */ + + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillRadialGradientEndPoint', + ['x', 'y'] + ); + + /** + * get/set fill radial gradient end point + * @name fillRadialGradientEndPoint + * @method + * @memberof Konva.Shape.prototype + * @param {Object} endPoint + * @param {Number} endPoint.x + * @param {Number} endPoint.y + * @returns {Object} + * @example + * // get fill radial gradient end point + * var endPoint = shape.fillRadialGradientEndPoint(); + * + * // set fill radial gradient end point + * shape.fillRadialGradientEndPoint({ + * x: 20 + * y: 10 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointX', 0); + /** + * get/set fill radial gradient end point x + * @name fillRadialGradientEndPointX + * @method + * @memberof Konva.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill radial gradient end point x + * var endPointX = shape.fillRadialGradientEndPointX(); + * + * // set fill radial gradient end point x + * shape.fillRadialGradientEndPointX(20); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointY', 0); + /** + * get/set fill radial gradient end point y + * @name fillRadialGradientEndPointY + * @method + * @memberof Konva.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill radial gradient end point y + * var endPointY = shape.fillRadialGradientEndPointY(); + * + * // set fill radial gradient end point y + * shape.fillRadialGradientEndPointY(20); + */ + + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRotation', 0); + + /** + * get/set fill pattern rotation in degrees + * @name fillPatternRotation + * @method + * @memberof Konva.Shape.prototype + * @param {Number} rotation + * @returns {Konva.Shape} + * @example + * // get fill pattern rotation + * var patternRotation = shape.fillPatternRotation(); + * + * // set fill pattern rotation + * shape.fillPatternRotation(20); + */ + + Konva.Factory.backCompat(Konva.Shape, { + dashArray: 'dash', + getDashArray: 'getDash', + setDashArray: 'getDash', + + drawFunc: 'sceneFunc', + getDrawFunc: 'getSceneFunc', + setDrawFunc: 'setSceneFunc', + + drawHitFunc: 'hitFunc', + getDrawHitFunc: 'getHitFunc', + setDrawHitFunc: 'setHitFunc' + }); + + Konva.Collection.mapMethods(Konva.Shape); +})(Konva); + +(function() { + 'use strict'; + // CONSTANTS + var STAGE = 'Stage', + STRING = 'string', + PX = 'px', + MOUSEOUT = 'mouseout', + MOUSELEAVE = 'mouseleave', + MOUSEOVER = 'mouseover', + MOUSEENTER = 'mouseenter', + MOUSEMOVE = 'mousemove', + MOUSEDOWN = 'mousedown', + MOUSEUP = 'mouseup', + CONTEXTMENU = 'contextmenu', + CLICK = 'click', + DBL_CLICK = 'dblclick', + TOUCHSTART = 'touchstart', + TOUCHEND = 'touchend', + TAP = 'tap', + DBL_TAP = 'dbltap', + TOUCHMOVE = 'touchmove', + DOMMOUSESCROLL = 'DOMMouseScroll', + MOUSEWHEEL = 'mousewheel', + WHEEL = 'wheel', + CONTENT_MOUSEOUT = 'contentMouseout', + CONTENT_MOUSEOVER = 'contentMouseover', + CONTENT_MOUSEMOVE = 'contentMousemove', + CONTENT_MOUSEDOWN = 'contentMousedown', + CONTENT_MOUSEUP = 'contentMouseup', + CONTENT_CONTEXTMENU = 'contentContextmenu', + CONTENT_CLICK = 'contentClick', + CONTENT_DBL_CLICK = 'contentDblclick', + CONTENT_TOUCHSTART = 'contentTouchstart', + CONTENT_TOUCHEND = 'contentTouchend', + CONTENT_DBL_TAP = 'contentDbltap', + CONTENT_TAP = 'contentTap', + CONTENT_TOUCHMOVE = 'contentTouchmove', + CONTENT_WHEEL = 'contentWheel', + DIV = 'div', + RELATIVE = 'relative', + KONVA_CONTENT = 'konvajs-content', + SPACE = ' ', + UNDERSCORE = '_', + CONTAINER = 'container', + EMPTY_STRING = '', + EVENTS = [ + MOUSEDOWN, + MOUSEMOVE, + MOUSEUP, + MOUSEOUT, + TOUCHSTART, + TOUCHMOVE, + TOUCHEND, + MOUSEOVER, + DOMMOUSESCROLL, + MOUSEWHEEL, + WHEEL, + CONTEXTMENU + ], + // cached variables + eventsLength = EVENTS.length; + + function addEvent(ctx, eventName) { + ctx.content.addEventListener( + eventName, + function(evt) { + ctx[UNDERSCORE + eventName](evt); + }, + false + ); + } + + /** + * Stage constructor. A stage is used to contain multiple layers + * @constructor + * @memberof Konva + * @augments Konva.Container + * @param {Object} config + * @param {String|Element} config.container Container selector or DOM element + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var stage = new Konva.Stage({ + * width: 500, + * height: 800, + * container: 'containerId' // or "#containerId" or ".containerClass" + * }); + */ + Konva.Stage = function(config) { + this.___init(config); + }; + + Konva.Util.addMethods(Konva.Stage, { + ___init: function(config) { + this.nodeType = STAGE; + // call super constructor + Konva.Container.call(this, config); + this._id = Konva.idCounter++; + this._buildDOM(); + this._bindContentEvents(); + this._enableNestedTransforms = false; + Konva.stages.push(this); + }, + _validateAdd: function(child) { + if (child.getType() !== 'Layer') { + Konva.Util.throw('You may only add layers to the stage.'); + } + }, + /** + * set container dom element which contains the stage wrapper div element + * @method + * @memberof Konva.Stage.prototype + * @param {DomElement} container can pass in a dom element or id string + */ + setContainer: function(container) { + if (typeof container === STRING) { + if (container.charAt(0) === '.') { + var className = container.slice(1); + container = Konva.document.getElementsByClassName(className)[0]; + } else { + var id; + if (container.charAt(0) !== '#') { + id = container; + } else { + id = container.slice(1); + } + container = Konva.document.getElementById(id); + } + if (!container) { + throw 'Can not find container in document with id ' + id; + } + } + this._setAttr(CONTAINER, container); + return this; + }, + shouldDrawHit: function() { + return true; + }, + draw: function() { + Konva.Node.prototype.draw.call(this); + return this; + }, + /** + * draw layer scene graphs + * @name draw + * @method + * @memberof Konva.Stage.prototype + */ + + /** + * draw layer hit graphs + * @name drawHit + * @method + * @memberof Konva.Stage.prototype + */ + + /** + * set height + * @method + * @memberof Konva.Stage.prototype + * @param {Number} height + */ + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + this._resizeDOM(); + return this; + }, + /** + * set width + * @method + * @memberof Konva.Stage.prototype + * @param {Number} width + */ + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + this._resizeDOM(); + return this; + }, + /** + * clear all layers + * @method + * @memberof Konva.Stage.prototype + */ + clear: function() { + var layers = this.children, len = layers.length, n; + + for (n = 0; n < len; n++) { + layers[n].clear(); + } + return this; + }, + clone: function(obj) { + if (!obj) { + obj = {}; + } + obj.container = Konva.document.createElement(DIV); + return Konva.Container.prototype.clone.call(this, obj); + }, + /** + * destroy stage + * @method + * @memberof Konva.Stage.prototype + */ + destroy: function() { + var content = this.content; + Konva.Container.prototype.destroy.call(this); + + if (content && Konva.Util._isInDocument(content)) { + this.getContainer().removeChild(content); + } + var index = Konva.stages.indexOf(this); + if (index > -1) { + Konva.stages.splice(index, 1); + } + return this; + }, + /** + * get pointer position which can be a touch position or mouse position + * @method + * @memberof Konva.Stage.prototype + * @returns {Object} + */ + getPointerPosition: function() { + return this.pointerPos; + }, + getStage: function() { + return this; + }, + /** + * get stage content div element which has the + * the class name "konvajs-content" + * @method + * @memberof Konva.Stage.prototype + */ + getContent: function() { + return this.content; + }, + /** + * Creates a composite data URL + * @method + * @memberof Konva.Stage.prototype + * @param {Object} config + * @param {Function} [config.callback] function executed when the composite has completed. Deprecated as method is sync now. + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + */ + toDataURL: function(config) { + config = config || {}; + + var mimeType = config.mimeType || null, + quality = config.quality || null, + x = config.x || 0, + y = config.y || 0, + canvas = new Konva.SceneCanvas({ + width: config.width || this.getWidth(), + height: config.height || this.getHeight(), + pixelRatio: config.pixelRatio + }), + _context = canvas.getContext()._context, + layers = this.children; + + if (x || y) { + _context.translate((-1) * x, (-1) * y); + } + + layers.each(function(layer) { + var width = layer.getCanvas().getWidth(); + var height = layer.getCanvas().getHeight(); + var ratio = layer.getCanvas().getPixelRatio(); + _context.drawImage( + layer.getCanvas()._canvas, + 0, + 0, + width / ratio, + height / ratio + ); + }); + var src = canvas.toDataURL(mimeType, quality); + + if (config.callback) { + config.callback(src); + } + + return src; + }, + /** + * converts stage into an image. + * @method + * @memberof Konva.Stage.prototype + * @param {Object} config + * @param {Function} config.callback function executed when the composite has completed + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + */ + toImage: function(config) { + var cb = config.callback; + + config.callback = function(dataUrl) { + Konva.Util._getImage(dataUrl, function(img) { + cb(img); + }); + }; + this.toDataURL(config); + }, + /** + * get visible intersection shape. This is the preferred + * method for determining if a point intersects a shape or not + * @method + * @memberof Konva.Stage.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @param {String} [selector] + * @returns {Konva.Node} + * @example + * var shape = stage.getIntersection({x: 50, y: 50}); + * // or if you interested in shape parent: + * var group = stage.getIntersection({x: 50, y: 50}, 'Group'); + */ + getIntersection: function(pos, selector) { + var layers = this.getChildren(), + len = layers.length, + end = len - 1, + n, + shape; + + for (n = end; n >= 0; n--) { + shape = layers[n].getIntersection(pos, selector); + if (shape) { + return shape; + } + } + + return null; + }, + _resizeDOM: function() { + if (this.content) { + var width = this.getWidth(), + height = this.getHeight(), + layers = this.getChildren(), + len = layers.length, + n, + layer; + + // set content dimensions + this.content.style.width = width + PX; + this.content.style.height = height + PX; + + this.bufferCanvas.setSize(width, height); + this.bufferHitCanvas.setSize(width, height); + + // set layer dimensions + for (n = 0; n < len; n++) { + layer = layers[n]; + layer.setSize(width, height); + layer.batchDraw(); + } + } + }, + /** + * add layer or layers to stage + * @method + * @memberof Konva.Stage.prototype + * @param {...Konva.Layer} layer + * @example + * stage.add(layer1, layer2, layer3); + */ + add: function(layer) { + if (arguments.length > 1) { + for (var i = 0; i < arguments.length; i++) { + this.add(arguments[i]); + } + return this; + } + Konva.Container.prototype.add.call(this, layer); + layer._setCanvasSize(this.width(), this.height()); + + // draw layer and append canvas to container + layer.draw(); + this.content.appendChild(layer.canvas._canvas); + + // chainable + return this; + }, + getParent: function() { + return null; + }, + getLayer: function() { + return null; + }, + /** + * returns a {@link Konva.Collection} of layers + * @method + * @memberof Konva.Stage.prototype + */ + getLayers: function() { + return this.getChildren(); + }, + _bindContentEvents: function() { + for (var n = 0; n < eventsLength; n++) { + addEvent(this, EVENTS[n]); + } + }, + _mouseover: function(evt) { + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + this._fire(CONTENT_MOUSEOVER, { evt: evt }); + } + }, + _mouseout: function(evt) { + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var targetShape = this.targetShape; + + if (targetShape && !Konva.isDragging()) { + targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); + targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); + this.targetShape = null; + } + this.pointerPos = undefined; + + this._fire(CONTENT_MOUSEOUT, { evt: evt }); + } + }, + _mousemove: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchmove(evt); + } + // workaround fake mousemove event in chrome browser https://code.google.com/p/chromium/issues/detail?id=161464 + if ( + (typeof evt.movementX !== 'undefined' || + typeof evt.movementY !== 'undefined') && + evt.movementY === 0 && + evt.movementX === 0 + ) { + return null; + } + if (Konva.UA.mobile) { + return null; + } + this._setPointerPosition(evt); + var shape; + + if (!Konva.isDragging()) { + shape = this.getIntersection(this.getPointerPosition()); + if (shape && shape.isListening()) { + if ( + !Konva.isDragging() && + (!this.targetShape || this.targetShape._id !== shape._id) + ) { + if (this.targetShape) { + this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }, shape); + this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }, shape); + } + shape._fireAndBubble(MOUSEOVER, { evt: evt }, this.targetShape); + shape._fireAndBubble(MOUSEENTER, { evt: evt }, this.targetShape); + this.targetShape = shape; + } else { + shape._fireAndBubble(MOUSEMOVE, { evt: evt }); + } + } else { + /* + * if no shape was detected, clear target shape and try + * to run mouseout from previous target shape + */ + if (this.targetShape && !Konva.isDragging()) { + this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); + this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); + this.targetShape = null; + } + } + + // content event + this._fire(CONTENT_MOUSEMOVE, { evt: evt }); + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mousedown: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchstart(evt); + } + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Konva.listenClickTap = true; + + if (shape && shape.isListening()) { + this.clickStartShape = shape; + shape._fireAndBubble(MOUSEDOWN, { evt: evt }); + } + + // content event + this._fire(CONTENT_MOUSEDOWN, { evt: evt }); + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mouseup: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchend(evt); + } + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()), + clickStartShape = this.clickStartShape, + fireDblClick = false, + dd = Konva.DD; + + if (Konva.inDblClickWindow) { + fireDblClick = true; + Konva.inDblClickWindow = false; + } else if (!dd || !dd.justDragged) { + // don't set inDblClickWindow after dragging + Konva.inDblClickWindow = true; + } else if (dd) { + dd.justDragged = false; + } + + setTimeout( + function() { + Konva.inDblClickWindow = false; + }, + Konva.dblClickWindow + ); + + if (shape && shape.isListening()) { + shape._fireAndBubble(MOUSEUP, { evt: evt }); + + // detect if click or double click occurred + if ( + Konva.listenClickTap && + clickStartShape && + clickStartShape._id === shape._id + ) { + shape._fireAndBubble(CLICK, { evt: evt }); + + if (fireDblClick) { + shape._fireAndBubble(DBL_CLICK, { evt: evt }); + } + } + } + // content events + this._fire(CONTENT_MOUSEUP, { evt: evt }); + if (Konva.listenClickTap) { + this._fire(CONTENT_CLICK, { evt: evt }); + if (fireDblClick) { + this._fire(CONTENT_DBL_CLICK, { evt: evt }); + } + } + + Konva.listenClickTap = false; + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _contextmenu: function(evt) { + this._fire(CONTENT_CONTEXTMENU, { evt: evt }); + }, + _touchstart: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Konva.listenClickTap = true; + + if (shape && shape.isListening()) { + this.tapStartShape = shape; + shape._fireAndBubble(TOUCHSTART, { evt: evt }); + + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && shape.preventDefault() && evt.preventDefault + ) { + evt.preventDefault(); + } + } + // content event + this._fire(CONTENT_TOUCHSTART, { evt: evt }); + }, + _touchend: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()), + fireDblClick = false; + + if (Konva.inDblClickWindow) { + fireDblClick = true; + Konva.inDblClickWindow = false; + } else { + Konva.inDblClickWindow = true; + } + + setTimeout( + function() { + Konva.inDblClickWindow = false; + }, + Konva.dblClickWindow + ); + + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHEND, { evt: evt }); + + // detect if tap or double tap occurred + if ( + Konva.listenClickTap && + this.tapStartShape && + shape._id === this.tapStartShape._id + ) { + shape._fireAndBubble(TAP, { evt: evt }); + + if (fireDblClick) { + shape._fireAndBubble(DBL_TAP, { evt: evt }); + } + } + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && shape.preventDefault() && evt.preventDefault + ) { + evt.preventDefault(); + } + } + // content events + this._fire(CONTENT_TOUCHEND, { evt: evt }); + if (Konva.listenClickTap) { + this._fire(CONTENT_TAP, { evt: evt }); + if (fireDblClick) { + this._fire(CONTENT_DBL_TAP, { evt: evt }); + } + } + + Konva.listenClickTap = false; + }, + _touchmove: function(evt) { + this._setPointerPosition(evt); + var dd = Konva.DD, shape; + if (!Konva.isDragging()) { + shape = this.getIntersection(this.getPointerPosition()); + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHMOVE, { evt: evt }); + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && shape.preventDefault() && evt.preventDefault + ) { + evt.preventDefault(); + } + } + this._fire(CONTENT_TOUCHMOVE, { evt: evt }); + } + if (dd) { + if (Konva.isDragging() && Konva.DD.node.preventDefault()) { + evt.preventDefault(); + } + } + }, + _DOMMouseScroll: function(evt) { + this._mousewheel(evt); + }, + _mousewheel: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + if (shape && shape.isListening()) { + shape._fireAndBubble(WHEEL, { evt: evt }); + } + this._fire(CONTENT_WHEEL, { evt: evt }); + }, + _wheel: function(evt) { + this._mousewheel(evt); + }, + _setPointerPosition: function(evt) { + var contentPosition = this._getContentPosition(), x = null, y = null; + evt = evt ? evt : window.event; + + // touch events + if (evt.touches !== undefined) { + // currently, only handle one finger + if (evt.touches.length > 0) { + var touch = evt.touches[0]; + // get the information for finger #1 + x = touch.offsetX; + y = touch.offsetY; + } + } else { + // mouse events + x = evt.offsetX; + y = evt.offsetY; + } + if (x !== null && y !== null) { + this.pointerPos = { + x: x, + y: y + }; + } + }, + _getContentPosition: function() { + var rect = this.content.getBoundingClientRect + ? this.content.getBoundingClientRect() + : { top: 0, left: 0 }; + return { + top: rect.top, + left: rect.left + }; + }, + _buildDOM: function() { + var container = this.getContainer(); + if (!container) { + if (Konva.Util.isBrowser()) { + throw 'Stage has no container. A container is required.'; + } else { + // automatically create element for jsdom in nodejs env + container = Konva.document.createElement(DIV); + } + } + // clear content inside container + container.innerHTML = EMPTY_STRING; + + // content + this.content = Konva.document.createElement(DIV); + this.content.style.position = RELATIVE; + this.content.className = KONVA_CONTENT; + this.content.setAttribute('role', 'presentation'); + container.appendChild(this.content); + + // the buffer canvas pixel ratio must be 1 because it is used as an + // intermediate canvas before copying the result onto a scene canvas. + // not setting it to 1 will result in an over compensation + this.bufferCanvas = new Konva.SceneCanvas(); + this.bufferHitCanvas = new Konva.HitCanvas({ pixelRatio: 1 }); + + this._resizeDOM(); + }, + _onContent: function(typesStr, handler) { + var types = typesStr.split(SPACE), len = types.length, n, baseEvent; + + for (n = 0; n < len; n++) { + baseEvent = types[n]; + this.content.addEventListener(baseEvent, handler, false); + } + }, + // currently cache function is now working for stage, because stage has no its own canvas element + // TODO: may be it is better to cache all children layers? + cache: function() { + Konva.Util.warn( + 'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.' + ); + }, + clearCache: function() {} + }); + Konva.Util.extend(Konva.Stage, Konva.Container); + + // add getters and setters + Konva.Factory.addGetter(Konva.Stage, 'container'); + Konva.Factory.addOverloadedGetterSetter(Konva.Stage, 'container'); + + /** + * get container DOM element + * @name container + * @method + * @memberof Konva.Stage.prototype + * @returns {DomElement} container + * @example + * // get container + * var container = stage.container(); + * // set container + * var container = document.createElement('div'); + * body.appendChild(container); + * stage.container(container); + */ +})(); + +(function() { + 'use strict'; + /** + * BaseLayer constructor. + * @constructor + * @memberof Konva + * @augments Konva.Container + * @param {Object} config + * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want + * to clear the canvas before each layer draw. The default value is true. + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * * @param {Object} [config.clip] set clip + * @param {Number} [config.clipX] set clip x + * @param {Number} [config.clipY] set clip y + * @param {Number} [config.clipWidth] set clip width + * @param {Number} [config.clipHeight] set clip height + * @param {Function} [config.clipFunc] set clip func + + * @example + * var layer = new Konva.Layer(); + */ + Konva.BaseLayer = function(config) { + this.___init(config); + }; + + Konva.Util.addMethods(Konva.BaseLayer, { + ___init: function(config) { + this.nodeType = 'Layer'; + Konva.Container.call(this, config); + }, + createPNGStream: function() { + return this.canvas._canvas.createPNGStream(); + }, + /** + * get layer canvas + * @method + * @memberof Konva.BaseLayer.prototype + */ + getCanvas: function() { + return this.canvas; + }, + /** + * get layer hit canvas + * @method + * @memberof Konva.BaseLayer.prototype + */ + getHitCanvas: function() { + return this.hitCanvas; + }, + /** + * get layer canvas context + * @method + * @memberof Konva.BaseLayer.prototype + */ + getContext: function() { + return this.getCanvas().getContext(); + }, + /** + * clear scene and hit canvas contexts tied to the layer + * @method + * @memberof Konva.BaseLayer.prototype + * @param {Object} [bounds] + * @param {Number} [bounds.x] + * @param {Number} [bounds.y] + * @param {Number} [bounds.width] + * @param {Number} [bounds.height] + * @example + * layer.clear(); + * layer.clear({ + * x : 0, + * y : 0, + * width : 100, + * height : 100 + * }); + */ + clear: function(bounds) { + this.getContext().clear(bounds); + return this; + }, + clearHitCache: function() { + this._hitImageData = undefined; + }, + // extend Node.prototype.setZIndex + setZIndex: function(index) { + Konva.Node.prototype.setZIndex.call(this, index); + var stage = this.getStage(); + if (stage) { + stage.content.removeChild(this.getCanvas()._canvas); + + if (index < stage.getChildren().length - 1) { + stage.content.insertBefore( + this.getCanvas()._canvas, + stage.getChildren()[index + 1].getCanvas()._canvas + ); + } else { + stage.content.appendChild(this.getCanvas()._canvas); + } + } + return this; + }, + // extend Node.prototype.moveToTop + moveToTop: function() { + Konva.Node.prototype.moveToTop.call(this); + var stage = this.getStage(); + if (stage) { + stage.content.removeChild(this.getCanvas()._canvas); + stage.content.appendChild(this.getCanvas()._canvas); + } + return this; + }, + // extend Node.prototype.moveUp + moveUp: function() { + var moved = Konva.Node.prototype.moveUp.call(this); + if (!moved) { + return this; + } + var stage = this.getStage(); + if (!stage) { + return this; + } + stage.content.removeChild(this.getCanvas()._canvas); + + if (this.index < stage.getChildren().length - 1) { + stage.content.insertBefore( + this.getCanvas()._canvas, + stage.getChildren()[this.index + 1].getCanvas()._canvas + ); + } else { + stage.content.appendChild(this.getCanvas()._canvas); + } + return this; + }, + // extend Node.prototype.moveDown + moveDown: function() { + if (Konva.Node.prototype.moveDown.call(this)) { + var stage = this.getStage(); + if (stage) { + var children = stage.getChildren(); + stage.content.removeChild(this.getCanvas()._canvas); + stage.content.insertBefore( + this.getCanvas()._canvas, + children[this.index + 1].getCanvas()._canvas + ); + } + } + return this; + }, + // extend Node.prototype.moveToBottom + moveToBottom: function() { + if (Konva.Node.prototype.moveToBottom.call(this)) { + var stage = this.getStage(); + if (stage) { + var children = stage.getChildren(); + stage.content.removeChild(this.getCanvas()._canvas); + stage.content.insertBefore( + this.getCanvas()._canvas, + children[1].getCanvas()._canvas + ); + } + } + return this; + }, + getLayer: function() { + return this; + }, + remove: function() { + var _canvas = this.getCanvas()._canvas; + + Konva.Node.prototype.remove.call(this); + + if (_canvas && _canvas.parentNode && Konva.Util._isInDocument(_canvas)) { + _canvas.parentNode.removeChild(_canvas); + } + return this; + }, + getStage: function() { + return this.parent; + }, + setSize: function(width, height) { + this.canvas.setSize(width, height); + return this; + }, + /** + * get/set width of layer.getter return width of stage. setter doing nothing. + * if you want change width use `stage.width(value);` + * @name width + * @method + * @memberof Konva.BaseLayer.prototype + * @returns {Number} + * @example + * var width = layer.width(); + */ + getWidth: function() { + if (this.parent) { + return this.parent.getWidth(); + } + }, + setWidth: function() { + Konva.Util.warn( + 'Can not change width of layer. Use "stage.width(value)" function instead.' + ); + }, + /** + * get/set height of layer.getter return height of stage. setter doing nothing. + * if you want change height use `stage.height(value);` + * @name height + * @method + * @memberof Konva.BaseLayer.prototype + * @returns {Number} + * @example + * var height = layer.height(); + */ + getHeight: function() { + if (this.parent) { + return this.parent.getHeight(); + } + }, + setHeight: function() { + Konva.Util.warn( + 'Can not change height of layer. Use "stage.height(value)" function instead.' + ); + }, + // the apply transform method is handled by the Layer and FastLayer class + // because it is up to the layer to decide if an absolute or relative transform + // should be used + _applyTransform: function(shape, context, top) { + var m = shape.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + }); + Konva.Util.extend(Konva.BaseLayer, Konva.Container); + + // add getters and setters + Konva.Factory.addGetterSetter(Konva.BaseLayer, 'clearBeforeDraw', true); + /** + * get/set clearBeforeDraw flag which determines if the layer is cleared or not + * before drawing + * @name clearBeforeDraw + * @method + * @memberof Konva.BaseLayer.prototype + * @param {Boolean} clearBeforeDraw + * @returns {Boolean} + * @example + * // get clearBeforeDraw flag + * var clearBeforeDraw = layer.clearBeforeDraw(); + * + * // disable clear before draw + * layer.clearBeforeDraw(false); + * + * // enable clear before draw + * layer.clearBeforeDraw(true); + */ + + Konva.Collection.mapMethods(Konva.BaseLayer); +})(); + +(function() { + 'use strict'; + // constants + var HASH = '#', + BEFORE_DRAW = 'beforeDraw', + DRAW = 'draw', + /* + * 2 - 3 - 4 + * | | + * 1 - 0 5 + * | + * 8 - 7 - 6 + */ + INTERSECTION_OFFSETS = [ + { x: 0, y: 0 }, // 0 + { x: -1, y: 0 }, // 1 + { x: -1, y: -1 }, // 2 + { x: 0, y: -1 }, // 3 + { x: 1, y: -1 }, // 4 + { x: 1, y: 0 }, // 5 + { x: 1, y: 1 }, // 6 + { x: 0, y: 1 }, // 7 + { x: -1, y: 1 } // 8 + ], + INTERSECTION_OFFSETS_LEN = INTERSECTION_OFFSETS.length; + + /** + * Layer constructor. Layers are tied to their own canvas element and are used + * to contain groups or shapes. + * @constructor + * @memberof Konva + * @augments Konva.BaseLayer + * @param {Object} config + * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want + * to clear the canvas before each layer draw. The default value is true. + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * * @param {Object} [config.clip] set clip + * @param {Number} [config.clipX] set clip x + * @param {Number} [config.clipY] set clip y + * @param {Number} [config.clipWidth] set clip width + * @param {Number} [config.clipHeight] set clip height + * @param {Function} [config.clipFunc] set clip func + + * @example + * var layer = new Konva.Layer(); + */ + Konva.Layer = function(config) { + this.____init(config); + }; + + Konva.Util.addMethods(Konva.Layer, { + ____init: function(config) { + this.nodeType = 'Layer'; + this.canvas = new Konva.SceneCanvas(); + this.hitCanvas = new Konva.HitCanvas({ + pixelRatio: 1 + }); + // call super constructor + Konva.BaseLayer.call(this, config); + }, + _setCanvasSize: function(width, height) { + this.canvas.setSize(width, height); + this.hitCanvas.setSize(width, height); + }, + _validateAdd: function(child) { + var type = child.getType(); + if (type !== 'Group' && type !== 'Shape') { + Konva.Util.throw('You may only add groups and shapes to a layer.'); + } + }, + /** + * get visible intersection shape. This is the preferred + * method for determining if a point intersects a shape or not + * also you may pass optional selector parametr to return ancestor of intersected shape + * @method + * @memberof Konva.Layer.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @param {String} [selector] + * @returns {Konva.Node} + * @example + * var shape = layer.getIntersection({x: 50, y: 50}); + * // or if you interested in shape parent: + * var group = layer.getIntersection({x: 50, y: 50}, 'Group'); + */ + getIntersection: function(pos, selector) { + var obj, i, intersectionOffset, shape; + + if (!this.hitGraphEnabled() || !this.isVisible()) { + return null; + } + // in some cases antialiased area may be bigger than 1px + // it is possible if we will cache node, then scale it a lot + // TODO: check { 0; 0 } point before loop, and remove it from INTERSECTION_OFFSETS. + var spiralSearchDistance = 1; + var continueSearch = false; + while (true) { + for (i = 0; i < INTERSECTION_OFFSETS_LEN; i++) { + intersectionOffset = INTERSECTION_OFFSETS[i]; + obj = this._getIntersection({ + x: pos.x + intersectionOffset.x * spiralSearchDistance, + y: pos.y + intersectionOffset.y * spiralSearchDistance + }); + shape = obj.shape; + if (shape && selector) { + return shape.findAncestor(selector, true); + } else if (shape) { + return shape; + } + // we should continue search if we found antialiased pixel + // that means our node somewhere very close + continueSearch = !!obj.antialiased; + // stop search if found empty pixel + if (!obj.antialiased) { + break; + } + } + // if no shape, and no antialiased pixel, we should end searching + if (continueSearch) { + spiralSearchDistance += 1; + } else { + return null; + } + } + }, + _getImageData: function(x, y) { + var width = this.hitCanvas.width || 1, + height = this.hitCanvas.height || 1, + index = Math.round(y) * width + Math.round(x); + + if (!this._hitImageData) { + this._hitImageData = this.hitCanvas.context.getImageData( + 0, + 0, + width, + height + ); + } + + return [ + this._hitImageData.data[4 * index + 0], // Red + this._hitImageData.data[4 * index + 1], // Green + this._hitImageData.data[4 * index + 2], // Blue + this._hitImageData.data[4 * index + 3] // Alpha + ]; + }, + _getIntersection: function(pos) { + var ratio = this.hitCanvas.pixelRatio; + var p = this.hitCanvas.context.getImageData( + Math.round(pos.x * ratio), + Math.round(pos.y * ratio), + 1, + 1 + ).data, + p3 = p[3], + colorKey, + shape; + // fully opaque pixel + if (p3 === 255) { + colorKey = Konva.Util._rgbToHex(p[0], p[1], p[2]); + shape = Konva.shapes[HASH + colorKey]; + if (shape) { + return { + shape: shape + }; + } + return { + antialiased: true + }; + } else if (p3 > 0) { + // antialiased pixel + return { + antialiased: true + }; + } + // empty pixel + return {}; + }, + drawScene: function(can, top) { + var layer = this.getLayer(), canvas = can || (layer && layer.getCanvas()); + + this._fire(BEFORE_DRAW, { + node: this + }); + + if (this.getClearBeforeDraw()) { + canvas.getContext().clear(); + } + + Konva.Container.prototype.drawScene.call(this, canvas, top); + + this._fire(DRAW, { + node: this + }); + + return this; + }, + drawHit: function(can, top) { + var layer = this.getLayer(), canvas = can || (layer && layer.hitCanvas); + + if (layer && layer.getClearBeforeDraw()) { + layer.getHitCanvas().getContext().clear(); + } + + Konva.Container.prototype.drawHit.call(this, canvas, top); + this.imageData = null; // Clear imageData cache + return this; + }, + clear: function(bounds) { + Konva.BaseLayer.prototype.clear.call(this, bounds); + this.getHitCanvas().getContext().clear(bounds); + this.imageData = null; // Clear getImageData cache + return this; + }, + // extend Node.prototype.setVisible + setVisible: function(visible) { + Konva.Node.prototype.setVisible.call(this, visible); + if (visible) { + this.getCanvas()._canvas.style.display = 'block'; + this.hitCanvas._canvas.style.display = 'block'; + } else { + this.getCanvas()._canvas.style.display = 'none'; + this.hitCanvas._canvas.style.display = 'none'; + } + return this; + }, + /** + * enable hit graph + * @name enableHitGraph + * @method + * @memberof Konva.Layer.prototype + * @returns {Layer} + */ + enableHitGraph: function() { + this.setHitGraphEnabled(true); + return this; + }, + /** + * disable hit graph + * @name disableHitGraph + * @method + * @memberof Konva.Layer.prototype + * @returns {Layer} + */ + disableHitGraph: function() { + this.setHitGraphEnabled(false); + return this; + }, + setSize: function(width, height) { + Konva.BaseLayer.prototype.setSize.call(this, width, height); + this.hitCanvas.setSize(width, height); + return this; + } + }); + Konva.Util.extend(Konva.Layer, Konva.BaseLayer); + + Konva.Factory.addGetterSetter(Konva.Layer, 'hitGraphEnabled', true); + /** + * get/set hitGraphEnabled flag. Disabling the hit graph will greatly increase + * draw performance because the hit graph will not be redrawn each time the layer is + * drawn. This, however, also disables mouse/touch event detection + * @name hitGraphEnabled + * @method + * @memberof Konva.Layer.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get hitGraphEnabled flag + * var hitGraphEnabled = layer.hitGraphEnabled(); + * + * // disable hit graph + * layer.hitGraphEnabled(false); + * + * // enable hit graph + * layer.hitGraphEnabled(true); + */ + Konva.Collection.mapMethods(Konva.Layer); +})(); + +(function() { + 'use strict'; + /** + * FastLayer constructor. Layers are tied to their own canvas element and are used + * to contain shapes only. If you don't need node nesting, mouse and touch interactions, + * or event pub/sub, you should use FastLayer instead of Layer to create your layers. + * It renders about 2x faster than normal layers. + * @constructor + * @memberof Konva + * @augments Konva.BaseLayer + * @param {Object} config + * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want + * to clear the canvas before each layer draw. The default value is true. + * @param {Boolean} [config.visible] + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * * @param {Object} [config.clip] set clip + * @param {Number} [config.clipX] set clip x + * @param {Number} [config.clipY] set clip y + * @param {Number} [config.clipWidth] set clip width + * @param {Number} [config.clipHeight] set clip height + * @param {Function} [config.clipFunc] set clip func + + * @example + * var layer = new Konva.FastLayer(); + */ + Konva.FastLayer = function(config) { + this.____init(config); + }; + + Konva.Util.addMethods(Konva.FastLayer, { + ____init: function(config) { + this.nodeType = 'Layer'; + this.canvas = new Konva.SceneCanvas(); + // call super constructor + Konva.BaseLayer.call(this, config); + }, + _validateAdd: function(child) { + var type = child.getType(); + if (type !== 'Shape') { + Konva.Util.throw('You may only add shapes to a fast layer.'); + } + }, + _setCanvasSize: function(width, height) { + this.canvas.setSize(width, height); + }, + hitGraphEnabled: function() { + return false; + }, + getIntersection: function() { + return null; + }, + drawScene: function(can) { + var layer = this.getLayer(), canvas = can || (layer && layer.getCanvas()); + + if (this.getClearBeforeDraw()) { + canvas.getContext().clear(); + } + + Konva.Container.prototype.drawScene.call(this, canvas); + + return this; + }, + draw: function() { + this.drawScene(); + return this; + }, + // extend Node.prototype.setVisible + setVisible: function(visible) { + Konva.Node.prototype.setVisible.call(this, visible); + if (visible) { + this.getCanvas()._canvas.style.display = 'block'; + } else { + this.getCanvas()._canvas.style.display = 'none'; + } + return this; + } + }); + Konva.Util.extend(Konva.FastLayer, Konva.BaseLayer); + + Konva.Collection.mapMethods(Konva.FastLayer); +})(); + +(function() { + 'use strict'; + /** + * Group constructor. Groups are used to contain shapes or other groups. + * @constructor + * @memberof Konva + * @augments Konva.Container + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * * @param {Object} [config.clip] set clip + * @param {Number} [config.clipX] set clip x + * @param {Number} [config.clipY] set clip y + * @param {Number} [config.clipWidth] set clip width + * @param {Number} [config.clipHeight] set clip height + * @param {Function} [config.clipFunc] set clip func + + * @example + * var group = new Konva.Group(); + */ + Konva.Group = function(config) { + this.___init(config); + }; + + Konva.Util.addMethods(Konva.Group, { + ___init: function(config) { + this.nodeType = 'Group'; + // call super constructor + Konva.Container.call(this, config); + }, + _validateAdd: function(child) { + var type = child.getType(); + if (type !== 'Group' && type !== 'Shape') { + Konva.Util.throw('You may only add groups and shapes to groups.'); + } + } + }); + Konva.Util.extend(Konva.Group, Konva.Container); + + Konva.Collection.mapMethods(Konva.Group); +})(); + +(function(Konva) { + 'use strict'; + var now = (function() { + if (Konva.global.performance && Konva.global.performance.now) { + return function() { + return Konva.global.performance.now(); + }; + } + + return function() { + return new Date().getTime(); + }; + })(); + + function FRAF(callback) { + setTimeout(callback, 1000 / 60); + } + + var RAF = (function() { + return Konva.global.requestAnimationFrame || + Konva.global.webkitRequestAnimationFrame || + Konva.global.mozRequestAnimationFrame || + Konva.global.oRequestAnimationFrame || + Konva.global.msRequestAnimationFrame || + FRAF; + })(); + + function requestAnimFrame() { + return RAF.apply(Konva.global, arguments); + } + + /** + * Animation constructor. A stage is used to contain multiple layers and handle + * @constructor + * @memberof Konva + * @param {Function} func function executed on each animation frame. The function is passed a frame object, which contains + * timeDiff, lastTime, time, and frameRate properties. The timeDiff property is the number of milliseconds that have passed + * since the last animation frame. The lastTime property is time in milliseconds that elapsed from the moment the animation started + * to the last animation frame. The time property is the time in milliseconds that ellapsed from the moment the animation started + * to the current animation frame. The frameRate property is the current frame rate in frames / second. Return false from function, + * if you don't need to redraw layer/layers on some frames. + * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn on each animation frame. Can be a layer, an array of layers, or null. + * Not specifying a node will result in no redraw. + * @example + * // move a node to the right at 50 pixels / second + * var velocity = 50; + * + * var anim = new Konva.Animation(function(frame) { + * var dist = velocity * (frame.timeDiff / 1000); + * node.move(dist, 0); + * }, layer); + * + * anim.start(); + */ + Konva.Animation = function(func, layers) { + var Anim = Konva.Animation; + this.func = func; + this.setLayers(layers); + this.id = Anim.animIdCounter++; + this.frame = { + time: 0, + timeDiff: 0, + lastTime: now() + }; + }; + /* + * Animation methods + */ + Konva.Animation.prototype = { + /** + * set layers to be redrawn on each animation frame + * @method + * @memberof Konva.Animation.prototype + * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn.  Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw. + * @return {Konva.Animation} this + */ + setLayers: function(layers) { + var lays = []; + // if passing in no layers + if (!layers) { + lays = []; + } else if (layers.length > 0) { + // if passing in an array of Layers + // NOTE: layers could be an array or Konva.Collection. for simplicity, I'm just inspecting + // the length property to check for both cases + lays = layers; + } else { + // if passing in a Layer + lays = [layers]; + } + + this.layers = lays; + return this; + }, + /** + * get layers + * @method + * @memberof Konva.Animation.prototype + * @return {Array} Array of Konva.Layer + */ + getLayers: function() { + return this.layers; + }, + /** + * add layer. Returns true if the layer was added, and false if it was not + * @method + * @memberof Konva.Animation.prototype + * @param {Konva.Layer} layer to add + * @return {Bool} true if layer is added to animation, otherwise false + */ + addLayer: function(layer) { + var layers = this.layers, len = layers.length, n; + + // don't add the layer if it already exists + for (n = 0; n < len; n++) { + if (layers[n]._id === layer._id) { + return false; + } + } + + this.layers.push(layer); + return true; + }, + /** + * determine if animation is running or not. returns true or false + * @method + * @memberof Konva.Animation.prototype + * @return {Bool} is animation running? + */ + isRunning: function() { + var a = Konva.Animation, + animations = a.animations, + len = animations.length, + n; + + for (n = 0; n < len; n++) { + if (animations[n].id === this.id) { + return true; + } + } + return false; + }, + /** + * start animation + * @method + * @memberof Konva.Animation.prototype + * @return {Konva.Animation} this + */ + start: function() { + var Anim = Konva.Animation; + this.stop(); + this.frame.timeDiff = 0; + this.frame.lastTime = now(); + Anim._addAnimation(this); + return this; + }, + /** + * stop animation + * @method + * @memberof Konva.Animation.prototype + * @return {Konva.Animation} this + */ + stop: function() { + Konva.Animation._removeAnimation(this); + return this; + }, + _updateFrameObject: function(time) { + this.frame.timeDiff = time - this.frame.lastTime; + this.frame.lastTime = time; + this.frame.time += this.frame.timeDiff; + this.frame.frameRate = 1000 / this.frame.timeDiff; + } + }; + Konva.Animation.animations = []; + Konva.Animation.animIdCounter = 0; + Konva.Animation.animRunning = false; + + Konva.Animation._addAnimation = function(anim) { + this.animations.push(anim); + this._handleAnimation(); + }; + Konva.Animation._removeAnimation = function(anim) { + var id = anim.id, animations = this.animations, len = animations.length, n; + + for (n = 0; n < len; n++) { + if (animations[n].id === id) { + this.animations.splice(n, 1); + break; + } + } + }; + + Konva.Animation._runFrames = function() { + var layerHash = {}, + animations = this.animations, + anim, + layers, + func, + n, + i, + layersLen, + layer, + key, + needRedraw; + /* + * loop through all animations and execute animation + * function. if the animation object has specified node, + * we can add the node to the nodes hash to eliminate + * drawing the same node multiple times. The node property + * can be the stage itself or a layer + */ + /* + * WARNING: don't cache animations.length because it could change while + * the for loop is running, causing a JS error + */ + + for (n = 0; n < animations.length; n++) { + anim = animations[n]; + layers = anim.layers; + func = anim.func; + + anim._updateFrameObject(now()); + layersLen = layers.length; + + // if animation object has a function, execute it + if (func) { + // allow anim bypassing drawing + needRedraw = func.call(anim, anim.frame) !== false; + } else { + needRedraw = true; + } + if (!needRedraw) { + continue; + } + for (i = 0; i < layersLen; i++) { + layer = layers[i]; + + if (layer._id !== undefined) { + layerHash[layer._id] = layer; + } + } + } + + for (key in layerHash) { + if (!layerHash.hasOwnProperty(key)) { + continue; + } + layerHash[key].draw(); + } + }; + Konva.Animation._animationLoop = function() { + var Anim = Konva.Animation; + if (Anim.animations.length) { + Anim._runFrames(); + requestAnimFrame(Anim._animationLoop); + } else { + Anim.animRunning = false; + } + }; + Konva.Animation._handleAnimation = function() { + if (!this.animRunning) { + this.animRunning = true; + requestAnimFrame(this._animationLoop); + } + }; + + /** + * batch draw. this function will not do immediate draw + * but it will schedule drawing to next tick (requestAnimFrame) + * @method + * @return {Konva.Layer} this + * @memberof Konva.Base.prototype + */ + Konva.BaseLayer.prototype.batchDraw = function() { + var that = this, Anim = Konva.Animation; + + if (!this.batchAnim) { + this.batchAnim = new Anim( + function() { + // stop animation after first tick + that.batchAnim.stop(); + }, + this + ); + } + + this.lastBatchDrawTime = now(); + + if (!this.batchAnim.isRunning()) { + this.batchAnim.start(); + } + return this; + }; + + /** + * batch draw + * @method + * @return {Konva.Stage} this + * @memberof Konva.Stage.prototype + */ + Konva.Stage.prototype.batchDraw = function() { + this.getChildren().each(function(layer) { + layer.batchDraw(); + }); + return this; + }; +})(Konva); + +(function() { + 'use strict'; + var blacklist = { + node: 1, + duration: 1, + easing: 1, + onFinish: 1, + yoyo: 1 + }, + PAUSED = 1, + PLAYING = 2, + REVERSING = 3, + idCounter = 0, + colorAttrs = ['fill', 'stroke', 'shadowColor']; + + var Tween = function(prop, propFunc, func, begin, finish, duration, yoyo) { + this.prop = prop; + this.propFunc = propFunc; + this.begin = begin; + this._pos = begin; + this.duration = duration; + this._change = 0; + this.prevPos = 0; + this.yoyo = yoyo; + this._time = 0; + this._position = 0; + this._startTime = 0; + this._finish = 0; + this.func = func; + this._change = finish - this.begin; + this.pause(); + }; + /* + * Tween methods + */ + Tween.prototype = { + fire: function(str) { + var handler = this[str]; + if (handler) { + handler(); + } + }, + setTime: function(t) { + if (t > this.duration) { + if (this.yoyo) { + this._time = this.duration; + this.reverse(); + } else { + this.finish(); + } + } else if (t < 0) { + if (this.yoyo) { + this._time = 0; + this.play(); + } else { + this.reset(); + } + } else { + this._time = t; + this.update(); + } + }, + getTime: function() { + return this._time; + }, + setPosition: function(p) { + this.prevPos = this._pos; + this.propFunc(p); + this._pos = p; + }, + getPosition: function(t) { + if (t === undefined) { + t = this._time; + } + return this.func(t, this.begin, this._change, this.duration); + }, + play: function() { + this.state = PLAYING; + this._startTime = this.getTimer() - this._time; + this.onEnterFrame(); + this.fire('onPlay'); + }, + reverse: function() { + this.state = REVERSING; + this._time = this.duration - this._time; + this._startTime = this.getTimer() - this._time; + this.onEnterFrame(); + this.fire('onReverse'); + }, + seek: function(t) { + this.pause(); + this._time = t; + this.update(); + this.fire('onSeek'); + }, + reset: function() { + this.pause(); + this._time = 0; + this.update(); + this.fire('onReset'); + }, + finish: function() { + this.pause(); + this._time = this.duration; + this.update(); + this.fire('onFinish'); + }, + update: function() { + this.setPosition(this.getPosition(this._time)); + }, + onEnterFrame: function() { + var t = this.getTimer() - this._startTime; + if (this.state === PLAYING) { + this.setTime(t); + } else if (this.state === REVERSING) { + this.setTime(this.duration - t); + } + }, + pause: function() { + this.state = PAUSED; + this.fire('onPause'); + }, + getTimer: function() { + return new Date().getTime(); + } + }; + + /** + * Tween constructor. Tweens enable you to animate a node between the current state and a new state. + * You can play, pause, reverse, seek, reset, and finish tweens. By default, tweens are animated using + * a linear easing. For more tweening options, check out {@link Konva.Easings} + * @constructor + * @memberof Konva + * @example + * // instantiate new tween which fully rotates a node in 1 second + * var tween = new Konva.Tween({ + * node: node, + * rotationDeg: 360, + * duration: 1, + * easing: Konva.Easings.EaseInOut + * }); + * + * // play tween + * tween.play(); + * + * // pause tween + * tween.pause(); + */ + Konva.Tween = function(config) { + var that = this, + node = config.node, + nodeId = node._id, + duration, + easing = config.easing || Konva.Easings.Linear, + yoyo = !!config.yoyo, + key; + + if (typeof config.duration === 'undefined') { + duration = 1; + } else if (config.duration === 0) { + // zero is bad value for duration + duration = 0.001; + } else { + duration = config.duration; + } + this.node = node; + this._id = idCounter++; + + var layers = node.getLayer() || + (node instanceof Konva.Stage ? node.getLayers() : null); + if (!layers) { + Konva.Util.error( + 'Tween constructor have `node` that is not in a layer. Please add node into layer first.' + ); + } + this.anim = new Konva.Animation( + function() { + that.tween.onEnterFrame(); + }, + layers + ); + + this.tween = new Tween( + key, + function(i) { + that._tweenFunc(i); + }, + easing, + 0, + 1, + duration * 1000, + yoyo + ); + + this._addListeners(); + + // init attrs map + if (!Konva.Tween.attrs[nodeId]) { + Konva.Tween.attrs[nodeId] = {}; + } + if (!Konva.Tween.attrs[nodeId][this._id]) { + Konva.Tween.attrs[nodeId][this._id] = {}; + } + // init tweens map + if (!Konva.Tween.tweens[nodeId]) { + Konva.Tween.tweens[nodeId] = {}; + } + + for (key in config) { + if (blacklist[key] === undefined) { + this._addAttr(key, config[key]); + } + } + + this.reset(); + + // callbacks + this.onFinish = config.onFinish; + this.onReset = config.onReset; + }; + + // start/diff object = attrs.nodeId.tweenId.attr + Konva.Tween.attrs = {}; + // tweenId = tweens.nodeId.attr + Konva.Tween.tweens = {}; + + Konva.Tween.prototype = { + _addAttr: function(key, end) { + var node = this.node, + nodeId = node._id, + start, + diff, + tweenId, + n, + len, + trueEnd, + trueStart; + + // remove conflict from tween map if it exists + tweenId = Konva.Tween.tweens[nodeId][key]; + + if (tweenId) { + delete Konva.Tween.attrs[nodeId][tweenId][key]; + } + + // add to tween map + start = node.getAttr(key); + + if (Konva.Util._isArray(end)) { + diff = []; + len = Math.max(end.length, start.length); + + if (key === 'points' && end.length !== start.length) { + // before tweening points we need to make sure that start.length === end.length + // Konva.Util._prepareArrayForTween thinking that end.length > start.length + + if (end.length > start.length) { + // so in this case we will increase number of starting points + trueStart = start; + start = Konva.Util._prepareArrayForTween(start, end, node.closed()); + } else { + // in this case we will increase number of eding points + trueEnd = end; + end = Konva.Util._prepareArrayForTween(end, start, node.closed()); + } + } + + for (n = 0; n < len; n++) { + diff.push(end[n] - start[n]); + } + } else if (colorAttrs.indexOf(key) !== -1) { + start = Konva.Util.colorToRGBA(start); + var endRGBA = Konva.Util.colorToRGBA(end); + diff = { + r: endRGBA.r - start.r, + g: endRGBA.g - start.g, + b: endRGBA.b - start.b, + a: endRGBA.a - start.a + }; + } else { + diff = end - start; + } + + Konva.Tween.attrs[nodeId][this._id][key] = { + start: start, + diff: diff, + end: end, + trueEnd: trueEnd, + trueStart: trueStart + }; + Konva.Tween.tweens[nodeId][key] = this._id; + }, + _tweenFunc: function(i) { + var node = this.node, + attrs = Konva.Tween.attrs[node._id][this._id], + key, + attr, + start, + diff, + newVal, + n, + len, + end; + + for (key in attrs) { + attr = attrs[key]; + start = attr.start; + diff = attr.diff; + end = attr.end; + + if (Konva.Util._isArray(start)) { + newVal = []; + len = Math.max(start.length, end.length); + for (n = 0; n < len; n++) { + newVal.push((start[n] || 0) + diff[n] * i); + } + } else if (colorAttrs.indexOf(key) !== -1) { + newVal = 'rgba(' + + Math.round(start.r + diff.r * i) + + ',' + + Math.round(start.g + diff.g * i) + + ',' + + Math.round(start.b + diff.b * i) + + ',' + + (start.a + diff.a * i) + + ')'; + } else { + newVal = start + diff * i; + } + + node.setAttr(key, newVal); + } + }, + _addListeners: function() { + var that = this; + + // start listeners + this.tween.onPlay = function() { + that.anim.start(); + }; + this.tween.onReverse = function() { + that.anim.start(); + }; + + // stop listeners + this.tween.onPause = function() { + that.anim.stop(); + }; + this.tween.onFinish = function() { + var node = that.node; + + // after tweening points of line we need to set original end + var attrs = Konva.Tween.attrs[node._id][that._id]; + if (attrs.points && attrs.points.trueEnd) { + node.points(attrs.points.trueEnd); + } + + if (that.onFinish) { + that.onFinish.call(that); + } + }; + this.tween.onReset = function() { + var node = that.node; + // after tweening points of line we need to set original start + var attrs = Konva.Tween.attrs[node._id][that._id]; + if (attrs.points && attrs.points.trueStart) { + node.points(attrs.points.trueStart); + } + + if (that.onReset) { + that.onReset(); + } + }; + }, + /** + * play + * @method + * @memberof Konva.Tween.prototype + * @returns {Tween} + */ + play: function() { + this.tween.play(); + return this; + }, + /** + * reverse + * @method + * @memberof Konva.Tween.prototype + * @returns {Tween} + */ + reverse: function() { + this.tween.reverse(); + return this; + }, + /** + * reset + * @method + * @memberof Konva.Tween.prototype + * @returns {Tween} + */ + reset: function() { + this.tween.reset(); + return this; + }, + /** + * seek + * @method + * @memberof Konva.Tween.prototype + * @param {Integer} t time in seconds between 0 and the duration + * @returns {Tween} + */ + seek: function(t) { + this.tween.seek(t * 1000); + return this; + }, + /** + * pause + * @method + * @memberof Konva.Tween.prototype + * @returns {Tween} + */ + pause: function() { + this.tween.pause(); + return this; + }, + /** + * finish + * @method + * @memberof Konva.Tween.prototype + * @returns {Tween} + */ + finish: function() { + this.tween.finish(); + return this; + }, + /** + * destroy + * @method + * @memberof Konva.Tween.prototype + */ + destroy: function() { + var nodeId = this.node._id, + thisId = this._id, + attrs = Konva.Tween.tweens[nodeId], + key; + + this.pause(); + + for (key in attrs) { + delete Konva.Tween.tweens[nodeId][key]; + } + + delete Konva.Tween.attrs[nodeId][thisId]; + } + }; + + /** + * Tween node properties. Shorter usage of {@link Konva.Tween} object. + * + * @method Konva.Node#to + * @memberof Konva.Node + * @param {Object} [params] tween params + * @example + * + * circle.to({ + * x : 50, + * duration : 0.5 + * }); + */ + Konva.Node.prototype.to = function(params) { + var onFinish = params.onFinish; + params.node = this; + params.onFinish = function() { + this.destroy(); + if (onFinish) { + onFinish(); + } + }; + var tween = new Konva.Tween(params); + tween.play(); + }; + + /* + * These eases were ported from an Adobe Flash tweening library to JavaScript + * by Xaric + */ + + /** + * @namespace Easings + * @memberof Konva + */ + Konva.Easings = { + /** + * back ease in + * @function + * @memberof Konva.Easings + */ + BackEaseIn: function(t, b, c, d) { + var s = 1.70158; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + /** + * back ease out + * @function + * @memberof Konva.Easings + */ + BackEaseOut: function(t, b, c, d) { + var s = 1.70158; + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + /** + * back ease in out + * @function + * @memberof Konva.Easings + */ + BackEaseInOut: function(t, b, c, d) { + var s = 1.70158; + if ((t /= d / 2) < 1) { + return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; + }, + /** + * elastic ease in + * @function + * @memberof Konva.Easings + */ + ElasticEaseIn: function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if (t === 0) { + return b; + } + if ((t /= d) === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + if (!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + return -(a * + Math.pow(2, 10 * (t -= 1)) * + Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + }, + /** + * elastic ease out + * @function + * @memberof Konva.Easings + */ + ElasticEaseOut: function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if (t === 0) { + return b; + } + if ((t /= d) === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + if (!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + return a * + Math.pow(2, (-10) * t) * + Math.sin((t * d - s) * (2 * Math.PI) / p) + + c + + b; + }, + /** + * elastic ease in out + * @function + * @memberof Konva.Easings + */ + ElasticEaseInOut: function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if (t === 0) { + return b; + } + if ((t /= d / 2) === 2) { + return b + c; + } + if (!p) { + p = d * (0.3 * 1.5); + } + if (!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + if (t < 1) { + return (-0.5) * + (a * + Math.pow(2, 10 * (t -= 1)) * + Math.sin((t * d - s) * (2 * Math.PI) / p)) + + b; + } + return a * + Math.pow(2, (-10) * (t -= 1)) * + Math.sin((t * d - s) * (2 * Math.PI) / p) * + 0.5 + + c + + b; + }, + /** + * bounce ease out + * @function + * @memberof Konva.Easings + */ + BounceEaseOut: function(t, b, c, d) { + if ((t /= d) < 1 / 2.75) { + return c * (7.5625 * t * t) + b; + } else if (t < 2 / 2.75) { + return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b; + } else if (t < 2.5 / 2.75) { + return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b; + } else { + return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b; + } + }, + /** + * bounce ease in + * @function + * @memberof Konva.Easings + */ + BounceEaseIn: function(t, b, c, d) { + return c - Konva.Easings.BounceEaseOut(d - t, 0, c, d) + b; + }, + /** + * bounce ease in out + * @function + * @memberof Konva.Easings + */ + BounceEaseInOut: function(t, b, c, d) { + if (t < d / 2) { + return Konva.Easings.BounceEaseIn(t * 2, 0, c, d) * 0.5 + b; + } else { + return Konva.Easings.BounceEaseOut(t * 2 - d, 0, c, d) * 0.5 + + c * 0.5 + + b; + } + }, + /** + * ease in + * @function + * @memberof Konva.Easings + */ + EaseIn: function(t, b, c, d) { + return c * (t /= d) * t + b; + }, + /** + * ease out + * @function + * @memberof Konva.Easings + */ + EaseOut: function(t, b, c, d) { + return (-c) * (t /= d) * (t - 2) + b; + }, + /** + * ease in out + * @function + * @memberof Konva.Easings + */ + EaseInOut: function(t, b, c, d) { + if ((t /= d / 2) < 1) { + return c / 2 * t * t + b; + } + return (-c) / 2 * (--t * (t - 2) - 1) + b; + }, + /** + * strong ease in + * @function + * @memberof Konva.Easings + */ + StrongEaseIn: function(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + }, + /** + * strong ease out + * @function + * @memberof Konva.Easings + */ + StrongEaseOut: function(t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + }, + /** + * strong ease in out + * @function + * @memberof Konva.Easings + */ + StrongEaseInOut: function(t, b, c, d) { + if ((t /= d / 2) < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + }, + /** + * linear + * @function + * @memberof Konva.Easings + */ + Linear: function(t, b, c, d) { + return c * t / d + b; + } + }; +})(); + +(function() { + 'use strict'; + Konva.DD = { + // properties + anim: new Konva.Animation(function() { + var b = this.dirty; + this.dirty = false; + return b; + }), + isDragging: false, + justDragged: false, + offset: { + x: 0, + y: 0 + }, + node: null, + + // methods + _drag: function(evt) { + var dd = Konva.DD, node = dd.node; + + if (node) { + if (!dd.isDragging) { + var pos = node.getStage().getPointerPosition(); + var dragDistance = node.dragDistance(); + var distance = Math.max( + Math.abs(pos.x - dd.startPointerPos.x), + Math.abs(pos.y - dd.startPointerPos.y) + ); + if (distance < dragDistance) { + return; + } + } + + node.getStage()._setPointerPosition(evt); + node._setDragPosition(evt); + if (!dd.isDragging) { + dd.isDragging = true; + node.fire( + 'dragstart', + { + type: 'dragstart', + target: node, + evt: evt + }, + true + ); + } + + // execute ondragmove if defined + node.fire( + 'dragmove', + { + type: 'dragmove', + target: node, + evt: evt + }, + true + ); + } + }, + _endDragBefore: function(evt) { + var dd = Konva.DD, node = dd.node, layer; + + if (node) { + layer = node.getLayer(); + dd.anim.stop(); + + // only fire dragend event if the drag and drop + // operation actually started. + if (dd.isDragging) { + dd.isDragging = false; + dd.justDragged = true; + Konva.listenClickTap = false; + + if (evt) { + evt.dragEndNode = node; + } + } + + delete dd.node; + + if (node.getLayer() || layer || node instanceof Konva.Stage) { + (layer || node).draw(); + } + } + }, + _endDragAfter: function(evt) { + evt = evt || {}; + var dragEndNode = evt.dragEndNode; + + if (evt && dragEndNode) { + dragEndNode.fire( + 'dragend', + { + type: 'dragend', + target: dragEndNode, + evt: evt + }, + true + ); + } + } + }; + + // Node extenders + + /** + * initiate drag and drop + * @method + * @memberof Konva.Node.prototype + */ + Konva.Node.prototype.startDrag = function() { + var dd = Konva.DD, + stage = this.getStage(), + layer = this.getLayer(), + pos = stage.getPointerPosition(), + ap = this.getAbsolutePosition(); + + if (pos) { + if (dd.node) { + dd.node.stopDrag(); + } + + dd.node = this; + dd.startPointerPos = pos; + dd.offset.x = pos.x - ap.x; + dd.offset.y = pos.y - ap.y; + dd.anim.setLayers(layer || this.getLayers()); + dd.anim.start(); + + this._setDragPosition(); + } + }; + + Konva.Node.prototype._setDragPosition = function(evt) { + var dd = Konva.DD, + pos = this.getStage().getPointerPosition(), + dbf = this.getDragBoundFunc(); + if (!pos) { + return; + } + var newNodePos = { + x: pos.x - dd.offset.x, + y: pos.y - dd.offset.y + }; + + if (dbf !== undefined) { + newNodePos = dbf.call(this, newNodePos, evt); + } + this.setAbsolutePosition(newNodePos); + + if ( + !this._lastPos || + this._lastPos.x !== newNodePos.x || + this._lastPos.y !== newNodePos.y + ) { + dd.anim.dirty = true; + } + + this._lastPos = newNodePos; + }; + + /** + * stop drag and drop + * @method + * @memberof Konva.Node.prototype + */ + Konva.Node.prototype.stopDrag = function() { + var dd = Konva.DD, evt = {}; + dd._endDragBefore(evt); + dd._endDragAfter(evt); + }; + + Konva.Node.prototype.setDraggable = function(draggable) { + this._setAttr('draggable', draggable); + this._dragChange(); + }; + + var origRemove = Konva.Node.prototype.remove; + + Konva.Node.prototype.__originalRemove = origRemove; + Konva.Node.prototype.remove = function() { + var dd = Konva.DD; + + // stop DD + if (dd.node && dd.node._id === this._id) { + this.stopDrag(); + } + + origRemove.call(this); + }; + + /** + * determine if node is currently in drag and drop mode + * @method + * @memberof Konva.Node.prototype + */ + Konva.Node.prototype.isDragging = function() { + var dd = Konva.DD; + return !!(dd.node && dd.node._id === this._id && dd.isDragging); + }; + + Konva.Node.prototype._listenDrag = function() { + var that = this; + + this._dragCleanup(); + + if (this.getClassName() === 'Stage') { + this.on('contentMousedown.konva contentTouchstart.konva', function(evt) { + if (!Konva.DD.node) { + that.startDrag(evt); + } + }); + } else { + this.on('mousedown.konva touchstart.konva', function(evt) { + // ignore right and middle buttons + if (evt.evt.button === 1 || evt.evt.button === 2) { + return; + } + if (!Konva.DD.node) { + that.startDrag(evt); + } + }); + } + + // listening is required for drag and drop + /* + this._listeningEnabled = true; + this._clearSelfAndAncestorCache('listeningEnabled'); + */ + }; + + Konva.Node.prototype._dragChange = function() { + if (this.attrs.draggable) { + this._listenDrag(); + } else { + // remove event listeners + this._dragCleanup(); + + /* + * force drag and drop to end + * if this node is currently in + * drag and drop mode + */ + var stage = this.getStage(); + var dd = Konva.DD; + if (stage && dd.node && dd.node._id === this._id) { + dd.node.stopDrag(); + } + } + }; + + Konva.Node.prototype._dragCleanup = function() { + if (this.getClassName() === 'Stage') { + this.off('contentMousedown.konva'); + this.off('contentTouchstart.konva'); + } else { + this.off('mousedown.konva'); + this.off('touchstart.konva'); + } + }; + + Konva.Factory.addGetterSetter(Konva.Node, 'dragBoundFunc'); + + /** + * get/set drag bound function. This is used to override the default + * drag and drop position + * @name dragBoundFunc + * @method + * @memberof Konva.Node.prototype + * @param {Function} dragBoundFunc + * @returns {Function} + * @example + * // get drag bound function + * var dragBoundFunc = node.dragBoundFunc(); + * + * // create vertical drag and drop + * node.dragBoundFunc(function(pos){ + * return { + * x: this.getAbsolutePosition().x, + * y: pos.y + * }; + * }); + */ + + Konva.Factory.addGetter(Konva.Node, 'draggable', false); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'draggable'); + + /** + * get/set draggable flag + * @name draggable + * @method + * @memberof Konva.Node.prototype + * @param {Boolean} draggable + * @returns {Boolean} + * @example + * // get draggable flag + * var draggable = node.draggable(); + * + * // enable drag and drop + * node.draggable(true); + * + * // disable drag and drop + * node.draggable(false); + */ + + var html = Konva.document.documentElement; + html.addEventListener('mouseup', Konva.DD._endDragBefore, true); + html.addEventListener('touchend', Konva.DD._endDragBefore, true); + + html.addEventListener('mousemove', Konva.DD._drag); + html.addEventListener('touchmove', Konva.DD._drag); + + html.addEventListener('mouseup', Konva.DD._endDragAfter, false); + html.addEventListener('touchend', Konva.DD._endDragAfter, false); +})(); + +(function() { + 'use strict'; + /** + * Rect constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} [config.cornerRadius] + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var rect = new Konva.Rect({ + * width: 100, + * height: 50, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 5 + * }); + */ + Konva.Rect = function(config) { + this.___init(config); + }; + + Konva.Rect.prototype = { + ___init: function(config) { + Konva.Shape.call(this, config); + this.className = 'Rect'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var cornerRadius = this.getCornerRadius(), + width = this.getWidth(), + height = this.getHeight(); + + context.beginPath(); + + if (!cornerRadius) { + // simple rect - don't bother doing all that complicated maths stuff. + context.rect(0, 0, width, height); + } else { + // arcTo would be nicer, but browser support is patchy (Opera) + cornerRadius = Math.min(cornerRadius, width / 2, height / 2); + context.moveTo(cornerRadius, 0); + context.lineTo(width - cornerRadius, 0); + context.arc( + width - cornerRadius, + cornerRadius, + cornerRadius, + Math.PI * 3 / 2, + 0, + false + ); + context.lineTo(width, height - cornerRadius); + context.arc( + width - cornerRadius, + height - cornerRadius, + cornerRadius, + 0, + Math.PI / 2, + false + ); + context.lineTo(cornerRadius, height); + context.arc( + cornerRadius, + height - cornerRadius, + cornerRadius, + Math.PI / 2, + Math.PI, + false + ); + context.lineTo(0, cornerRadius); + context.arc( + cornerRadius, + cornerRadius, + cornerRadius, + Math.PI, + Math.PI * 3 / 2, + false + ); + } + context.closePath(); + context.fillStrokeShape(this); + } + }; + + Konva.Util.extend(Konva.Rect, Konva.Shape); + + Konva.Factory.addGetterSetter(Konva.Rect, 'cornerRadius', 0); + /** + * get/set corner radius + * @name cornerRadius + * @method + * @memberof Konva.Rect.prototype + * @param {Number} cornerRadius + * @returns {Number} + * @example + * // get corner radius + * var cornerRadius = rect.cornerRadius(); + * + * // set corner radius + * rect.cornerRadius(10); + */ + + Konva.Collection.mapMethods(Konva.Rect); +})(); + +(function() { + 'use strict'; + // the 0.0001 offset fixes a bug in Chrome 27 + var PIx2 = Math.PI * 2 - 0.0001, CIRCLE = 'Circle'; + + /** + * Circle constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.radius + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * // create circle + * var circle = new Konva.Circle({ + * radius: 40, + * fill: 'red', + * stroke: 'black' + * strokeWidth: 5 + * }); + */ + Konva.Circle = function(config) { + this.___init(config); + }; + + Konva.Circle.prototype = { + _centroid: true, + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = CIRCLE; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + context.beginPath(); + context.arc(0, 0, this.getRadius(), 0, PIx2, false); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + if (this.radius() !== width / 2) { + this.setRadius(width / 2); + } + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + if (this.radius() !== height / 2) { + this.setRadius(height / 2); + } + } + }; + Konva.Util.extend(Konva.Circle, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Circle, 'radius', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Circle, 'radius'); + + /** + * get/set radius + * @name radius + * @method + * @memberof Konva.Circle.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radius + * var radius = circle.radius(); + * + * // set radius + * circle.radius(10); + */ + + Konva.Collection.mapMethods(Konva.Circle); +})(); + +(function() { + 'use strict'; + // the 0.0001 offset fixes a bug in Chrome 27 + var PIx2 = Math.PI * 2 - 0.0001, ELLIPSE = 'Ellipse'; + + /** + * Ellipse constructor + * @constructor + * @augments Konva.Shape + * @param {Object} config + * @param {Object} config.radius defines x and y radius + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var ellipse = new Konva.Ellipse({ + * radius : { + * x : 50, + * y : 50 + * }, + * fill: 'red' + * }); + */ + Konva.Ellipse = function(config) { + this.___init(config); + }; + + Konva.Ellipse.prototype = { + _centroid: true, + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = ELLIPSE; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var rx = this.getRadiusX(), ry = this.getRadiusY(); + + context.beginPath(); + context.save(); + if (rx !== ry) { + context.scale(1, ry / rx); + } + context.arc(0, 0, rx, 0, PIx2, false); + context.restore(); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getRadiusX() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getRadiusY() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + this.setRadius({ + x: width / 2 + }); + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + this.setRadius({ + y: height / 2 + }); + } + }; + Konva.Util.extend(Konva.Ellipse, Konva.Shape); + + // add getters setters + Konva.Factory.addComponentsGetterSetter(Konva.Ellipse, 'radius', ['x', 'y']); + + /** + * get/set radius + * @name radius + * @method + * @memberof Konva.Ellipse.prototype + * @param {Object} radius + * @param {Number} radius.x + * @param {Number} radius.y + * @returns {Object} + * @example + * // get radius + * var radius = ellipse.radius(); + * + * // set radius + * ellipse.radius({ + * x: 200, + * y: 100 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Ellipse, 'radiusX', 0); + /** + * get/set radius x + * @name radiusX + * @method + * @memberof Konva.Ellipse.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get radius x + * var radiusX = ellipse.radiusX(); + * + * // set radius x + * ellipse.radiusX(200); + */ + + Konva.Factory.addGetterSetter(Konva.Ellipse, 'radiusY', 0); + /** + * get/set radius y + * @name radiusY + * @method + * @memberof Konva.Ellipse.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get radius y + * var radiusY = ellipse.radiusY(); + * + * // set radius y + * ellipse.radiusY(200); + */ + + Konva.Collection.mapMethods(Konva.Ellipse); +})(); + +(function() { + 'use strict'; + // the 0.0001 offset fixes a bug in Chrome 27 + var PIx2 = Math.PI * 2 - 0.0001; + /** + * Ring constructor + * @constructor + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @param {Boolean} [config.clockwise] + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var ring = new Konva.Ring({ + * innerRadius: 40, + * outerRadius: 80, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 5 + * }); + */ + Konva.Ring = function(config) { + this.___init(config); + }; + + Konva.Ring.prototype = { + _centroid: true, + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = 'Ring'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + context.beginPath(); + context.arc(0, 0, this.getInnerRadius(), 0, PIx2, false); + context.moveTo(this.getOuterRadius(), 0); + context.arc(0, 0, this.getOuterRadius(), PIx2, 0, true); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + if (this.outerRadius() !== width / 2) { + this.setOuterRadius(width / 2); + } + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + if (this.outerRadius() !== height / 2) { + this.setOuterRadius(height / 2); + } + }, + setOuterRadius: function(val) { + this._setAttr('outerRadius', val); + this.setWidth(val * 2); + this.setHeight(val * 2); + } + }; + Konva.Util.extend(Konva.Ring, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Ring, 'innerRadius', 0); + + /** + * get/set innerRadius + * @name innerRadius + * @method + * @memberof Konva.Ring.prototype + * @param {Number} innerRadius + * @returns {Number} + * @example + * // get inner radius + * var innerRadius = ring.innerRadius(); + * + * // set inner radius + * ring.innerRadius(20); + */ + Konva.Factory.addGetter(Konva.Ring, 'outerRadius', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Ring, 'outerRadius'); + + /** + * get/set outerRadius + * @name outerRadius + * @method + * @memberof Konva.Ring.prototype + * @param {Number} outerRadius + * @returns {Number} + * @example + * // get outer radius + * var outerRadius = ring.outerRadius(); + * + * // set outer radius + * ring.outerRadius(20); + */ + + Konva.Collection.mapMethods(Konva.Ring); +})(); + +(function() { + 'use strict'; + /** + * Wedge constructor + * @constructor + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.angle in degrees + * @param {Number} config.radius + * @param {Boolean} [config.clockwise] + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * // draw a wedge that's pointing downwards + * var wedge = new Konva.Wedge({ + * radius: 40, + * fill: 'red', + * stroke: 'black' + * strokeWidth: 5, + * angleDeg: 60, + * rotationDeg: -120 + * }); + */ + Konva.Wedge = function(config) { + this.___init(config); + }; + + Konva.Wedge.prototype = { + _centroid: true, + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = 'Wedge'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + context.beginPath(); + context.arc( + 0, + 0, + this.getRadius(), + 0, + Konva.getAngle(this.getAngle()), + this.getClockwise() + ); + context.lineTo(0, 0); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + if (this.radius() !== width / 2) { + this.setRadius(width / 2); + } + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + if (this.radius() !== height / 2) { + this.setRadius(height / 2); + } + } + }; + Konva.Util.extend(Konva.Wedge, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Wedge, 'radius', 0); + + /** + * get/set radius + * @name radius + * @method + * @memberof Konva.Wedge.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radius + * var radius = wedge.radius(); + * + * // set radius + * wedge.radius(10); + */ + + Konva.Factory.addGetterSetter(Konva.Wedge, 'angle', 0); + + /** + * get/set angle in degrees + * @name angle + * @method + * @memberof Konva.Wedge.prototype + * @param {Number} angle + * @returns {Number} + * @example + * // get angle + * var angle = wedge.angle(); + * + * // set angle + * wedge.angle(20); + */ + + Konva.Factory.addGetterSetter(Konva.Wedge, 'clockwise', false); + + /** + * get/set clockwise flag + * @name clockwise + * @method + * @memberof Konva.Wedge.prototype + * @param {Number} clockwise + * @returns {Number} + * @example + * // get clockwise flag + * var clockwise = wedge.clockwise(); + * + * // draw wedge counter-clockwise + * wedge.clockwise(false); + * + * // draw wedge clockwise + * wedge.clockwise(true); + */ + + Konva.Factory.backCompat(Konva.Wedge, { + angleDeg: 'angle', + getAngleDeg: 'getAngle', + setAngleDeg: 'setAngle' + }); + + Konva.Collection.mapMethods(Konva.Wedge); +})(); + +(function() { + 'use strict'; + /** + * Arc constructor + * @constructor + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.angle in degrees + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @param {Boolean} [config.clockwise] + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * // draw a Arc that's pointing downwards + * var arc = new Konva.Arc({ + * innerRadius: 40, + * outerRadius: 80, + * fill: 'red', + * stroke: 'black' + * strokeWidth: 5, + * angle: 60, + * rotationDeg: -120 + * }); + */ + Konva.Arc = function(config) { + this.___init(config); + }; + + Konva.Arc.prototype = { + _centroid: true, + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = 'Arc'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var angle = Konva.getAngle(this.angle()), clockwise = this.clockwise(); + + context.beginPath(); + context.arc(0, 0, this.getOuterRadius(), 0, angle, clockwise); + context.arc(0, 0, this.getInnerRadius(), angle, 0, !clockwise); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + if (this.getOuterRadius() !== width / 2) { + this.setOuterRadius(width / 2); + } + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + if (this.getOuterRadius() !== height / 2) { + this.setOuterRadius(height / 2); + } + } + }; + Konva.Util.extend(Konva.Arc, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Arc, 'innerRadius', 0); + + /** + * get/set innerRadius + * @name innerRadius + * @method + * @memberof Konva.Arc.prototype + * @param {Number} innerRadius + * @returns {Number} + * @example + * // get inner radius + * var innerRadius = arc.innerRadius(); + * + * // set inner radius + * arc.innerRadius(20); + */ + + Konva.Factory.addGetterSetter(Konva.Arc, 'outerRadius', 0); + + /** + * get/set outerRadius + * @name outerRadius + * @method + * @memberof Konva.Arc.prototype + * @param {Number} outerRadius + * @returns {Number} + * @example + * // get outer radius + * var outerRadius = arc.outerRadius(); + * + * // set outer radius + * arc.outerRadius(20); + */ + + Konva.Factory.addGetterSetter(Konva.Arc, 'angle', 0); + + /** + * get/set angle in degrees + * @name angle + * @method + * @memberof Konva.Arc.prototype + * @param {Number} angle + * @returns {Number} + * @example + * // get angle + * var angle = arc.angle(); + * + * // set angle + * arc.angle(20); + */ + + Konva.Factory.addGetterSetter(Konva.Arc, 'clockwise', false); + + /** + * get/set clockwise flag + * @name clockwise + * @method + * @memberof Konva.Arc.prototype + * @param {Boolean} clockwise + * @returns {Boolean} + * @example + * // get clockwise flag + * var clockwise = arc.clockwise(); + * + * // draw arc counter-clockwise + * arc.clockwise(false); + * + * // draw arc clockwise + * arc.clockwise(true); + */ + + Konva.Collection.mapMethods(Konva.Arc); +})(); + +(function() { + 'use strict'; + // CONSTANTS + var IMAGE = 'Image'; + + /** + * Image constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Image} config.image + * @param {Object} [config.crop] + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var imageObj = new Image(); + * imageObj.onload = function() { + * var image = new Konva.Image({ + * x: 200, + * y: 50, + * image: imageObj, + * width: 100, + * height: 100 + * }); + * }; + * imageObj.src = '/path/to/image.jpg' + */ + Konva.Image = function(config) { + this.___init(config); + }; + + Konva.Image.prototype = { + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = IMAGE; + this.sceneFunc(this._sceneFunc); + this.hitFunc(this._hitFunc); + }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && + this.hasStroke() && + this.getStage(); + }, + _sceneFunc: function(context) { + var width = this.getWidth(), + height = this.getHeight(), + image = this.getImage(), + cropWidth, + cropHeight, + params; + + if (image) { + cropWidth = this.getCropWidth(); + cropHeight = this.getCropHeight(); + if (cropWidth && cropHeight) { + params = [ + image, + this.getCropX(), + this.getCropY(), + cropWidth, + cropHeight, + 0, + 0, + width, + height + ]; + } else { + params = [image, 0, 0, width, height]; + } + } + + if (this.hasFill() || this.hasStroke()) { + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + } + + if (image) { + context.drawImage.apply(context, params); + } + }, + _hitFunc: function(context) { + var width = this.getWidth(), height = this.getHeight(); + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + }, + getWidth: function() { + var image = this.getImage(); + return this.attrs.width || (image ? image.width : 0); + }, + getHeight: function() { + var image = this.getImage(); + return this.attrs.height || (image ? image.height : 0); + } + }; + Konva.Util.extend(Konva.Image, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Image, 'image'); + + /** + * set image + * @name setImage + * @method + * @memberof Konva.Image.prototype + * @param {Image} image + */ + + /** + * get image + * @name getImage + * @method + * @memberof Konva.Image.prototype + * @returns {Image} + */ + + Konva.Factory.addComponentsGetterSetter(Konva.Image, 'crop', [ + 'x', + 'y', + 'width', + 'height' + ]); + /** + * get/set crop + * @method + * @name crop + * @memberof Konva.Image.prototype + * @param {Object} crop + * @param {Number} crop.x + * @param {Number} crop.y + * @param {Number} crop.width + * @param {Number} crop.height + * @returns {Object} + * @example + * // get crop + * var crop = image.crop(); + * + * // set crop + * image.crop({ + * x: 20, + * y: 20, + * width: 20, + * height: 20 + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Image, 'cropX', 0); + /** + * get/set crop x + * @method + * @name cropX + * @memberof Konva.Image.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get crop x + * var cropX = image.cropX(); + * + * // set crop x + * image.cropX(20); + */ + + Konva.Factory.addGetterSetter(Konva.Image, 'cropY', 0); + /** + * get/set crop y + * @name cropY + * @method + * @memberof Konva.Image.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get crop y + * var cropY = image.cropY(); + * + * // set crop y + * image.cropY(20); + */ + + Konva.Factory.addGetterSetter(Konva.Image, 'cropWidth', 0); + /** + * get/set crop width + * @name cropWidth + * @method + * @memberof Konva.Image.prototype + * @param {Number} width + * @returns {Number} + * @example + * // get crop width + * var cropWidth = image.cropWidth(); + * + * // set crop width + * image.cropWidth(20); + */ + + Konva.Factory.addGetterSetter(Konva.Image, 'cropHeight', 0); + /** + * get/set crop height + * @name cropHeight + * @method + * @memberof Konva.Image.prototype + * @param {Number} height + * @returns {Number} + * @example + * // get crop height + * var cropHeight = image.cropHeight(); + * + * // set crop height + * image.cropHeight(20); + */ + + Konva.Collection.mapMethods(Konva.Image); + + /** + * load image from given url and create `Konva.Image` instance + * @method + * @memberof Konva.Image + * @param {String} url image source + * @param {Function} callback with Konva.Image instance as first argument + * @example + * Konva.Image.fromURL(imageURL, function(image){ + * // image is Konva.Image instance + * layer.add(image); + * layer.draw(); + * }); + */ + Konva.Image.fromURL = function(url, callback) { + var img = new Image(); + img.onload = function() { + var image = new Konva.Image({ + image: img + }); + callback(image); + }; + img.src = url; + }; +})(); + +/*eslint-disable max-depth */ +(function() { + 'use strict'; + // var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + // constants + var AUTO = 'auto', + //CANVAS = 'canvas', + CENTER = 'center', + JUSTIFY = 'justify', + CHANGE_KONVA = 'Change.konva', + CONTEXT_2D = '2d', + DASH = '-', + EMPTY_STRING = '', + LEFT = 'left', + TEXT = 'text', + TEXT_UPPER = 'Text', + MIDDLE = 'middle', + NORMAL = 'normal', + PX_SPACE = 'px ', + SPACE = ' ', + RIGHT = 'right', + WORD = 'word', + CHAR = 'char', + NONE = 'none', + ATTR_CHANGE_LIST = [ + 'fontFamily', + 'fontSize', + 'fontStyle', + 'fontVariant', + 'padding', + 'align', + 'lineHeight', + 'text', + 'width', + 'height', + 'wrap', + 'letterSpacing' + ], + // cached variables + attrChangeListLen = ATTR_CHANGE_LIST.length, + dummyContext = Konva.Util.createCanvasElement().getContext(CONTEXT_2D); + + /** + * Text constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} [config.fontFamily] default is Arial + * @param {Number} [config.fontSize] in pixels. Default is 12 + * @param {String} [config.fontStyle] can be normal, bold, or italic. Default is normal + * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal + * @param {String} config.text + * @param {String} [config.align] can be left, center, or right + * @param {Number} [config.padding] + * @param {Number} [config.lineHeight] default is 1 + * @param {String} [config.wrap] can be word, char, or none. Default is word + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var text = new Konva.Text({ + * x: 10, + * y: 15, + * text: 'Simple Text', + * fontSize: 30, + * fontFamily: 'Calibri', + * fill: 'green' + * }); + */ + Konva.Text = function(config) { + this.___init(config); + }; + function _fillFunc(context) { + context.fillText(this.partialText, 0, 0); + } + function _strokeFunc(context) { + context.strokeText(this.partialText, 0, 0); + } + + Konva.Text.prototype = { + ___init: function(config) { + config = config || {}; + + // set default color to black + if ( + !config.fillLinearGradientColorStops && + !config.fillRadialGradientColorStops + ) { + config.fill = config.fill || 'black'; + } + // + // if (config.width === undefined) { + // config.width = AUTO; + // } + // if (config.height === undefined) { + // config.height = AUTO; + // } + + // call super constructor + Konva.Shape.call(this, config); + + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this.className = TEXT_UPPER; + + // update text data for certain attr changes + for (var n = 0; n < attrChangeListLen; n++) { + this.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, this._setTextData); + } + + this._setTextData(); + this.sceneFunc(this._sceneFunc); + this.hitFunc(this._hitFunc); + }, + _sceneFunc: function(context) { + var p = this.getPadding(), + textHeight = this.getTextHeight(), + lineHeightPx = this.getLineHeight() * textHeight, + textArr = this.textArr, + textArrLen = textArr.length, + align = this.getAlign(), + totalWidth = this.getWidth(), + letterSpacing = this.getLetterSpacing(), + textDecoration = this.textDecoration(), + fill = this.fill(), + fontSize = this.fontSize(), + n; + + context.setAttr('font', this._getContextFont()); + + context.setAttr('textBaseline', MIDDLE); + context.setAttr('textAlign', LEFT); + context.save(); + if (p) { + context.translate(p, 0); + context.translate(0, p + textHeight / 2); + } else { + context.translate(0, textHeight / 2); + } + + // draw text lines + for (n = 0; n < textArrLen; n++) { + var obj = textArr[n], text = obj.text, width = obj.width; + + // horizontal alignment + context.save(); + if (align === RIGHT) { + context.translate(totalWidth - width - p * 2, 0); + } else if (align === CENTER) { + context.translate((totalWidth - width - p * 2) / 2, 0); + } + + if (textDecoration.indexOf('underline') !== -1) { + context.save(); + context.beginPath(); + context.moveTo(0, Math.round(lineHeightPx / 2)); + context.lineTo(Math.round(width), Math.round(lineHeightPx / 2)); + // TODO: I have no idea what is real ratio + // just /20 looks good enough + context.lineWidth = fontSize / 15; + context.strokeStyle = fill; + context.stroke(); + context.restore(); + } + if (textDecoration.indexOf('line-through') !== -1) { + context.save(); + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(Math.round(width), 0); + context.lineWidth = fontSize / 15; + context.strokeStyle = fill; + context.stroke(); + context.restore(); + } + if (letterSpacing !== 0 || align === JUSTIFY) { + // var words = text.split(' '); + var spacesNumber = text.split(' ').length - 1; + for (var li = 0; li < text.length; li++) { + var letter = text[li]; + // skip justify for the last line + if (letter === ' ' && n !== textArrLen - 1 && align === JUSTIFY) { + context.translate( + Math.floor((totalWidth - width) / spacesNumber), + 0 + ); + } + this.partialText = letter; + context.fillStrokeShape(this); + context.translate( + Math.round(this._getTextSize(letter).width) + letterSpacing, + 0 + ); + } + } else { + this.partialText = text; + + context.fillStrokeShape(this); + } + context.restore(); + context.translate(0, lineHeightPx); + } + context.restore(); + }, + _hitFunc: function(context) { + var width = this.getWidth(), height = this.getHeight(); + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + }, + // _useBufferCanvas: function(caching) { + // var useIt = Konva.Shape.prototype._useBufferCanvas.call(this, caching); + // if (useIt) { + // return true; + // } + // return false; + // // return isFirefox && this.hasFill() && this.hasShadow(); + // }, + setText: function(text) { + var str = Konva.Util._isString(text) ? text : (text || '').toString(); + this._setAttr(TEXT, str); + return this; + }, + /** + * get width of text area, which includes padding + * @method + * @memberof Konva.Text.prototype + * @returns {Number} + */ + getWidth: function() { + var isAuto = this.attrs.width === AUTO || this.attrs.width === undefined; + return isAuto + ? this.getTextWidth() + this.getPadding() * 2 + : this.attrs.width; + }, + /** + * get the height of the text area, which takes into account multi-line text, line heights, and padding + * @method + * @memberof Konva.Text.prototype + * @returns {Number} + */ + getHeight: function() { + var isAuto = this.attrs.height === AUTO || + this.attrs.height === undefined; + return isAuto + ? this.getTextHeight() * this.textArr.length * this.getLineHeight() + + this.getPadding() * 2 + : this.attrs.height; + }, + /** + * get text width + * @method + * @memberof Konva.Text.prototype + * @returns {Number} + */ + getTextWidth: function() { + return this.textWidth; + }, + /** + * get text height + * @method + * @memberof Konva.Text.prototype + * @returns {Number} + */ + getTextHeight: function() { + return this.textHeight; + }, + _getTextSize: function(text) { + var _context = dummyContext, fontSize = this.getFontSize(), metrics; + + _context.save(); + _context.font = this._getContextFont(); + + metrics = _context.measureText(text); + _context.restore(); + return { + width: metrics.width, + height: parseInt(fontSize, 10) + }; + }, + _getContextFont: function() { + // IE don't want to work with usual font style + // bold was not working + // removing font variant will solve + // fix for: https://github.com/konvajs/konva/issues/94 + if (Konva.UA.isIE) { + return this.getFontStyle() + + SPACE + + this.getFontSize() + + PX_SPACE + + this.getFontFamily(); + } + return this.getFontStyle() + + SPACE + + this.getFontVariant() + + SPACE + + this.getFontSize() + + PX_SPACE + + this.getFontFamily(); + }, + _addTextLine: function(line) { + if (this.align() === JUSTIFY) { + line = line.trim(); + } + var width = this._getTextWidth(line); + return this.textArr.push({ text: line, width: width }); + }, + _getTextWidth: function(text) { + var latterSpacing = this.getLetterSpacing(); + var length = text.length; + return dummyContext.measureText(text).width + + (length ? latterSpacing * (length - 1) : 0); + }, + _setTextData: function() { + var lines = this.getText().split('\n'), + fontSize = +this.getFontSize(), + textWidth = 0, + lineHeightPx = this.getLineHeight() * fontSize, + width = this.attrs.width, + height = this.attrs.height, + fixedWidth = width !== AUTO, + fixedHeight = height !== AUTO, + padding = this.getPadding(), + maxWidth = width - padding * 2, + maxHeightPx = height - padding * 2, + currentHeightPx = 0, + wrap = this.getWrap(), + shouldWrap = wrap !== NONE, + wrapAtWord = wrap !== CHAR && shouldWrap; + + this.textArr = []; + dummyContext.save(); + dummyContext.font = this._getContextFont(); + for (var i = 0, max = lines.length; i < max; ++i) { + var line = lines[i]; + + var lineWidth = this._getTextWidth(line); + if (fixedWidth && lineWidth > maxWidth) { + /* + * if width is fixed and line does not fit entirely + * break the line into multiple fitting lines + */ + while (line.length > 0) { + /* + * use binary search to find the longest substring that + * that would fit in the specified width + */ + var low = 0, high = line.length, match = '', matchWidth = 0; + while (low < high) { + var mid = low + high >>> 1, + substr = line.slice(0, mid + 1), + substrWidth = this._getTextWidth(substr); + if (substrWidth <= maxWidth) { + low = mid + 1; + match = substr; + matchWidth = substrWidth; + } else { + high = mid; + } + } + /* + * 'low' is now the index of the substring end + * 'match' is the substring + * 'matchWidth' is the substring width in px + */ + if (match) { + // a fitting substring was found + if (wrapAtWord) { + // try to find a space or dash where wrapping could be done + var wrapIndex = Math.max( + match.lastIndexOf(SPACE), + match.lastIndexOf(DASH) + ) + 1; + if (wrapIndex > 0) { + // re-cut the substring found at the space/dash position + low = wrapIndex; + match = match.slice(0, low); + matchWidth = this._getTextWidth(match); + } + } + this._addTextLine(match); + textWidth = Math.max(textWidth, matchWidth); + currentHeightPx += lineHeightPx; + if ( + !shouldWrap || + (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) + ) { + /* + * stop wrapping if wrapping is disabled or if adding + * one more line would overflow the fixed height + */ + break; + } + line = line.slice(low); + if (line.length > 0) { + // Check if the remaining text would fit on one line + lineWidth = this._getTextWidth(line); + if (lineWidth <= maxWidth) { + // if it does, add the line and break out of the loop + this._addTextLine(line); + currentHeightPx += lineHeightPx; + textWidth = Math.max(textWidth, lineWidth); + break; + } + } + } else { + // not even one character could fit in the element, abort + break; + } + } + } else { + // element width is automatically adjusted to max line width + this._addTextLine(line); + currentHeightPx += lineHeightPx; + textWidth = Math.max(textWidth, lineWidth); + } + // if element height is fixed, abort if adding one more line would overflow + if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) { + break; + } + } + dummyContext.restore(); + this.textHeight = fontSize; + // var maxTextWidth = 0; + // for(var j = 0; j < this.textArr.length; j++) { + // maxTextWidth = Math.max(maxTextWidth, this.textArr[j].width); + // } + this.textWidth = textWidth; + } + }; + Konva.Util.extend(Konva.Text, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Text, 'fontFamily', 'Arial'); + + /** + * get/set font family + * @name fontFamily + * @method + * @memberof Konva.Text.prototype + * @param {String} fontFamily + * @returns {String} + * @example + * // get font family + * var fontFamily = text.fontFamily(); + * + * // set font family + * text.fontFamily('Arial'); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'fontSize', 12); + + /** + * get/set font size in pixels + * @name fontSize + * @method + * @memberof Konva.Text.prototype + * @param {Number} fontSize + * @returns {Number} + * @example + * // get font size + * var fontSize = text.fontSize(); + * + * // set font size to 22px + * text.fontSize(22); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'fontStyle', NORMAL); + + /** + * set font style. Can be 'normal', 'italic', or 'bold'. 'normal' is the default. + * @name fontStyle + * @method + * @memberof Konva.Text.prototype + * @param {String} fontStyle + * @returns {String} + * @example + * // get font style + * var fontStyle = text.fontStyle(); + * + * // set font style + * text.fontStyle('bold'); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'fontVariant', NORMAL); + + /** + * set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. + * @name fontVariant + * @method + * @memberof Konva.Text.prototype + * @param {String} fontVariant + * @returns {String} + * @example + * // get font variant + * var fontVariant = text.fontVariant(); + * + * // set font variant + * text.fontVariant('small-caps'); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'padding', 0); + + /** + * set padding + * @name padding + * @method + * @memberof Konva.Text.prototype + * @param {Number} padding + * @returns {Number} + * @example + * // get padding + * var padding = text.padding(); + * + * // set padding to 10 pixels + * text.padding(10); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'align', LEFT); + + /** + * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' + * @name align + * @method + * @memberof Konva.Text.prototype + * @param {String} align + * @returns {String} + * @example + * // get text align + * var align = text.align(); + * + * // center text + * text.align('center'); + * + * // align text to right + * text.align('right'); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'lineHeight', 1); + + /** + * get/set line height. The default is 1. + * @name lineHeight + * @method + * @memberof Konva.Text.prototype + * @param {Number} lineHeight + * @returns {Number} + * @example + * // get line height + * var lineHeight = text.lineHeight(); + * + * // set the line height + * text.lineHeight(2); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'wrap', WORD); + + /** + * get/set wrap. Can be word, char, or none. Default is word. + * @name wrap + * @method + * @memberof Konva.Text.prototype + * @param {String} wrap + * @returns {String} + * @example + * // get wrap + * var wrap = text.wrap(); + * + * // set wrap + * text.wrap('word'); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'letterSpacing', 0); + + /** + * set letter spacing property. Default value is 0. + * @name letterSpacing + * @method + * @memberof Konva.TextPath.prototype + * @param {Number} letterSpacing + */ + + Konva.Factory.addGetter(Konva.Text, 'text', EMPTY_STRING); + Konva.Factory.addOverloadedGetterSetter(Konva.Text, 'text'); + + /** + * get/set text + * @name getText + * @method + * @memberof Konva.Text.prototype + * @param {String} text + * @returns {String} + * @example + * // get text + * var text = text.text(); + * + * // set text + * text.text('Hello world!'); + */ + + Konva.Factory.addGetterSetter(Konva.Text, 'textDecoration', EMPTY_STRING); + + /** + * get/set text decoration of a text. Possible values are 'underline', 'line-through' or combination of these values separated by space + * @name textDecoration + * @method + * @memberof Konva.Text.prototype + * @param {String} textDecoration + * @returns {String} + * @example + * // get text decoration + * var textDecoration = text.textDecoration(); + * + * // underline text + * text.textDecoration('underline'); + * + * // strike text + * text.textDecoration('line-through'); + * + * // underline and strike text + * text.textDecoration('underline line-through'); + */ + + Konva.Collection.mapMethods(Konva.Text); +})(); + +(function() { + 'use strict'; + /** + * Line constructor.  Lines are defined by an array of points and + * a tension + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Array} config.points + * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. + * The default is 0 + * @param {Boolean} [config.closed] defines whether or not the line shape is closed, creating a polygon or blob + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var line = new Konva.Line({ + * x: 100, + * y: 50, + * points: [73, 70, 340, 23, 450, 60, 500, 20], + * stroke: 'red', + * tension: 1 + * }); + */ + Konva.Line = function(config) { + this.___init(config); + }; + + Konva.Line.prototype = { + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = 'Line'; + + this.on( + 'pointsChange.konva tensionChange.konva closedChange.konva', + function() { + this._clearCache('tensionPoints'); + } + ); + + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var points = this.getPoints(), + length = points.length, + tension = this.getTension(), + closed = this.getClosed(), + tp, + len, + n; + + if (!length) { + return; + } + + context.beginPath(); + context.moveTo(points[0], points[1]); + + // tension + if (tension !== 0 && length > 4) { + tp = this.getTensionPoints(); + len = tp.length; + n = closed ? 0 : 4; + + if (!closed) { + context.quadraticCurveTo(tp[0], tp[1], tp[2], tp[3]); + } + + while (n < len - 2) { + context.bezierCurveTo( + tp[n++], + tp[n++], + tp[n++], + tp[n++], + tp[n++], + tp[n++] + ); + } + + if (!closed) { + context.quadraticCurveTo( + tp[len - 2], + tp[len - 1], + points[length - 2], + points[length - 1] + ); + } + } else { + // no tension + for (n = 2; n < length; n += 2) { + context.lineTo(points[n], points[n + 1]); + } + } + + // closed e.g. polygons and blobs + if (closed) { + context.closePath(); + context.fillStrokeShape(this); + } else { + // open e.g. lines and splines + context.strokeShape(this); + } + }, + getTensionPoints: function() { + return this._getCache('tensionPoints', this._getTensionPoints); + }, + _getTensionPoints: function() { + if (this.getClosed()) { + return this._getTensionPointsClosed(); + } else { + return Konva.Util._expandPoints(this.getPoints(), this.getTension()); + } + }, + _getTensionPointsClosed: function() { + var p = this.getPoints(), + len = p.length, + tension = this.getTension(), + util = Konva.Util, + firstControlPoints = util._getControlPoints( + p[len - 2], + p[len - 1], + p[0], + p[1], + p[2], + p[3], + tension + ), + lastControlPoints = util._getControlPoints( + p[len - 4], + p[len - 3], + p[len - 2], + p[len - 1], + p[0], + p[1], + tension + ), + middle = Konva.Util._expandPoints(p, tension), + tp = [firstControlPoints[2], firstControlPoints[3]] + .concat(middle) + .concat([ + lastControlPoints[0], + lastControlPoints[1], + p[len - 2], + p[len - 1], + lastControlPoints[2], + lastControlPoints[3], + firstControlPoints[0], + firstControlPoints[1], + p[0], + p[1] + ]); + + return tp; + }, + getWidth: function() { + return this.getSelfRect().width; + }, + getHeight: function() { + return this.getSelfRect().height; + }, + // overload size detection + getSelfRect: function() { + var points; + if (this.getTension() !== 0) { + points = this._getTensionPoints(); + } else { + points = this.getPoints(); + } + var minX = points[0]; + var maxX = points[0]; + var minY = points[1]; + var maxY = points[1]; + var x, y; + for (var i = 0; i < points.length / 2; i++) { + x = points[i * 2]; + y = points[i * 2 + 1]; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + return { + x: Math.round(minX), + y: Math.round(minY), + width: Math.round(maxX - minX), + height: Math.round(maxY - minY) + }; + } + }; + Konva.Util.extend(Konva.Line, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Line, 'closed', false); + + /** + * get/set closed flag. The default is false + * @name closed + * @method + * @memberof Konva.Line.prototype + * @param {Boolean} closed + * @returns {Boolean} + * @example + * // get closed flag + * var closed = line.closed(); + * + * // close the shape + * line.closed(true); + * + * // open the shape + * line.closed(false); + */ + + Konva.Factory.addGetterSetter(Konva.Line, 'tension', 0); + + /** + * get/set tension + * @name tension + * @method + * @memberof Konva.Line.prototype + * @param {Number} Higher values will result in a more curvy line. A value of 0 will result in no interpolation. + * The default is 0 + * @returns {Number} + * @example + * // get tension + * var tension = line.tension(); + * + * // set tension + * line.tension(3); + */ + + Konva.Factory.addGetterSetter(Konva.Line, 'points', []); + /** + * get/set points array + * @name points + * @method + * @memberof Konva.Line.prototype + * @param {Array} points + * @returns {Array} + * @example + * // get points + * var points = line.points(); + * + * // set points + * line.points([10, 20, 30, 40, 50, 60]); + * + * // push a new point + * line.points(line.points().concat([70, 80])); + */ + + Konva.Collection.mapMethods(Konva.Line); +})(); + +(function() { + 'use strict'; + /** + * Sprite constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} config.animation animation key + * @param {Object} config.animations animation map + * @param {Integer} [config.frameIndex] animation frame index + * @param {Image} config.image image object + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var imageObj = new Image(); + * imageObj.onload = function() { + * var sprite = new Konva.Sprite({ + * x: 200, + * y: 100, + * image: imageObj, + * animation: 'standing', + * animations: { + * standing: [ + * // x, y, width, height (6 frames) + * 0, 0, 49, 109, + * 52, 0, 49, 109, + * 105, 0, 49, 109, + * 158, 0, 49, 109, + * 210, 0, 49, 109, + * 262, 0, 49, 109 + * ], + * kicking: [ + * // x, y, width, height (6 frames) + * 0, 109, 45, 98, + * 45, 109, 45, 98, + * 95, 109, 63, 98, + * 156, 109, 70, 98, + * 229, 109, 60, 98, + * 287, 109, 41, 98 + * ] + * }, + * frameRate: 7, + * frameIndex: 0 + * }); + * }; + * imageObj.src = '/path/to/image.jpg' + */ + Konva.Sprite = function(config) { + this.___init(config); + }; + + Konva.Sprite.prototype = { + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = 'Sprite'; + + this._updated = true; + var that = this; + this.anim = new Konva.Animation(function() { + // if we don't need to redraw layer we should return false + var updated = that._updated; + that._updated = false; + return updated; + }); + this.on('animationChange.konva', function() { + // reset index when animation changes + this.frameIndex(0); + }); + this.on('frameIndexChange.konva', function() { + this._updated = true; + }); + // smooth change for frameRate + this.on('frameRateChange.konva', function() { + if (!this.anim.isRunning()) { + return; + } + clearInterval(this.interval); + this._setInterval(); + }); + + this.sceneFunc(this._sceneFunc); + this.hitFunc(this._hitFunc); + }, + _sceneFunc: function(context) { + var anim = this.getAnimation(), + index = this.frameIndex(), + ix4 = index * 4, + set = this.getAnimations()[anim], + offsets = this.frameOffsets(), + x = set[ix4 + 0], + y = set[ix4 + 1], + width = set[ix4 + 2], + height = set[ix4 + 3], + image = this.getImage(); + + if (this.hasFill() || this.hasStroke()) { + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + } + + if (image) { + if (offsets) { + var offset = offsets[anim], ix2 = index * 2; + context.drawImage( + image, + x, + y, + width, + height, + offset[ix2 + 0], + offset[ix2 + 1], + width, + height + ); + } else { + context.drawImage(image, x, y, width, height, 0, 0, width, height); + } + } + }, + _hitFunc: function(context) { + var anim = this.getAnimation(), + index = this.frameIndex(), + ix4 = index * 4, + set = this.getAnimations()[anim], + offsets = this.frameOffsets(), + width = set[ix4 + 2], + height = set[ix4 + 3]; + + context.beginPath(); + if (offsets) { + var offset = offsets[anim]; + var ix2 = index * 2; + context.rect(offset[ix2 + 0], offset[ix2 + 1], width, height); + } else { + context.rect(0, 0, width, height); + } + context.closePath(); + context.fillShape(this); + }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && + this.hasStroke(); + }, + _setInterval: function() { + var that = this; + this.interval = setInterval( + function() { + that._updateIndex(); + }, + 1000 / this.getFrameRate() + ); + }, + /** + * start sprite animation + * @method + * @memberof Konva.Sprite.prototype + */ + start: function() { + 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.setLayers(layer); + this._setInterval(); + this.anim.start(); + }, + /** + * stop sprite animation + * @method + * @memberof Konva.Sprite.prototype + */ + stop: function() { + this.anim.stop(); + clearInterval(this.interval); + }, + /** + * determine if animation of sprite is running or not. returns true or false + * @method + * @memberof Konva.Animation.prototype + * @returns {Boolean} + */ + isRunning: function() { + return this.anim.isRunning(); + }, + _updateIndex: function() { + var index = this.frameIndex(), + animation = this.getAnimation(), + animations = this.getAnimations(), + anim = animations[animation], + len = anim.length / 4; + + if (index < len - 1) { + this.frameIndex(index + 1); + } else { + this.frameIndex(0); + } + } + }; + Konva.Util.extend(Konva.Sprite, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Sprite, 'animation'); + + /** + * get/set animation key + * @name animation + * @method + * @memberof Konva.Sprite.prototype + * @param {String} anim animation key + * @returns {String} + * @example + * // get animation key + * var animation = sprite.animation(); + * + * // set animation key + * sprite.animation('kicking'); + */ + + Konva.Factory.addGetterSetter(Konva.Sprite, 'animations'); + + /** + * get/set animations map + * @name animations + * @method + * @memberof Konva.Sprite.prototype + * @param {Object} animations + * @returns {Object} + * @example + * // get animations map + * var animations = sprite.animations(); + * + * // set animations map + * sprite.animations({ + * standing: [ + * // x, y, width, height (6 frames) + * 0, 0, 49, 109, + * 52, 0, 49, 109, + * 105, 0, 49, 109, + * 158, 0, 49, 109, + * 210, 0, 49, 109, + * 262, 0, 49, 109 + * ], + * kicking: [ + * // x, y, width, height (6 frames) + * 0, 109, 45, 98, + * 45, 109, 45, 98, + * 95, 109, 63, 98, + * 156, 109, 70, 98, + * 229, 109, 60, 98, + * 287, 109, 41, 98 + * ] + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Sprite, 'frameOffsets'); + + /** + * get/set offsets map + * @name offsets + * @method + * @memberof Konva.Sprite.prototype + * @param {Object} offsets + * @returns {Object} + * @example + * // get offsets map + * var offsets = sprite.offsets(); + * + * // set offsets map + * sprite.offsets({ + * standing: [ + * // x, y (6 frames) + * 0, 0, + * 0, 0, + * 5, 0, + * 0, 0, + * 0, 3, + * 2, 0 + * ], + * kicking: [ + * // x, y (6 frames) + * 0, 5, + * 5, 0, + * 10, 0, + * 0, 0, + * 2, 1, + * 0, 0 + * ] + * }); + */ + + Konva.Factory.addGetterSetter(Konva.Sprite, 'image'); + + /** + * get/set image + * @name image + * @method + * @memberof Konva.Sprite.prototype + * @param {Image} image + * @returns {Image} + * @example + * // get image + * var image = sprite.image(); + * + * // set image + * sprite.image(imageObj); + */ + + Konva.Factory.addGetterSetter(Konva.Sprite, 'frameIndex', 0); + + /** + * set/set animation frame index + * @name frameIndex + * @method + * @memberof Konva.Sprite.prototype + * @param {Integer} frameIndex + * @returns {Integer} + * @example + * // get animation frame index + * var frameIndex = sprite.frameIndex(); + * + * // set animation frame index + * sprite.frameIndex(3); + */ + + Konva.Factory.addGetterSetter(Konva.Sprite, 'frameRate', 17); + + /** + * get/set frame rate in frames per second. Increase this number to make the sprite + * animation run faster, and decrease the number to make the sprite animation run slower + * The default is 17 frames per second + * @name frameRate + * @method + * @memberof Konva.Sprite.prototype + * @param {Integer} frameRate + * @returns {Integer} + * @example + * // get frame rate + * var frameRate = sprite.frameRate(); + * + * // set frame rate to 2 frames per second + * sprite.frameRate(2); + */ + + Konva.Factory.backCompat(Konva.Sprite, { + index: 'frameIndex', + getIndex: 'getFrameIndex', + setIndex: 'setFrameIndex' + }); + + Konva.Collection.mapMethods(Konva.Sprite); +})(); + +/*eslint-disable no-shadow, max-len, max-depth */ +(function() { + 'use strict'; + /** + * Path constructor. + * @author Jason Follas + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} config.data SVG data string + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var path = new Konva.Path({ + * x: 240, + * y: 40, + * data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z', + * fill: 'green', + * scale: 2 + * }); + */ + Konva.Path = function(config) { + this.___init(config); + }; + + Konva.Path.prototype = { + ___init: function(config) { + this.dataArray = []; + var that = this; + + // call super constructor + Konva.Shape.call(this, config); + this.className = 'Path'; + + this.dataArray = Konva.Path.parsePathData(this.getData()); + this.on('dataChange.konva', function() { + that.dataArray = Konva.Path.parsePathData(this.getData()); + }); + + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: 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; + } + } + + context.fillStrokeShape(this); + }, + getSelfRect: function() { + var points = []; + this.dataArray.forEach(function(data) { + points = points.concat(data.points); + }); + var minX = points[0]; + var maxX = points[0]; + var minY = points[1]; + var maxY = points[1]; + var x, y; + for (var i = 0; i < points.length / 2; i++) { + x = points[i * 2]; + y = points[i * 2 + 1]; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + return { + x: Math.round(minX), + y: Math.round(minY), + width: Math.round(maxX - minX), + height: Math.round(maxY - minY) + }; + } + }; + Konva.Util.extend(Konva.Path, Konva.Shape); + + Konva.Path.getLineLength = function(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + }; + Konva.Path.getPointOnLine = function(dist, P1x, P1y, P2x, P2y, fromX, fromY) { + if (fromX === undefined) { + fromX = P1x; + } + if (fromY === undefined) { + fromY = P1y; + } + + var m = (P2y - P1y) / (P2x - P1x + 0.00000001); + var run = Math.sqrt(dist * dist / (1 + m * m)); + if (P2x < P1x) { + run *= -1; + } + var rise = m * run; + var pt; + + if (P2x === P1x) { + // vertical line + pt = { + x: fromX, + y: fromY + rise + }; + } else if ((fromY - P1y) / (fromX - P1x + 0.00000001) === m) { + pt = { + x: fromX + run, + y: fromY + rise + }; + } else { + var ix, iy; + + var len = this.getLineLength(P1x, P1y, P2x, P2y); + if (len < 0.00000001) { + return undefined; + } + var u = (fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y); + u = u / (len * len); + ix = P1x + u * (P2x - P1x); + iy = P1y + u * (P2y - P1y); + + var pRise = this.getLineLength(fromX, fromY, ix, iy); + var pRun = Math.sqrt(dist * dist - pRise * pRise); + run = Math.sqrt(pRun * pRun / (1 + m * m)); + if (P2x < P1x) { + run *= -1; + } + rise = m * run; + pt = { + x: ix + run, + y: iy + rise + }; + } + + return pt; + }; + + Konva.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 + }; + }; + Konva.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 + }; + }; + Konva.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 + */ + Konva.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 (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; + // Move var from within the switch to up here (jshint) + var prevCmd, ctlPtx, ctlPty; // Ss, Tt + var rx, ry, psi, fa, fs, x1, y1; // Aa + + // 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': + var dx = p.shift(); + var dy = p.shift(); + cpx += dx; + cpy += dy; + cmd = 'M'; + // After closing the path move the current position + // to the the first point of the path (if any). + if (ca.length > 2 && ca[ca.length - 1].command === 'z') { + for (var idx = ca.length - 2; idx >= 0; idx--) { + if (ca[idx].command === 'M') { + cpx = ca[idx].points[0] + dx; + cpy = ca[idx].points[1] + dy; + break; + } + } + } + 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': + ctlPtx = cpx; + ctlPty = cpy; + 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': + ctlPtx = cpx; + ctlPty = cpy; + 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': + ctlPtx = cpx; + ctlPty = cpy; + 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': + ctlPtx = cpx; + ctlPty = cpy; + 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': + rx = p.shift(); + ry = p.shift(); + psi = p.shift(); + fa = p.shift(); + fs = p.shift(); + 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': + rx = p.shift(); + ry = p.shift(); + psi = p.shift(); + fa = p.shift(); + fs = p.shift(); + 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; + }; + Konva.Path.calcLength = function(x, y, cmd, points) { + var len, p1, p2, t; + var path = Konva.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; + }; + Konva.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 + Konva.Factory.addGetterSetter(Konva.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 + * @method + * @memberof Konva.Path.prototype + * @param {String} SVG path command string + */ + + /** + * get SVG path data string + * @name getData + * @method + * @memberof Konva.Path.prototype + */ + + Konva.Collection.mapMethods(Konva.Path); +})(); + +(function() { + 'use strict'; + var EMPTY_STRING = '', + //CALIBRI = 'Calibri', + NORMAL = 'normal'; + + /** + * Path constructor. + * @author Jason Follas + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {String} [config.fontFamily] default is Calibri + * @param {Number} [config.fontSize] default is 12 + * @param {String} [config.fontStyle] can be normal, bold, or italic. Default is normal + * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal + * @param {String} config.text + * @param {String} config.data SVG data string + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var textpath = new Konva.TextPath({ + * x: 100, + * y: 50, + * fill: '#333', + * fontSize: '24', + * fontFamily: 'Arial', + * text: 'All the world\'s a stage, and all the men and women merely players.', + * data: 'M10,10 C0,0 10,150 100,100 S300,150 400,50' + * }); + */ + Konva.TextPath = function(config) { + this.___init(config); + }; + + function _fillFunc(context) { + context.fillText(this.partialText, 0, 0); + } + function _strokeFunc(context) { + context.strokeText(this.partialText, 0, 0); + } + + Konva.TextPath.prototype = { + ___init: function(config) { + var that = this; + this.dummyCanvas = Konva.Util.createCanvasElement(); + this.dataArray = []; + + // call super constructor + Konva.Shape.call(this, config); + + // overrides + // TODO: shouldn't this be on the prototype? + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this._fillFuncHit = _fillFunc; + this._strokeFuncHit = _strokeFunc; + + this.className = 'TextPath'; + + this.dataArray = Konva.Path.parsePathData(this.attrs.data); + this.on('dataChange.konva', function() { + that.dataArray = Konva.Path.parsePathData(this.attrs.data); + that._setTextData(); + }); + + // update text data for certain attr changes + this.on( + 'textChange.konva alignChange.konva letterSpacingChange.konva', + that._setTextData + ); + that._setTextData(); + this.sceneFunc(this._sceneFunc); + this.hitFunc(this._hitFunc); + }, + _sceneFunc: function(context) { + context.setAttr('font', this._getContextFont()); + context.setAttr('textBaseline', this.getTextBaseline()); + context.setAttr('textAlign', 'left'); + context.save(); + + var textDecoration = this.textDecoration(); + var fill = this.fill(); + var fontSize = this.fontSize(); + + var glyphInfo = this.glyphInfo; + if (textDecoration === 'underline') { + context.beginPath(); + } + for (var i = 0; i < glyphInfo.length; i++) { + context.save(); + + var p0 = glyphInfo[i].p0; + + context.translate(p0.x, p0.y); + context.rotate(glyphInfo[i].rotation); + this.partialText = glyphInfo[i].text; + + context.fillStrokeShape(this); + if (textDecoration === 'underline') { + if (i === 0) { + context.moveTo(0, fontSize / 2 + 1); + } + + context.lineTo(fontSize, fontSize / 2 + 1); + } + context.restore(); + + //// To assist with debugging visually, uncomment following + // + // if (i % 2) + // context.strokeStyle = 'cyan'; + // else + // context.strokeStyle = 'green'; + // var p1 = glyphInfo[i].p1; + // context.moveTo(p0.x, p0.y); + // context.lineTo(p1.x, p1.y); + // context.stroke(); + } + if (textDecoration === 'underline') { + context.strokeStyle = fill; + context.lineWidth = fontSize / 20; + context.stroke(); + } + + context.restore(); + }, + _hitFunc: function(context) { + context.beginPath(); + + var glyphInfo = this.glyphInfo; + if (glyphInfo.length >= 1) { + var p0 = glyphInfo[0].p0; + context.moveTo(p0.x, p0.y); + } + for (var i = 0; i < glyphInfo.length; i++) { + var p1 = glyphInfo[i].p1; + context.lineTo(p1.x, p1.y); + } + context.setAttr('lineWidth', this.getFontSize()); + context.setAttr('strokeStyle', this.colorKey); + context.stroke(); + }, + /** + * get text width in pixels + * @method + * @memberof Konva.TextPath.prototype + */ + getTextWidth: function() { + return this.textWidth; + }, + /** + * get text height in pixels + * @method + * @memberof Konva.TextPath.prototype + */ + getTextHeight: function() { + return this.textHeight; + }, + /** + * set text + * @method + * @memberof Konva.TextPath.prototype + * @param {String} text + */ + setText: function(text) { + Konva.Text.prototype.setText.call(this, text); + }, + _getTextSize: function(text) { + var dummyCanvas = this.dummyCanvas; + var _context = dummyCanvas.getContext('2d'); + + _context.save(); + + _context.font = this._getContextFont(); + var metrics = _context.measureText(text); + + _context.restore(); + + return { + width: metrics.width, + height: parseInt(this.attrs.fontSize, 10) + }; + }, + _setTextData: function() { + var that = this; + var size = this._getTextSize(this.attrs.text); + var letterSpacing = this.getLetterSpacing(); + var align = this.align(); + + this.textWidth = size.width; + this.textHeight = size.height; + + var textFullWidth = Math.max( + this.textWidth + ((this.attrs.text || '').length - 1) * letterSpacing, + 0 + ); + + this.glyphInfo = []; + + var fullPathWidth = 0; + for (var l = 0; l < that.dataArray.length; l++) { + if (that.dataArray[l].pathLength > 0) { + fullPathWidth += that.dataArray[l].pathLength; + } + } + + var offset = 0; + if (align === 'center') { + offset = Math.max(0, fullPathWidth / 2 - textFullWidth / 2); + } + if (align === 'right') { + offset = Math.max(0, fullPathWidth - textFullWidth); + } + + var charArr = this.getText().split(''); + var spacesNumber = this.getText().split(' ').length - 1; + + var p0, p1, pathCmd; + + var pIndex = -1; + var currentT = 0; + // var sumLength = 0; + // for(var j = 0; j < that.dataArray.length; j++) { + // if(that.dataArray[j].pathLength > 0) { + // + // if (sumLength + that.dataArray[j].pathLength > offset) {} + // fullPathWidth += that.dataArray[j].pathLength; + // } + // } + + var getNextPathSegment = function() { + currentT = 0; + var pathData = that.dataArray; + + for (var j = pIndex + 1; j < pathData.length; j++) { + if (pathData[j].pathLength > 0) { + pIndex = j; + + return pathData[j]; + } else if (pathData[j].command === 'M') { + p0 = { + x: pathData[j].points[0], + y: pathData[j].points[1] + }; + } + } + + return {}; + }; + + var findSegmentToFitCharacter = function(c) { + var glyphWidth = that._getTextSize(c).width + letterSpacing; + + if (c === ' ' && align === 'justify') { + glyphWidth += (fullPathWidth - textFullWidth) / spacesNumber; + } + + var currLen = 0; + var attempts = 0; + + 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 ( + Konva.Path.getLineLength( + p0.x, + p0.y, + pathCmd.points[0], + pathCmd.points[1] + ) > glyphWidth + ) { + p1 = Konva.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; + } else if (glyphWidth > currLen) { + // Just in case start is 0 + currentT += Math.PI / 180.0 * dTheta / Math.abs(dTheta); + } else { + currentT -= Math.PI / 360.0 * dTheta / Math.abs(dTheta); + } + + // Credit for bug fix: @therth https://github.com/ericdrowell/KonvaJS/issues/249 + // Old code failed to render text along arc of this path: "M 50 50 a 150 50 0 0 1 250 50 l 50 0" + if ( + (dTheta < 0 && currentT < end) || + (dTheta >= 0 && currentT > end) + ) { + currentT = end; + needNewSegment = true; + } + p1 = Konva.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 = Konva.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 = Konva.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 = Konva.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); + } + + if (needNewSegment) { + needNewSegment = false; + pathCmd = undefined; + } + } + }; + + // fake search for offset, this is very bad approach + // TODO: find other way to add offset from start (for align) + var testChar = 'C'; + var glyphWidth = that._getTextSize(testChar).width + letterSpacing; + for (var k = 0; k < offset / glyphWidth; k++) { + findSegmentToFitCharacter(testChar); + if (p0 === undefined || p1 === undefined) { + break; + } + p0 = p1; + } + + 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 = Konva.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 = Konva.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; + } + }, + getSelfRect: function() { + var points = []; + + this.glyphInfo.forEach(function(info) { + points.push(info.p0.x); + points.push(info.p0.y); + points.push(info.p1.x); + points.push(info.p1.y); + }); + var minX = points[0]; + var maxX = points[0]; + var minY = points[0]; + var maxY = points[0]; + var x, y; + for (var i = 0; i < points.length / 2; i++) { + x = points[i * 2]; + y = points[i * 2 + 1]; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + var fontSize = this.fontSize(); + return { + x: Math.round(minX) - fontSize / 2, + y: Math.round(minY) - fontSize / 2, + width: Math.round(maxX - minX) + fontSize, + height: Math.round(maxY - minY) + fontSize + }; + } + }; + + // map TextPath methods to Text + Konva.TextPath.prototype._getContextFont = Konva.Text.prototype._getContextFont; + + Konva.Util.extend(Konva.TextPath, Konva.Shape); + + // add setters and getters + Konva.Factory.addGetterSetter(Konva.TextPath, 'fontFamily', 'Arial'); + + /** + * set font family + * @name setFontFamily + * @method + * @memberof Konva.TextPath.prototype + * @param {String} fontFamily + */ + + /** + * get font family + * @name getFontFamily + * @method + * @memberof Konva.TextPath.prototype + */ + + Konva.Factory.addGetterSetter(Konva.TextPath, 'fontSize', 12); + + /** + * set font size + * @name setFontSize + * @method + * @memberof Konva.TextPath.prototype + * @param {int} fontSize + */ + + /** + * get font size + * @name getFontSize + * @method + * @memberof Konva.TextPath.prototype + */ + + Konva.Factory.addGetterSetter(Konva.TextPath, 'fontStyle', NORMAL); + + /** + * set font style. Can be 'normal', 'italic', or 'bold'. 'normal' is the default. + * @name setFontStyle + * @method + * @memberof Konva.TextPath.prototype + * @param {String} fontStyle + */ + Konva.Factory.addGetterSetter(Konva.TextPath, 'align', 'left'); + + /** + * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' + * @name align + * @method + * @memberof Konva.Text.prototype + * @param {String} align + * @returns {String} + * @example + * // get text align + * var align = text.align(); + * + * // center text + * text.align('center'); + * + * // align text to right + * text.align('right'); + */ + + Konva.Factory.addGetterSetter(Konva.TextPath, 'letterSpacing', 0); + + /** + * set letter spacing property. Default value is 0. + * @name letterSpacing + * @method + * @memberof Konva.TextPath.prototype + * @param {Number} letterSpacing + */ + + Konva.Factory.addGetterSetter(Konva.TextPath, 'textBaseline', 'middle'); + + /** + * set textBaseline property. Default value is 'middle'. + * Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging' + * @name textBaseline + * @method + * @memberof Konva.TextPath.prototype + * @param {Number} textBaseline + */ + + /** + * get font style + * @name getFontStyle + * @method + * @memberof Konva.TextPath.prototype + */ + + Konva.Factory.addGetterSetter(Konva.TextPath, 'fontVariant', NORMAL); + + /** + * set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. + * @name setFontVariant + * @method + * @memberof Konva.TextPath.prototype + * @param {String} fontVariant + */ + + /** + * @get font variant + * @name getFontVariant + * @method + * @memberof Konva.TextPath.prototype + */ + + Konva.Factory.addGetter(Konva.TextPath, 'text', EMPTY_STRING); + + /** + * get text + * @name getText + * @method + * @memberof Konva.TextPath.prototype + */ + + Konva.Factory.addGetterSetter(Konva.TextPath, 'textDecoration', null); + + /** + * get/set text decoration of a text. Can be '' or 'underline' + * @name textDecoration + * @method + * @memberof Konva.Text.prototype + * @param {String} textDecoration + * @returns {String} + * @example + * // get text decoration + * var textDecoration = text.textDecoration(); + * + * // center text + * text.textDecoration('underline'); + */ + + Konva.Collection.mapMethods(Konva.TextPath); +})(); + +(function() { + 'use strict'; + /** + * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc. + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Number} config.sides + * @param {Number} config.radius + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var hexagon = new Konva.RegularPolygon({ + * x: 100, + * y: 200, + * sides: 6, + * radius: 70, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 4 + * }); + */ + Konva.RegularPolygon = function(config) { + this.___init(config); + }; + + Konva.RegularPolygon.prototype = { + _centroid: true, + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = 'RegularPolygon'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var sides = this.attrs.sides, radius = this.attrs.radius, n, x, y; + + context.beginPath(); + context.moveTo(0, 0 - radius); + + for (n = 1; n < sides; n++) { + x = radius * Math.sin(n * 2 * Math.PI / sides); + y = (-1) * radius * Math.cos(n * 2 * Math.PI / sides); + context.lineTo(x, y); + } + context.closePath(); + context.fillStrokeShape(this); + }, + getWidth: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + if (this.radius() !== width / 2) { + this.setRadius(width / 2); + } + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + if (this.radius() !== height / 2) { + this.setRadius(height / 2); + } + } + }; + Konva.Util.extend(Konva.RegularPolygon, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.RegularPolygon, 'radius', 0); + + /** + * set radius + * @name setRadius + * @method + * @memberof Konva.RegularPolygon.prototype + * @param {Number} radius + */ + + /** + * get radius + * @name getRadius + * @method + * @memberof Konva.RegularPolygon.prototype + */ + + Konva.Factory.addGetterSetter(Konva.RegularPolygon, 'sides', 0); + + /** + * set number of sides + * @name setSides + * @method + * @memberof Konva.RegularPolygon.prototype + * @param {int} sides + */ + + /** + * get number of sides + * @name getSides + * @method + * @memberof Konva.RegularPolygon.prototype + */ + + Konva.Collection.mapMethods(Konva.RegularPolygon); +})(); + +(function() { + 'use strict'; + /** + * Star constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Integer} config.numPoints + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] + * @example + * var star = new Konva.Star({ + * x: 100, + * y: 200, + * numPoints: 5, + * innerRadius: 70, + * outerRadius: 70, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 4 + * }); + */ + Konva.Star = function(config) { + this.___init(config); + }; + + Konva.Star.prototype = { + _centroid: true, + ___init: function(config) { + // call super constructor + Konva.Shape.call(this, config); + this.className = 'Star'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var innerRadius = this.innerRadius(), + outerRadius = this.outerRadius(), + numPoints = this.numPoints(); + + context.beginPath(); + context.moveTo(0, 0 - outerRadius); + + for (var n = 1; n < numPoints * 2; n++) { + var radius = n % 2 === 0 ? outerRadius : innerRadius; + var x = radius * Math.sin(n * Math.PI / numPoints); + var y = (-1) * radius * Math.cos(n * Math.PI / numPoints); + context.lineTo(x, y); + } + context.closePath(); + + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + if (this.outerRadius() !== width / 2) { + this.setOuterRadius(width / 2); + } + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + if (this.outerRadius() !== height / 2) { + this.setOuterRadius(height / 2); + } + } + }; + Konva.Util.extend(Konva.Star, Konva.Shape); + + // add getters setters + Konva.Factory.addGetterSetter(Konva.Star, 'numPoints', 5); + + /** + * set number of points + * @name setNumPoints + * @method + * @memberof Konva.Star.prototype + * @param {Integer} points + */ + + /** + * get number of points + * @name getNumPoints + * @method + * @memberof Konva.Star.prototype + */ + + Konva.Factory.addGetterSetter(Konva.Star, 'innerRadius', 0); + + /** + * set inner radius + * @name setInnerRadius + * @method + * @memberof Konva.Star.prototype + * @param {Number} radius + */ + + /** + * get inner radius + * @name getInnerRadius + * @method + * @memberof Konva.Star.prototype + */ + + Konva.Factory.addGetterSetter(Konva.Star, 'outerRadius', 0); + + /** + * set outer radius + * @name setOuterRadius + * @method + * @memberof Konva.Star.prototype + * @param {Number} radius + */ - for (i = 0; i < len; i += 4) { - // red - data[i] = 255 - data[i]; - // green - data[i + 1] = 255 - data[i + 1]; - // blue - data[i + 2] = 255 - data[i + 2]; - } - }; + /** + * get outer radius + * @name getOuterRadius + * @method + * @memberof Konva.Star.prototype + */ + + Konva.Collection.mapMethods(Konva.Star); })(); - -/* - the Gauss filter - master repo: https://github.com/pavelpower/kineticjsGaussFilter -*/ -(function() { - 'use strict'; - /* - - StackBlur - a fast almost Gaussian Blur For Canvas - - Version: 0.5 - Author: Mario Klingemann - Contact: mario@quasimondo.com - Website: http://www.quasimondo.com/StackBlurForCanvas - Twitter: @quasimondo - - In case you find this class useful - especially in commercial projects - - I am not totally unhappy for a small donation to my PayPal account - mario@quasimondo.de - - Or support me on flattr: - https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript - - Copyright (c) 2010 Mario Klingemann - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - */ - - function BlurStack() { - this.r = 0; - this.g = 0; - this.b = 0; - this.a = 0; - this.next = null; - } - - var mul_table = [ - 512, - 512, - 456, - 512, - 328, - 456, - 335, - 512, - 405, - 328, - 271, - 456, - 388, - 335, - 292, - 512, - 454, - 405, - 364, - 328, - 298, - 271, - 496, - 456, - 420, - 388, - 360, - 335, - 312, - 292, - 273, - 512, - 482, - 454, - 428, - 405, - 383, - 364, - 345, - 328, - 312, - 298, - 284, - 271, - 259, - 496, - 475, - 456, - 437, - 420, - 404, - 388, - 374, - 360, - 347, - 335, - 323, - 312, - 302, - 292, - 282, - 273, - 265, - 512, - 497, - 482, - 468, - 454, - 441, - 428, - 417, - 405, - 394, - 383, - 373, - 364, - 354, - 345, - 337, - 328, - 320, - 312, - 305, - 298, - 291, - 284, - 278, - 271, - 265, - 259, - 507, - 496, - 485, - 475, - 465, - 456, - 446, - 437, - 428, - 420, - 412, - 404, - 396, - 388, - 381, - 374, - 367, - 360, - 354, - 347, - 341, - 335, - 329, - 323, - 318, - 312, - 307, - 302, - 297, - 292, - 287, - 282, - 278, - 273, - 269, - 265, - 261, - 512, - 505, - 497, - 489, - 482, - 475, - 468, - 461, - 454, - 447, - 441, - 435, - 428, - 422, - 417, - 411, - 405, - 399, - 394, - 389, - 383, - 378, - 373, - 368, - 364, - 359, - 354, - 350, - 345, - 341, - 337, - 332, - 328, - 324, - 320, - 316, - 312, - 309, - 305, - 301, - 298, - 294, - 291, - 287, - 284, - 281, - 278, - 274, - 271, - 268, - 265, - 262, - 259, - 257, - 507, - 501, - 496, - 491, - 485, - 480, - 475, - 470, - 465, - 460, - 456, - 451, - 446, - 442, - 437, - 433, - 428, - 424, - 420, - 416, - 412, - 408, - 404, - 400, - 396, - 392, - 388, - 385, - 381, - 377, - 374, - 370, - 367, - 363, - 360, - 357, - 354, - 350, - 347, - 344, - 341, - 338, - 335, - 332, - 329, - 326, - 323, - 320, - 318, - 315, - 312, - 310, - 307, - 304, - 302, - 299, - 297, - 294, - 292, - 289, - 287, - 285, - 282, - 280, - 278, - 275, - 273, - 271, - 269, - 267, - 265, - 263, - 261, - 259 - ]; - - var shg_table = [ - 9, - 11, - 12, - 13, - 13, - 14, - 14, - 15, - 15, - 15, - 15, - 16, - 16, - 16, - 16, - 17, - 17, - 17, - 17, - 17, - 17, - 17, - 18, - 18, - 18, - 18, - 18, - 18, - 18, - 18, - 18, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 19, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 21, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 23, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24 - ]; - - function filterGaussBlurRGBA(imageData, radius) { - var pixels = imageData.data, - width = imageData.width, - height = imageData.height; - - var x, - y, - i, - p, - yp, - yi, - yw, - r_sum, - g_sum, - b_sum, - a_sum, - r_out_sum, - g_out_sum, - b_out_sum, - a_out_sum, - r_in_sum, - g_in_sum, - b_in_sum, - a_in_sum, - pr, - pg, - pb, - pa, - rbs; - - var div = radius + radius + 1, - widthMinus1 = width - 1, - heightMinus1 = height - 1, - radiusPlus1 = radius + 1, - sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2, - stackStart = new BlurStack(), - stackEnd = null, - stack = stackStart, - stackIn = null, - stackOut = null, - mul_sum = mul_table[radius], - shg_sum = shg_table[radius]; - - for (i = 1; i < div; i++) { - stack = stack.next = new BlurStack(); - if (i === radiusPlus1) { - stackEnd = stack; - } - } - - stack.next = stackStart; - - yw = yi = 0; - - for (y = 0; y < height; y++) { - r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0; - - r_out_sum = radiusPlus1 * (pr = pixels[yi]); - g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); - b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); - a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); - - r_sum += sumFactor * pr; - g_sum += sumFactor * pg; - b_sum += sumFactor * pb; - a_sum += sumFactor * pa; - - stack = stackStart; - - for (i = 0; i < radiusPlus1; i++) { - stack.r = pr; - stack.g = pg; - stack.b = pb; - stack.a = pa; - stack = stack.next; - } - - for (i = 1; i < radiusPlus1; i++) { - p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); - r_sum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i); - g_sum += (stack.g = pg = pixels[p + 1]) * rbs; - b_sum += (stack.b = pb = pixels[p + 2]) * rbs; - a_sum += (stack.a = pa = pixels[p + 3]) * rbs; - - r_in_sum += pr; - g_in_sum += pg; - b_in_sum += pb; - a_in_sum += pa; - - stack = stack.next; - } - - stackIn = stackStart; - stackOut = stackEnd; - for (x = 0; x < width; x++) { - pixels[yi + 3] = pa = a_sum * mul_sum >> shg_sum; - if (pa !== 0) { - pa = 255 / pa; - pixels[yi] = (r_sum * mul_sum >> shg_sum) * pa; - pixels[yi + 1] = (g_sum * mul_sum >> shg_sum) * pa; - pixels[yi + 2] = (b_sum * mul_sum >> shg_sum) * pa; - } else { - pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0; - } - - r_sum -= r_out_sum; - g_sum -= g_out_sum; - b_sum -= b_out_sum; - a_sum -= a_out_sum; - - r_out_sum -= stackIn.r; - g_out_sum -= stackIn.g; - b_out_sum -= stackIn.b; - a_out_sum -= stackIn.a; - - p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2; - - r_in_sum += stackIn.r = pixels[p]; - g_in_sum += stackIn.g = pixels[p + 1]; - b_in_sum += stackIn.b = pixels[p + 2]; - a_in_sum += stackIn.a = pixels[p + 3]; - - r_sum += r_in_sum; - g_sum += g_in_sum; - b_sum += b_in_sum; - a_sum += a_in_sum; - - stackIn = stackIn.next; - - r_out_sum += pr = stackOut.r; - g_out_sum += pg = stackOut.g; - b_out_sum += pb = stackOut.b; - a_out_sum += pa = stackOut.a; - - r_in_sum -= pr; - g_in_sum -= pg; - b_in_sum -= pb; - a_in_sum -= pa; - - stackOut = stackOut.next; - - yi += 4; - } - yw += width; - } - - for (x = 0; x < width; x++) { - g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0; - - yi = x << 2; - r_out_sum = radiusPlus1 * (pr = pixels[yi]); - g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); - b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); - a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); - - r_sum += sumFactor * pr; - g_sum += sumFactor * pg; - b_sum += sumFactor * pb; - a_sum += sumFactor * pa; - - stack = stackStart; - - for (i = 0; i < radiusPlus1; i++) { - stack.r = pr; - stack.g = pg; - stack.b = pb; - stack.a = pa; - stack = stack.next; - } - - yp = width; - - for (i = 1; i <= radius; i++) { - yi = yp + x << 2; - - r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i); - g_sum += (stack.g = pg = pixels[yi + 1]) * rbs; - b_sum += (stack.b = pb = pixels[yi + 2]) * rbs; - a_sum += (stack.a = pa = pixels[yi + 3]) * rbs; - - r_in_sum += pr; - g_in_sum += pg; - b_in_sum += pb; - a_in_sum += pa; - - stack = stack.next; - - if (i < heightMinus1) { - yp += width; - } - } - - yi = x; - stackIn = stackStart; - stackOut = stackEnd; - for (y = 0; y < height; y++) { - p = yi << 2; - pixels[p + 3] = pa = a_sum * mul_sum >> shg_sum; - if (pa > 0) { - pa = 255 / pa; - pixels[p] = (r_sum * mul_sum >> shg_sum) * pa; - pixels[p + 1] = (g_sum * mul_sum >> shg_sum) * pa; - pixels[p + 2] = (b_sum * mul_sum >> shg_sum) * pa; - } else { - pixels[p] = pixels[p + 1] = pixels[p + 2] = 0; - } - - r_sum -= r_out_sum; - g_sum -= g_out_sum; - b_sum -= b_out_sum; - a_sum -= a_out_sum; - - r_out_sum -= stackIn.r; - g_out_sum -= stackIn.g; - b_out_sum -= stackIn.b; - a_out_sum -= stackIn.a; - - p = x + - ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << - 2; - - r_sum += r_in_sum += stackIn.r = pixels[p]; - g_sum += g_in_sum += stackIn.g = pixels[p + 1]; - b_sum += b_in_sum += stackIn.b = pixels[p + 2]; - a_sum += a_in_sum += stackIn.a = pixels[p + 3]; - - stackIn = stackIn.next; - - r_out_sum += pr = stackOut.r; - g_out_sum += pg = stackOut.g; - b_out_sum += pb = stackOut.b; - a_out_sum += pa = stackOut.a; - - r_in_sum -= pr; - g_in_sum -= pg; - b_in_sum -= pb; - a_in_sum -= pa; - - stackOut = stackOut.next; - - yi += width; - } - } - } - - /** - * Blur Filter - * @function - * @name Blur - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Blur]); - * node.blurRadius(10); - */ - Konva.Filters.Blur = function Blur(imageData) { - var radius = Math.round(this.blurRadius()); - - if (radius > 0) { - filterGaussBlurRGBA(imageData, radius); - } - }; - - Konva.Factory.addGetterSetter( - Konva.Node, - 'blurRadius', - 0, - null, - Konva.Factory.afterSetFilter - ); - - /** - * get/set blur radius. Use with {@link Konva.Filters.Blur} filter - * @name blurRadius - * @method - * @memberof Konva.Node.prototype - * @param {Integer} radius - * @returns {Integer} - */ -})(); - -/*eslint-disable max-depth */ -(function() { - 'use strict'; - function pixelAt(idata, x, y) { - var idx = (y * idata.width + x) * 4; - var d = []; - d.push( - idata.data[idx++], - idata.data[idx++], - idata.data[idx++], - idata.data[idx++] - ); - return d; - } - - function rgbDistance(p1, p2) { - return Math.sqrt( - Math.pow(p1[0] - p2[0], 2) + - Math.pow(p1[1] - p2[1], 2) + - Math.pow(p1[2] - p2[2], 2) - ); - } - - function rgbMean(pTab) { - var m = [0, 0, 0]; - - for (var i = 0; i < pTab.length; i++) { - m[0] += pTab[i][0]; - m[1] += pTab[i][1]; - m[2] += pTab[i][2]; - } - - m[0] /= pTab.length; - m[1] /= pTab.length; - m[2] /= pTab.length; - - return m; - } - - function backgroundMask(idata, threshold) { - var rgbv_no = pixelAt(idata, 0, 0); - var rgbv_ne = pixelAt(idata, idata.width - 1, 0); - var rgbv_so = pixelAt(idata, 0, idata.height - 1); - var rgbv_se = pixelAt(idata, idata.width - 1, idata.height - 1); - - var thres = threshold || 10; - if ( - rgbDistance(rgbv_no, rgbv_ne) < thres && - rgbDistance(rgbv_ne, rgbv_se) < thres && - rgbDistance(rgbv_se, rgbv_so) < thres && - rgbDistance(rgbv_so, rgbv_no) < thres - ) { - // Mean color - var mean = rgbMean([rgbv_ne, rgbv_no, rgbv_se, rgbv_so]); - - // Mask based on color distance - var mask = []; - for (var i = 0; i < idata.width * idata.height; i++) { - var d = rgbDistance(mean, [ - idata.data[i * 4], - idata.data[i * 4 + 1], - idata.data[i * 4 + 2] - ]); - mask[i] = d < thres ? 0 : 255; - } - - return mask; - } - } - - function applyMask(idata, mask) { - for (var i = 0; i < idata.width * idata.height; i++) { - idata.data[4 * i + 3] = mask[i]; - } - } - - function erodeMask(mask, sw, sh) { - var weights = [1, 1, 1, 1, 0, 1, 1, 1, 1]; - var side = Math.round(Math.sqrt(weights.length)); - var halfSide = Math.floor(side / 2); - - var maskResult = []; - for (var y = 0; y < sh; y++) { - for (var x = 0; x < sw; x++) { - var so = y * sw + x; - var a = 0; - for (var cy = 0; cy < side; cy++) { - for (var cx = 0; cx < side; cx++) { - var scy = y + cy - halfSide; - var scx = x + cx - halfSide; - - if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { - var srcOff = scy * sw + scx; - var wt = weights[cy * side + cx]; - - a += mask[srcOff] * wt; - } - } - } - - maskResult[so] = a === 255 * 8 ? 255 : 0; - } - } - - return maskResult; - } - - function dilateMask(mask, sw, sh) { - var weights = [1, 1, 1, 1, 1, 1, 1, 1, 1]; - var side = Math.round(Math.sqrt(weights.length)); - var halfSide = Math.floor(side / 2); - - var maskResult = []; - for (var y = 0; y < sh; y++) { - for (var x = 0; x < sw; x++) { - var so = y * sw + x; - var a = 0; - for (var cy = 0; cy < side; cy++) { - for (var cx = 0; cx < side; cx++) { - var scy = y + cy - halfSide; - var scx = x + cx - halfSide; - - if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { - var srcOff = scy * sw + scx; - var wt = weights[cy * side + cx]; - - a += mask[srcOff] * wt; - } - } - } - - maskResult[so] = a >= 255 * 4 ? 255 : 0; - } - } - - return maskResult; - } - - function smoothEdgeMask(mask, sw, sh) { - var weights = [ - 1 / 9, - 1 / 9, - 1 / 9, - 1 / 9, - 1 / 9, - 1 / 9, - 1 / 9, - 1 / 9, - 1 / 9 - ]; - var side = Math.round(Math.sqrt(weights.length)); - var halfSide = Math.floor(side / 2); - - var maskResult = []; - for (var y = 0; y < sh; y++) { - for (var x = 0; x < sw; x++) { - var so = y * sw + x; - var a = 0; - for (var cy = 0; cy < side; cy++) { - for (var cx = 0; cx < side; cx++) { - var scy = y + cy - halfSide; - var scx = x + cx - halfSide; - - if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { - var srcOff = scy * sw + scx; - var wt = weights[cy * side + cx]; - - a += mask[srcOff] * wt; - } - } - } - - maskResult[so] = a; - } - } - - return maskResult; - } - - /** - * Mask Filter - * @function - * @name Mask - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Mask]); - * node.threshold(200); - */ - Konva.Filters.Mask = function(imageData) { - // Detect pixels close to the background color - var threshold = this.threshold(), - mask = backgroundMask(imageData, threshold); - if (mask) { - // Erode - mask = erodeMask(mask, imageData.width, imageData.height); - - // Dilate - mask = dilateMask(mask, imageData.width, imageData.height); - - // Gradient - mask = smoothEdgeMask(mask, imageData.width, imageData.height); - - // Apply mask - applyMask(imageData, mask); - - // todo : Update hit region function according to mask - } - - return imageData; - }; - - Konva.Factory.addGetterSetter( - Konva.Node, - 'threshold', - 0, - null, - Konva.Factory.afterSetFilter - ); -})(); - -(function() { - 'use strict'; - /** - * RGB Filter - * @function - * @name RGB - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.RGB]); - * node.blue(120); - * node.green(200); - */ - Konva.Filters.RGB = function(imageData) { - var data = imageData.data, - nPixels = data.length, - red = this.red(), - green = this.green(), - blue = this.blue(), - i, - brightness; - - for (i = 0; i < nPixels; i += 4) { - brightness = (0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]) / - 255; - data[i] = brightness * red; // r - data[i + 1] = brightness * green; // g - data[i + 2] = brightness * blue; // b - data[i + 3] = data[i + 3]; // alpha - } - }; - - Konva.Factory.addGetterSetter(Konva.Node, 'red', 0, function(val) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } - }); - /** - * get/set filter red value. Use with {@link Konva.Filters.RGB} filter. - * @name red - * @method - * @memberof Konva.Node.prototype - * @param {Integer} red value between 0 and 255 - * @returns {Integer} - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'green', 0, function(val) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } - }); - /** - * get/set filter green value. Use with {@link Konva.Filters.RGB} filter. - * @name green - * @method - * @memberof Konva.Node.prototype - * @param {Integer} green value between 0 and 255 - * @returns {Integer} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'blue', - 0, - Konva.Validators.RGBComponent, - Konva.Factory.afterSetFilter - ); - /** - * get/set filter blue value. Use with {@link Konva.Filters.RGB} filter. - * @name blue - * @method - * @memberof Konva.Node.prototype - * @param {Integer} blue value between 0 and 255 - * @returns {Integer} - */ -})(); - -(function() { - 'use strict'; - /** - * RGBA Filter - * @function - * @name RGBA - * @memberof Konva.Filters - * @param {Object} imageData - * @author codefo - * @example - * node.cache(); - * node.filters([Konva.Filters.RGBA]); - * node.blue(120); - * node.green(200); - * node.alpha(0.3); - */ - Konva.Filters.RGBA = function(imageData) { - var data = imageData.data, - nPixels = data.length, - red = this.red(), - green = this.green(), - blue = this.blue(), - alpha = this.alpha(), - i, - ia; - - for (i = 0; i < nPixels; i += 4) { - ia = 1 - alpha; - - data[i] = red * alpha + data[i] * ia; // r - data[i + 1] = green * alpha + data[i + 1] * ia; // g - data[i + 2] = blue * alpha + data[i + 2] * ia; // b - } - }; - - Konva.Factory.addGetterSetter(Konva.Node, 'red', 0, function(val) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } - }); - /** - * get/set filter red value. Use with {@link Konva.Filters.RGBA} filter. - * @name red - * @method - * @memberof Konva.Node.prototype - * @param {Integer} red value between 0 and 255 - * @returns {Integer} - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'green', 0, function(val) { - this._filterUpToDate = false; - if (val > 255) { - return 255; - } else if (val < 0) { - return 0; - } else { - return Math.round(val); - } - }); - /** - * get/set filter green value. Use with {@link Konva.Filters.RGBA} filter. - * @name green - * @method - * @memberof Konva.Node.prototype - * @param {Integer} green value between 0 and 255 - * @returns {Integer} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'blue', - 0, - Konva.Validators.RGBComponent, - Konva.Factory.afterSetFilter - ); - /** - * get/set filter blue value. Use with {@link Konva.Filters.RGBA} filter. - * @name blue - * @method - * @memberof Konva.Node.prototype - * @param {Integer} blue value between 0 and 255 - * @returns {Integer} - */ - - Konva.Factory.addGetterSetter(Konva.Node, 'alpha', 1, function(val) { - this._filterUpToDate = false; - if (val > 1) { - return 1; - } else if (val < 0) { - return 0; - } else { - return val; - } - }); - /** - * get/set filter alpha value. Use with {@link Konva.Filters.RGBA} filter. - * @name alpha - * @method - * @memberof Konva.Node.prototype - * @param {Float} alpha value between 0 and 1 - * @returns {Float} - */ -})(); - -(function() { - 'use strict'; - /** - * HSV Filter. Adjusts the hue, saturation and value - * @function - * @name HSV - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * image.filters([Konva.Filters.HSV]); - * image.value(200); - */ - - Konva.Filters.HSV = function(imageData) { - var data = imageData.data, - nPixels = data.length, - v = Math.pow(2, this.value()), - s = Math.pow(2, this.saturation()), - h = Math.abs(this.hue() + 360) % 360, - i; - - // Basis for the technique used: - // http://beesbuzz.biz/code/hsv_color_transforms.php - // V is the value multiplier (1 for none, 2 for double, 0.5 for half) - // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) - // H is the hue shift in degrees (0 to 360) - // vsu = V*S*cos(H*PI/180); - // vsw = V*S*sin(H*PI/180); - //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] - //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] - //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] - - // Precompute the values in the matrix: - var vsu = v * s * Math.cos(h * Math.PI / 180), - vsw = v * s * Math.sin(h * Math.PI / 180); - // (result spot)(source spot) - var rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, - rg = 0.587 * v - 0.587 * vsu + 0.330 * vsw, - rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; - var gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, - gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, - gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; - var br = 0.299 * v - 0.300 * vsu + 1.250 * vsw, - bg = 0.587 * v - 0.586 * vsu - 1.050 * vsw, - bb = 0.114 * v + 0.886 * vsu - 0.200 * vsw; - - var r, g, b, a; - - for (i = 0; i < nPixels; i += 4) { - r = data[i + 0]; - g = data[i + 1]; - b = data[i + 2]; - a = data[i + 3]; - - data[i + 0] = rr * r + rg * g + rb * b; - data[i + 1] = gr * r + gg * g + gb * b; - data[i + 2] = br * r + bg * g + bb * b; - data[i + 3] = a; // alpha - } - }; - - Konva.Factory.addGetterSetter( - Konva.Node, - 'hue', - 0, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name hue - * @method - * @memberof Konva.Node.prototype - * @param {Number} hue value between 0 and 359 - * @returns {Number} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'saturation', - 0, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name saturation - * @method - * @memberof Konva.Node.prototype - * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. - * @returns {Number} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'value', - 0, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set hsv value. Use with {@link Konva.Filters.HSV} filter. - * @name value - * @method - * @memberof Konva.Node.prototype - * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. - * @returns {Number} - */ -})(); - -(function() { - 'use strict'; - Konva.Factory.addGetterSetter( - Konva.Node, - 'hue', - 0, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name hue - * @method - * @memberof Konva.Node.prototype - * @param {Number} hue value between 0 and 359 - * @returns {Number} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'saturation', - 0, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter. - * @name saturation - * @method - * @memberof Konva.Node.prototype - * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. - * @returns {Number} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'luminance', - 0, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set hsl luminance. Use with {@link Konva.Filters.HSL} filter. - * @name value - * @method - * @memberof Konva.Node.prototype - * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. - * @returns {Number} - */ - - /** - * HSL Filter. Adjusts the hue, saturation and luminance (or lightness) - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * image.filters([Konva.Filters.HSL]); - * image.luminance(200); - */ - - Konva.Filters.HSL = function(imageData) { - var data = imageData.data, - nPixels = data.length, - v = 1, - s = Math.pow(2, this.saturation()), - h = Math.abs(this.hue() + 360) % 360, - l = this.luminance() * 127, - i; - - // Basis for the technique used: - // http://beesbuzz.biz/code/hsv_color_transforms.php - // V is the value multiplier (1 for none, 2 for double, 0.5 for half) - // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) - // H is the hue shift in degrees (0 to 360) - // vsu = V*S*cos(H*PI/180); - // vsw = V*S*sin(H*PI/180); - //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] - //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] - //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] - - // Precompute the values in the matrix: - var vsu = v * s * Math.cos(h * Math.PI / 180), - vsw = v * s * Math.sin(h * Math.PI / 180); - // (result spot)(source spot) - var rr = 0.299 * v + 0.701 * vsu + 0.167 * vsw, - rg = 0.587 * v - 0.587 * vsu + 0.330 * vsw, - rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw; - var gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, - gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, - gb = 0.114 * v - 0.114 * vsu + 0.293 * vsw; - var br = 0.299 * v - 0.300 * vsu + 1.250 * vsw, - bg = 0.587 * v - 0.586 * vsu - 1.050 * vsw, - bb = 0.114 * v + 0.886 * vsu - 0.200 * vsw; - - var r, g, b, a; - - for (i = 0; i < nPixels; i += 4) { - r = data[i + 0]; - g = data[i + 1]; - b = data[i + 2]; - a = data[i + 3]; - - data[i + 0] = rr * r + rg * g + rb * b + l; - data[i + 1] = gr * r + gg * g + gb * b + l; - data[i + 2] = br * r + bg * g + bb * b + l; - data[i + 3] = a; // alpha - } - }; -})(); - -(function() { - 'use strict'; - /** - * Emboss Filter. - * Pixastic Lib - Emboss filter - v0.1.0 - * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ - * License: [http://www.pixastic.com/lib/license.txt] - * @function - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Emboss]); - * node.embossStrength(0.8); - * node.embossWhiteLevel(0.3); - * node.embossDirection('right'); - * node.embossBlend(true); - */ - Konva.Filters.Emboss = function(imageData) { - // pixastic strength is between 0 and 10. I want it between 0 and 1 - // pixastic greyLevel is between 0 and 255. I want it between 0 and 1. Also, - // a max value of greyLevel yields a white emboss, and the min value yields a black - // emboss. Therefore, I changed greyLevel to whiteLevel - var strength = this.embossStrength() * 10, - greyLevel = this.embossWhiteLevel() * 255, - direction = this.embossDirection(), - blend = this.embossBlend(), - dirY = 0, - dirX = 0, - data = imageData.data, - w = imageData.width, - h = imageData.height, - w4 = w * 4, - y = h; - - switch (direction) { - case 'top-left': - dirY = -1; - dirX = -1; - break; - case 'top': - dirY = -1; - dirX = 0; - break; - case 'top-right': - dirY = -1; - dirX = 1; - break; - case 'right': - dirY = 0; - dirX = 1; - break; - case 'bottom-right': - dirY = 1; - dirX = 1; - break; - case 'bottom': - dirY = 1; - dirX = 0; - break; - case 'bottom-left': - dirY = 1; - dirX = -1; - break; - case 'left': - dirY = 0; - dirX = -1; - break; - default: - Konva.Util.error('Unknown emboss direction: ' + direction); - } - - do { - var offsetY = (y - 1) * w4; - - var otherY = dirY; - if (y + otherY < 1) { - otherY = 0; - } - if (y + otherY > h) { - otherY = 0; - } - - var offsetYOther = (y - 1 + otherY) * w * 4; - - var x = w; - do { - var offset = offsetY + (x - 1) * 4; - - var otherX = dirX; - if (x + otherX < 1) { - otherX = 0; - } - if (x + otherX > w) { - otherX = 0; - } - - var offsetOther = offsetYOther + (x - 1 + otherX) * 4; - - var dR = data[offset] - data[offsetOther]; - var dG = data[offset + 1] - data[offsetOther + 1]; - var dB = data[offset + 2] - data[offsetOther + 2]; - - var dif = dR; - var absDif = dif > 0 ? dif : -dif; - - var absG = dG > 0 ? dG : -dG; - var absB = dB > 0 ? dB : -dB; - - if (absG > absDif) { - dif = dG; - } - if (absB > absDif) { - dif = dB; - } - - dif *= strength; - - if (blend) { - var r = data[offset] + dif; - var g = data[offset + 1] + dif; - var b = data[offset + 2] + dif; - - data[offset] = r > 255 ? 255 : r < 0 ? 0 : r; - data[offset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; - data[offset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; - } else { - var grey = greyLevel - dif; - if (grey < 0) { - grey = 0; - } else if (grey > 255) { - grey = 255; - } - - data[offset] = data[offset + 1] = data[offset + 2] = grey; - } - } while (--x); - } while (--y); - }; - - Konva.Factory.addGetterSetter( - Konva.Node, - 'embossStrength', - 0.5, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set emboss strength. Use with {@link Konva.Filters.Emboss} filter. - * @name embossStrength - * @method - * @memberof Konva.Node.prototype - * @param {Number} level between 0 and 1. Default is 0.5 - * @returns {Number} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'embossWhiteLevel', - 0.5, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set emboss white level. Use with {@link Konva.Filters.Emboss} filter. - * @name embossWhiteLevel - * @method - * @memberof Konva.Node.prototype - * @param {Number} embossWhiteLevel between 0 and 1. Default is 0.5 - * @returns {Number} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'embossDirection', - 'top-left', - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set emboss direction. Use with {@link Konva.Filters.Emboss} filter. - * @name embossDirection - * @method - * @memberof Konva.Node.prototype - * @param {String} embossDirection can be top-left, top, top-right, right, bottom-right, bottom, bottom-left or left - * The default is top-left - * @returns {String} - */ - - Konva.Factory.addGetterSetter( - Konva.Node, - 'embossBlend', - false, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set emboss blend. Use with {@link Konva.Filters.Emboss} filter. - * @name embossBlend - * @method - * @memberof Konva.Node.prototype - * @param {Boolean} embossBlend - * @returns {Boolean} - */ -})(); - -(function() { - 'use strict'; - function remap(fromValue, fromMin, fromMax, toMin, toMax) { - // Compute the range of the data - var fromRange = fromMax - fromMin, toRange = toMax - toMin, toValue; - - // If either range is 0, then the value can only be mapped to 1 value - if (fromRange === 0) { - return toMin + toRange / 2; - } - if (toRange === 0) { - return toMin; - } - - // (1) untranslate, (2) unscale, (3) rescale, (4) retranslate - toValue = (fromValue - fromMin) / fromRange; - toValue = toRange * toValue + toMin; - - return toValue; - } - - /** - * Enhance Filter. Adjusts the colors so that they span the widest - * possible range (ie 0-255). Performs w*h pixel reads and w*h pixel - * writes. - * @function - * @name Enhance - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.Enhance]); - * node.enhance(0.4); - */ - Konva.Filters.Enhance = function(imageData) { - var data = imageData.data, - nSubPixels = data.length, - rMin = data[0], - rMax = rMin, - r, - gMin = data[1], - gMax = gMin, - g, - bMin = data[2], - bMax = bMin, - b, - i; - - // If we are not enhancing anything - don't do any computation - var enhanceAmount = this.enhance(); - if (enhanceAmount === 0) { - return; - } - - // 1st Pass - find the min and max for each channel: - for (i = 0; i < nSubPixels; i += 4) { - r = data[i + 0]; - if (r < rMin) { - rMin = r; - } else if (r > rMax) { - rMax = r; - } - g = data[i + 1]; - if (g < gMin) { - gMin = g; - } else if (g > gMax) { - gMax = g; - } - b = data[i + 2]; - if (b < bMin) { - bMin = b; - } else if (b > bMax) { - bMax = b; - } - //a = data[i + 3]; - //if (a < aMin) { aMin = a; } else - //if (a > aMax) { aMax = a; } - } - - // If there is only 1 level - don't remap - if (rMax === rMin) { - rMax = 255; - rMin = 0; - } - if (gMax === gMin) { - gMax = 255; - gMin = 0; - } - if (bMax === bMin) { - bMax = 255; - bMin = 0; - } - - var rMid, - rGoalMax, - rGoalMin, - gMid, - gGoalMax, - gGoalMin, - bMid, - bGoalMax, - bGoalMin; - - // If the enhancement is positive - stretch the histogram - if (enhanceAmount > 0) { - rGoalMax = rMax + enhanceAmount * (255 - rMax); - rGoalMin = rMin - enhanceAmount * (rMin - 0); - gGoalMax = gMax + enhanceAmount * (255 - gMax); - gGoalMin = gMin - enhanceAmount * (gMin - 0); - bGoalMax = bMax + enhanceAmount * (255 - bMax); - bGoalMin = bMin - enhanceAmount * (bMin - 0); - // If the enhancement is negative - compress the histogram - } else { - rMid = (rMax + rMin) * 0.5; - rGoalMax = rMax + enhanceAmount * (rMax - rMid); - rGoalMin = rMin + enhanceAmount * (rMin - rMid); - gMid = (gMax + gMin) * 0.5; - gGoalMax = gMax + enhanceAmount * (gMax - gMid); - gGoalMin = gMin + enhanceAmount * (gMin - gMid); - bMid = (bMax + bMin) * 0.5; - bGoalMax = bMax + enhanceAmount * (bMax - bMid); - bGoalMin = bMin + enhanceAmount * (bMin - bMid); - } - - // Pass 2 - remap everything, except the alpha - for (i = 0; i < nSubPixels; i += 4) { - data[i + 0] = remap(data[i + 0], rMin, rMax, rGoalMin, rGoalMax); - data[i + 1] = remap(data[i + 1], gMin, gMax, gGoalMin, gGoalMax); - data[i + 2] = remap(data[i + 2], bMin, bMax, bGoalMin, bGoalMax); - //data[i + 3] = remap(data[i + 3], aMin, aMax, aGoalMin, aGoalMax); - } - }; - - Konva.Factory.addGetterSetter( - Konva.Node, - 'enhance', - 0, - null, - Konva.Factory.afterSetFilter - ); - - /** - * get/set enhance. Use with {@link Konva.Filters.Enhance} filter. - * @name enhance - * @method - * @memberof Konva.Node.prototype - * @param {Float} amount - * @returns {Float} - */ -})(); - -(function() { - 'use strict'; - /** - * Posterize Filter. Adjusts the channels so that there are no more - * than n different values for that channel. This is also applied - * to the alpha channel. - * @function - * @name Posterize - * @author ippo615 - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Posterize]); - * node.levels(0.8); // between 0 and 1 - */ - - Konva.Filters.Posterize = function(imageData) { - // level must be between 1 and 255 - var levels = Math.round(this.levels() * 254) + 1, - data = imageData.data, - len = data.length, - scale = 255 / levels, - i; - - for (i = 0; i < len; i += 1) { - data[i] = Math.floor(data[i] / scale) * scale; - } - }; - - Konva.Factory.addGetterSetter( - Konva.Node, - 'levels', - 0.5, - null, - Konva.Factory.afterSetFilter - ); - - /** - * get/set levels. Must be a number between 0 and 1. Use with {@link Konva.Filters.Posterize} filter. - * @name levels - * @method - * @memberof Konva.Node.prototype - * @param {Number} level between 0 and 1 - * @returns {Number} - */ -})(); - + (function() { 'use strict'; + // constants + var ATTR_CHANGE_LIST = [ + 'fontFamily', + 'fontSize', + 'fontStyle', + 'padding', + 'lineHeight', + 'text', + 'width' + ], + CHANGE_KONVA = 'Change.konva', + NONE = 'none', + UP = 'up', + RIGHT = 'right', + DOWN = 'down', + LEFT = 'left', + LABEL = 'Label', + // cached variables + attrChangeListLen = ATTR_CHANGE_LIST.length; + /** - * Noise Filter. Randomly adds or substracts to the color channels - * @function - * @name Noise - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 + * Label constructor.  Labels are groups that contain a Text and Tag shape + * @constructor + * @memberof Konva + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] * @example - * node.cache(); - * node.filters([Konva.Filters.Noise]); - * node.noise(0.8); + * // create label + * var label = new Konva.Label({ + * x: 100, + * y: 100, + * draggable: true + * }); + * + * // add a tag to the label + * label.add(new Konva.Tag({ + * fill: '#bbb', + * stroke: '#333', + * shadowColor: 'black', + * shadowBlur: 10, + * shadowOffset: [10, 10], + * shadowOpacity: 0.2, + * lineJoin: 'round', + * pointerDirection: 'up', + * pointerWidth: 20, + * pointerHeight: 20, + * cornerRadius: 5 + * })); + * + * // add text to the label + * label.add(new Konva.Text({ + * text: 'Hello World!', + * fontSize: 50, + * lineHeight: 1.2, + * padding: 10, + * fill: 'green' + * })); */ - Konva.Filters.Noise = function(imageData) { - var amount = this.noise() * 255, - data = imageData.data, - nPixels = data.length, - half = amount / 2, - i; + Konva.Label = function(config) { + this.____init(config); + }; - for (i = 0; i < nPixels; i += 4) { - data[i + 0] += half - 2 * half * Math.random(); - data[i + 1] += half - 2 * half * Math.random(); - data[i + 2] += half - 2 * half * Math.random(); + Konva.Label.prototype = { + ____init: function(config) { + var that = this; + + Konva.Group.call(this, config); + this.className = LABEL; + + this.on('add.konva', function(evt) { + that._addListeners(evt.child); + that._sync(); + }); + }, + /** + * get Text shape for the label. You need to access the Text shape in order to update + * the text properties + * @name getText + * @method + * @memberof Konva.Label.prototype + */ + getText: function() { + return this.find('Text')[0]; + }, + /** + * get Tag shape for the label. You need to access the Tag shape in order to update + * the pointer properties and the corner radius + * @name getTag + * @method + * @memberof Konva.Label.prototype + */ + getTag: function() { + return this.find('Tag')[0]; + }, + _addListeners: function(text) { + var that = this, n; + var func = function() { + that._sync(); + }; + + // update text data for certain attr changes + for (n = 0; n < attrChangeListLen; n++) { + text.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, func); + } + }, + getWidth: function() { + return this.getText().getWidth(); + }, + getHeight: function() { + return this.getText().getHeight(); + }, + _sync: function() { + var text = this.getText(), + tag = this.getTag(), + width, + height, + pointerDirection, + pointerWidth, + x, + y, + pointerHeight; + + if (text && tag) { + width = text.getWidth(); + height = text.getHeight(); + pointerDirection = tag.getPointerDirection(); + pointerWidth = tag.getPointerWidth(); + pointerHeight = tag.getPointerHeight(); + x = 0; + y = 0; + + switch (pointerDirection) { + case UP: + x = width / 2; + y = (-1) * pointerHeight; + break; + case RIGHT: + x = width + pointerWidth; + y = height / 2; + break; + case DOWN: + x = width / 2; + y = height + pointerHeight; + break; + case LEFT: + x = (-1) * pointerWidth; + y = height / 2; + break; + } + + tag.setAttrs({ + x: (-1) * x, + y: (-1) * y, + width: width, + height: height + }); + + text.setAttrs({ + x: (-1) * x, + y: (-1) * y + }); + } } }; - Konva.Factory.addGetterSetter( - Konva.Node, - 'noise', - 0.2, - null, - Konva.Factory.afterSetFilter - ); - /** - * get/set noise amount. Must be a value between 0 and 1. Use with {@link Konva.Filters.Noise} filter. - * @name noise - * @method - * @memberof Konva.Node.prototype - * @param {Number} noise - * @returns {Number} - */ -})(); - -/*eslint-disable max-depth */ -(function() { - 'use strict'; + Konva.Util.extend(Konva.Label, Konva.Group); + + Konva.Collection.mapMethods(Konva.Label); + /** - * Pixelate Filter. Averages groups of pixels and redraws - * them as larger pixels - * @function - * @name Pixelate - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 - * @example - * node.cache(); - * node.filters([Konva.Filters.Pixelate]); - * node.pixelSize(10); + * Tag constructor.  A Tag can be configured + * to have a pointer element that points up, right, down, or left + * @constructor + * @memberof Konva + * @param {Object} config + * @param {String} [config.pointerDirection] can be up, right, down, left, or none; the default + * is none. When a pointer is present, the positioning of the label is relative to the tip of the pointer. + * @param {Number} [config.pointerWidth] + * @param {Number} [config.pointerHeight] + * @param {Number} [config.cornerRadius] */ + Konva.Tag = function(config) { + this.___init(config); + }; - Konva.Filters.Pixelate = function(imageData) { - var pixelSize = Math.ceil(this.pixelSize()), - width = imageData.width, - height = imageData.height, - x, - y, - i, - //pixelsPerBin = pixelSize * pixelSize, - red, - green, - blue, - alpha, - nBinsX = Math.ceil(width / pixelSize), - nBinsY = Math.ceil(height / pixelSize), - xBinStart, - xBinEnd, - yBinStart, - yBinEnd, - xBin, - yBin, - pixelsInBin; - imageData = imageData.data; + Konva.Tag.prototype = { + ___init: function(config) { + Konva.Shape.call(this, config); + this.className = 'Tag'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var width = this.getWidth(), + height = this.getHeight(), + pointerDirection = this.getPointerDirection(), + pointerWidth = this.getPointerWidth(), + pointerHeight = this.getPointerHeight(), + cornerRadius = Math.min(this.getCornerRadius(), width / 2, height / 2); - if (pixelSize <= 0) { - Konva.Util.error('pixelSize value can not be <= 0'); - return; - } + context.beginPath(); + if (!cornerRadius) { + context.moveTo(0, 0); + } else { + context.moveTo(cornerRadius, 0); + } - for (xBin = 0; xBin < nBinsX; xBin += 1) { - for (yBin = 0; yBin < nBinsY; yBin += 1) { - // Initialize the color accumlators to 0 - red = 0; - green = 0; - blue = 0; - alpha = 0; + if (pointerDirection === UP) { + context.lineTo((width - pointerWidth) / 2, 0); + context.lineTo(width / 2, (-1) * pointerHeight); + context.lineTo((width + pointerWidth) / 2, 0); + } - // Determine which pixels are included in this bin - xBinStart = xBin * pixelSize; - xBinEnd = xBinStart + pixelSize; - yBinStart = yBin * pixelSize; - yBinEnd = yBinStart + pixelSize; + if (!cornerRadius) { + context.lineTo(width, 0); + } else { + context.lineTo(width - cornerRadius, 0); + context.arc( + width - cornerRadius, + cornerRadius, + cornerRadius, + Math.PI * 3 / 2, + 0, + false + ); + } - // Add all of the pixels to this bin! - pixelsInBin = 0; - for (x = xBinStart; x < xBinEnd; x += 1) { - if (x >= width) { - continue; - } - for (y = yBinStart; y < yBinEnd; y += 1) { - if (y >= height) { - continue; - } - i = (width * y + x) * 4; - red += imageData[i + 0]; - green += imageData[i + 1]; - blue += imageData[i + 2]; - alpha += imageData[i + 3]; - pixelsInBin += 1; - } - } + if (pointerDirection === RIGHT) { + context.lineTo(width, (height - pointerHeight) / 2); + context.lineTo(width + pointerWidth, height / 2); + context.lineTo(width, (height + pointerHeight) / 2); + } - // Make sure the channels are between 0-255 - red = red / pixelsInBin; - green = green / pixelsInBin; - blue = blue / pixelsInBin; + if (!cornerRadius) { + context.lineTo(width, height); + } else { + context.lineTo(width, height - cornerRadius); + context.arc( + width - cornerRadius, + height - cornerRadius, + cornerRadius, + 0, + Math.PI / 2, + false + ); + } - // Draw this bin - for (x = xBinStart; x < xBinEnd; x += 1) { - if (x >= width) { - continue; - } - for (y = yBinStart; y < yBinEnd; y += 1) { - if (y >= height) { - continue; - } - i = (width * y + x) * 4; - imageData[i + 0] = red; - imageData[i + 1] = green; - imageData[i + 2] = blue; - imageData[i + 3] = alpha; - } - } + if (pointerDirection === DOWN) { + context.lineTo((width + pointerWidth) / 2, height); + context.lineTo(width / 2, height + pointerHeight); + context.lineTo((width - pointerWidth) / 2, height); + } + + if (!cornerRadius) { + context.lineTo(0, height); + } else { + context.lineTo(cornerRadius, height); + context.arc( + cornerRadius, + height - cornerRadius, + cornerRadius, + Math.PI / 2, + Math.PI, + false + ); + } + + if (pointerDirection === LEFT) { + context.lineTo(0, (height + pointerHeight) / 2); + context.lineTo((-1) * pointerWidth, height / 2); + context.lineTo(0, (height - pointerHeight) / 2); + } + + if (cornerRadius) { + context.lineTo(0, cornerRadius); + context.arc( + cornerRadius, + cornerRadius, + cornerRadius, + Math.PI, + Math.PI * 3 / 2, + false + ); + } + + context.closePath(); + context.fillStrokeShape(this); + }, + getSelfRect: function() { + var x = 0, + y = 0, + pointerWidth = this.getPointerWidth(), + pointerHeight = this.getPointerHeight(), + direction = this.pointerDirection(), + width = this.getWidth(), + height = this.getHeight(); + + if (direction === UP) { + y -= pointerHeight; + height += pointerHeight; + } else if (direction === DOWN) { + height += pointerHeight; + } else if (direction === LEFT) { + // ARGH!!! I have no idea why should I used magic 1.5!!!!!!!!! + x -= pointerWidth * 1.5; + width += pointerWidth; + } else if (direction === RIGHT) { + width += pointerWidth * 1.5; } + return { + x: x, + y: y, + width: width, + height: height + }; } }; - Konva.Factory.addGetterSetter( - Konva.Node, - 'pixelSize', - 8, - null, - Konva.Factory.afterSetFilter - ); + Konva.Util.extend(Konva.Tag, Konva.Shape); + Konva.Factory.addGetterSetter(Konva.Tag, 'pointerDirection', NONE); + /** - * get/set pixel size. Use with {@link Konva.Filters.Pixelate} filter. - * @name pixelSize - * @method - * @memberof Konva.Node.prototype - * @param {Integer} pixelSize - * @returns {Integer} - */ + * set pointer Direction + * @name setPointerDirection + * @method + * @memberof Konva.Tag.prototype + * @param {String} pointerDirection can be up, right, down, left, or none. The + * default is none + */ + + /** + * get pointer Direction + * @name getPointerDirection + * @method + * @memberof Konva.Tag.prototype + */ + + Konva.Factory.addGetterSetter(Konva.Tag, 'pointerWidth', 0); + + /** + * set pointer width + * @name setPointerWidth + * @method + * @memberof Konva.Tag.prototype + * @param {Number} pointerWidth + */ + + /** + * get pointer width + * @name getPointerWidth + * @method + * @memberof Konva.Tag.prototype + */ + + Konva.Factory.addGetterSetter(Konva.Tag, 'pointerHeight', 0); + + /** + * set pointer height + * @name setPointerHeight + * @method + * @memberof Konva.Tag.prototype + * @param {Number} pointerHeight + */ + + /** + * get pointer height + * @name getPointerHeight + * @method + * @memberof Konva.Tag.prototype + */ + + Konva.Factory.addGetterSetter(Konva.Tag, 'cornerRadius', 0); + + /** + * set corner radius + * @name setCornerRadius + * @method + * @memberof Konva.Tag.prototype + * @param {Number} corner radius + */ + + /** + * get corner radius + * @name getCornerRadius + * @method + * @memberof Konva.Tag.prototype + */ + + Konva.Collection.mapMethods(Konva.Tag); })(); - + (function() { 'use strict'; /** - * Threshold Filter. Pushes any value above the mid point to - * the max and any value below the mid point to the min. - * This affects the alpha channel. - * @function - * @name Threshold - * @memberof Konva.Filters - * @param {Object} imageData - * @author ippo615 + * Arrow constructor + * @constructor + * @memberof Konva + * @augments Konva.Shape + * @param {Object} config + * @param {Array} config.points + * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. + * The default is 0 + * @param {Number} config.pointerLength + * @param {Number} config.pointerWidth + * @param {String} [config.fill] fill color + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true + * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true + * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Number} [config.dragDistance] + * @param {Function} [config.dragBoundFunc] * @example - * node.cache(); - * node.filters([Konva.Filters.Threshold]); - * node.threshold(0.1); + * var line = new Konva.Line({ + * points: [73, 70, 340, 23, 450, 60, 500, 20], + * stroke: 'red', + * tension: 1, + * pointerLength : 10, + * pointerWidth : 12 + * }); */ + Konva.Arrow = function(config) { + this.____init(config); + }; - Konva.Filters.Threshold = function(imageData) { - var level = this.threshold() * 255, - data = imageData.data, - len = data.length, - i; + Konva.Arrow.prototype = { + ____init: function(config) { + // call super constructor + Konva.Line.call(this, config); + this.className = 'Arrow'; + }, + _sceneFunc: function(ctx) { + Konva.Line.prototype._sceneFunc.apply(this, arguments); + var PI2 = Math.PI * 2; + var points = this.points(); + var n = points.length; + var dx = points[n - 2] - points[n - 4]; + var dy = points[n - 1] - points[n - 3]; + var radians = (Math.atan2(dy, dx) + PI2) % PI2; + var length = this.pointerLength(); + var width = this.pointerWidth(); - for (i = 0; i < len; i += 1) { - data[i] = data[i] < level ? 0 : 255; + ctx.save(); + ctx.beginPath(); + ctx.translate(points[n - 2], points[n - 1]); + ctx.rotate(radians); + ctx.moveTo(0, 0); + ctx.lineTo(-length, width / 2); + ctx.lineTo(-length, (-width) / 2); + ctx.closePath(); + ctx.restore(); + + if (this.pointerAtBeginning()) { + ctx.save(); + ctx.translate(points[0], points[1]); + dx = points[2] - points[0]; + dy = points[3] - points[1]; + ctx.rotate((Math.atan2(-dy, -dx) + PI2) % PI2); + ctx.moveTo(0, 0); + ctx.lineTo(-length, width / 2); + ctx.lineTo(-length, (-width) / 2); + ctx.closePath(); + ctx.restore(); + } + ctx.fillStrokeShape(this); } }; - Konva.Factory.addGetterSetter( - Konva.Node, - 'threshold', - 0.5, - null, - Konva.Factory.afterSetFilter - ); + Konva.Util.extend(Konva.Arrow, Konva.Line); /** - * get/set threshold. Must be a value between 0 and 1. Use with {@link Konva.Filters.Threshold} or {@link Konva.Filters.Mask} filter. - * @name threshold - * @method - * @memberof Konva.Node.prototype - * @param {Number} threshold - * @returns {Number} - */ + * get/set pointerLength + * @name pointerLength + * @method + * @memberof Konva.Arrow.prototype + * @param {Number} Length of pointer of arrow. + * The default is 10. + * @returns {Number} + * @example + * // get tension + * var pointerLength = line.pointerLength(); + * + * // set tension + * line.pointerLength(15); + */ + + Konva.Factory.addGetterSetter(Konva.Arrow, 'pointerLength', 10); + /** + * get/set pointerWidth + * @name pointerWidth + * @method + * @memberof Konva.Arrow.prototype + * @param {Number} Width of pointer of arrow. + * The default is 10. + * @returns {Number} + * @example + * // get tension + * var pointerWidth = line.pointerWidth(); + * + * // set tension + * line.pointerWidth(15); + */ + + Konva.Factory.addGetterSetter(Konva.Arrow, 'pointerWidth', 10); + /** + * get/set pointerAtBeginning + * @name pointerAtBeginning + * @method + * @memberof Konva.Arrow.prototype + * @param {Number} Should pointer displayed at beginning of arrow. + * The default is false. + * @returns {Boolean} + * @example + * // get tension + * var pointerAtBeginning = line.pointerAtBeginning(); + * + * // set tension + * line.pointerAtBeginning(true); + */ + + Konva.Factory.addGetterSetter(Konva.Arrow, 'pointerAtBeginning', false); + Konva.Collection.mapMethods(Konva.Arrow); })(); - -(function() { - 'use strict'; - /** - * Sepia Filter - * Based on: Pixastic Lib - Sepia filter - v0.1.0 - * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ - * @function - * @name Sepia - * @memberof Konva.Filters - * @param {Object} imageData - * @author Jacob Seidelin - * @license MPL v1.1 [http://www.pixastic.com/lib/license.txt] - * @example - * node.cache(); - * node.filters([Konva.Filters.Sepia]); - */ - Konva.Filters.Sepia = function(imageData) { - var data = imageData.data, - w = imageData.width, - y = imageData.height, - w4 = w * 4, - offsetY, - x, - offset, - or, - og, - ob, - r, - g, - b; - - do { - offsetY = (y - 1) * w4; - x = w; - do { - offset = offsetY + (x - 1) * 4; - - or = data[offset]; - og = data[offset + 1]; - ob = data[offset + 2]; - - r = or * 0.393 + og * 0.769 + ob * 0.189; - g = or * 0.349 + og * 0.686 + ob * 0.168; - b = or * 0.272 + og * 0.534 + ob * 0.131; - - data[offset] = r > 255 ? 255 : r; - data[offset + 1] = g > 255 ? 255 : g; - data[offset + 2] = b > 255 ? 255 : b; - data[offset + 3] = data[offset + 3]; - } while (--x); - } while (--y); - }; -})(); - -(function() { - 'use strict'; - /** - * Solarize Filter - * Pixastic Lib - Solarize filter - v0.1.0 - * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ - * License: [http://www.pixastic.com/lib/license.txt] - * @function - * @name Solarize - * @memberof Konva.Filters - * @param {Object} imageData - * @example - * node.cache(); - * node.filters([Konva.Filters.Solarize]); - */ - Konva.Filters.Solarize = function(imageData) { - var data = imageData.data, - w = imageData.width, - h = imageData.height, - w4 = w * 4, - y = h; - - do { - var offsetY = (y - 1) * w4; - var x = w; - do { - var offset = offsetY + (x - 1) * 4; - var r = data[offset]; - var g = data[offset + 1]; - var b = data[offset + 2]; - - if (r > 127) { - r = 255 - r; - } - if (g > 127) { - g = 255 - g; - } - if (b > 127) { - b = 255 - b; - } - - data[offset] = r; - data[offset + 1] = g; - data[offset + 2] = b; - } while (--x); - } while (--y); - }; -})(); - -(function() { - 'use strict'; - /* - * ToPolar Filter. Converts image data to polar coordinates. Performs - * w*h*4 pixel reads and w*h pixel writes. The r axis is placed along - * what would be the y axis and the theta axis along the x axis. - * @function - * @author ippo615 - * @memberof Konva.Filters - * @param {ImageData} src, the source image data (what will be transformed) - * @param {ImageData} dst, the destination image data (where it will be saved) - * @param {Object} opt - * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, - * default is in the middle - * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, - * default is in the middle - */ - - var ToPolar = function(src, dst, opt) { - var srcPixels = src.data, - dstPixels = dst.data, - xSize = src.width, - ySize = src.height, - xMid = opt.polarCenterX || xSize / 2, - yMid = opt.polarCenterY || ySize / 2, - i, - x, - y, - r = 0, - g = 0, - b = 0, - a = 0; - - // Find the largest radius - var rad, rMax = Math.sqrt(xMid * xMid + yMid * yMid); - x = xSize - xMid; - y = ySize - yMid; - rad = Math.sqrt(x * x + y * y); - rMax = rad > rMax ? rad : rMax; - - // We'll be uisng y as the radius, and x as the angle (theta=t) - var rSize = ySize, tSize = xSize, radius, theta; - - // We want to cover all angles (0-360) and we need to convert to - // radians (*PI/180) - var conversion = 360 / tSize * Math.PI / 180, sin, cos; - - // var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale; - - for (theta = 0; theta < tSize; theta += 1) { - sin = Math.sin(theta * conversion); - cos = Math.cos(theta * conversion); - for (radius = 0; radius < rSize; radius += 1) { - x = Math.floor(xMid + rMax * radius / rSize * cos); - y = Math.floor(yMid + rMax * radius / rSize * sin); - i = (y * xSize + x) * 4; - r = srcPixels[i + 0]; - g = srcPixels[i + 1]; - b = srcPixels[i + 2]; - a = srcPixels[i + 3]; - - // Store it - //i = (theta * xSize + radius) * 4; - i = (theta + radius * xSize) * 4; - dstPixels[i + 0] = r; - dstPixels[i + 1] = g; - dstPixels[i + 2] = b; - dstPixels[i + 3] = a; - } - } - }; - - /* - * FromPolar Filter. Converts image data from polar coordinates back to rectangular. - * Performs w*h*4 pixel reads and w*h pixel writes. - * @function - * @author ippo615 - * @memberof Konva.Filters - * @param {ImageData} src, the source image data (what will be transformed) - * @param {ImageData} dst, the destination image data (where it will be saved) - * @param {Object} opt - * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, - * default is in the middle - * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, - * default is in the middle - * @param {Number} [opt.polarRotation] amount to rotate the image counterclockwis, - * 0 is no rotation, 360 degrees is a full rotation - */ - - var FromPolar = function(src, dst, opt) { - var srcPixels = src.data, - dstPixels = dst.data, - xSize = src.width, - ySize = src.height, - xMid = opt.polarCenterX || xSize / 2, - yMid = opt.polarCenterY || ySize / 2, - i, - x, - y, - dx, - dy, - r = 0, - g = 0, - b = 0, - a = 0; - - // Find the largest radius - var rad, rMax = Math.sqrt(xMid * xMid + yMid * yMid); - x = xSize - xMid; - y = ySize - yMid; - rad = Math.sqrt(x * x + y * y); - rMax = rad > rMax ? rad : rMax; - - // We'll be uisng x as the radius, and y as the angle (theta=t) - var rSize = ySize, - tSize = xSize, - radius, - theta, - phaseShift = opt.polarRotation || 0; - - // We need to convert to degrees and we need to make sure - // it's between (0-360) - // var conversion = tSize/360*180/Math.PI; - //var conversion = tSize/360*180/Math.PI; - - var x1, y1; - - for (x = 0; x < xSize; x += 1) { - for (y = 0; y < ySize; y += 1) { - dx = x - xMid; - dy = y - yMid; - radius = Math.sqrt(dx * dx + dy * dy) * rSize / rMax; - theta = (Math.atan2(dy, dx) * 180 / Math.PI + 360 + phaseShift) % 360; - theta = theta * tSize / 360; - x1 = Math.floor(theta); - y1 = Math.floor(radius); - i = (y1 * xSize + x1) * 4; - r = srcPixels[i + 0]; - g = srcPixels[i + 1]; - b = srcPixels[i + 2]; - a = srcPixels[i + 3]; - - // Store it - i = (y * xSize + x) * 4; - dstPixels[i + 0] = r; - dstPixels[i + 1] = g; - dstPixels[i + 2] = b; - dstPixels[i + 3] = a; - } - } - }; - - //Konva.Filters.ToPolar = Konva.Util._FilterWrapDoubleBuffer(ToPolar); - //Konva.Filters.FromPolar = Konva.Util._FilterWrapDoubleBuffer(FromPolar); - - // create a temporary canvas for working - shared between multiple calls - var tempCanvas = Konva.Util.createCanvasElement(); - - /* - * Kaleidoscope Filter. - * @function - * @name Kaleidoscope - * @author ippo615 - * @memberof Konva.Filters - * @example - * node.cache(); - * node.filters([Konva.Filters.Kaleidoscope]); - * node.kaleidoscopePower(3); - * node.kaleidoscopeAngle(45); - */ - Konva.Filters.Kaleidoscope = function(imageData) { - var xSize = imageData.width, ySize = imageData.height; - - var x, y, xoff, i, r, g, b, a, srcPos, dstPos; - var power = Math.round(this.kaleidoscopePower()); - var angle = Math.round(this.kaleidoscopeAngle()); - var offset = Math.floor(xSize * (angle % 360) / 360); - - if (power < 1) { - return; - } - - // Work with our shared buffer canvas - tempCanvas.width = xSize; - tempCanvas.height = ySize; - var scratchData = tempCanvas - .getContext('2d') - .getImageData(0, 0, xSize, ySize); - - // Convert thhe original to polar coordinates - ToPolar(imageData, scratchData, { - polarCenterX: xSize / 2, - polarCenterY: ySize / 2 - }); - - // Determine how big each section will be, if it's too small - // make it bigger - var minSectionSize = xSize / Math.pow(2, power); - while (minSectionSize <= 8) { - minSectionSize = minSectionSize * 2; - power -= 1; - } - minSectionSize = Math.ceil(minSectionSize); - var sectionSize = minSectionSize; - - // Copy the offset region to 0 - // Depending on the size of filter and location of the offset we may need - // to copy the section backwards to prevent it from rewriting itself - var xStart = 0, xEnd = sectionSize, xDelta = 1; - if (offset + minSectionSize > xSize) { - xStart = sectionSize; - xEnd = 0; - xDelta = -1; - } - for (y = 0; y < ySize; y += 1) { - for (x = xStart; x !== xEnd; x += xDelta) { - xoff = Math.round(x + offset) % xSize; - srcPos = (xSize * y + xoff) * 4; - r = scratchData.data[srcPos + 0]; - g = scratchData.data[srcPos + 1]; - b = scratchData.data[srcPos + 2]; - a = scratchData.data[srcPos + 3]; - dstPos = (xSize * y + x) * 4; - scratchData.data[dstPos + 0] = r; - scratchData.data[dstPos + 1] = g; - scratchData.data[dstPos + 2] = b; - scratchData.data[dstPos + 3] = a; - } - } - - // Perform the actual effect - for (y = 0; y < ySize; y += 1) { - sectionSize = Math.floor(minSectionSize); - for (i = 0; i < power; i += 1) { - for (x = 0; x < sectionSize + 1; x += 1) { - srcPos = (xSize * y + x) * 4; - r = scratchData.data[srcPos + 0]; - g = scratchData.data[srcPos + 1]; - b = scratchData.data[srcPos + 2]; - a = scratchData.data[srcPos + 3]; - dstPos = (xSize * y + sectionSize * 2 - x - 1) * 4; - scratchData.data[dstPos + 0] = r; - scratchData.data[dstPos + 1] = g; - scratchData.data[dstPos + 2] = b; - scratchData.data[dstPos + 3] = a; - } - sectionSize *= 2; - } - } - - // Convert back from polar coordinates - FromPolar(scratchData, imageData, { polarRotation: 0 }); - }; - - /** - * get/set kaleidoscope power. Use with {@link Konva.Filters.Kaleidoscope} filter. - * @name kaleidoscopePower - * @method - * @memberof Konva.Node.prototype - * @param {Integer} power of kaleidoscope - * @returns {Integer} - */ - Konva.Factory.addGetterSetter( - Konva.Node, - 'kaleidoscopePower', - 2, - null, - Konva.Factory.afterSetFilter - ); - - /** - * get/set kaleidoscope angle. Use with {@link Konva.Filters.Kaleidoscope} filter. - * @name kaleidoscopeAngle - * @method - * @memberof Konva.Node.prototype - * @param {Integer} degrees - * @returns {Integer} - */ - Konva.Factory.addGetterSetter( - Konva.Node, - 'kaleidoscopeAngle', - 0, - null, - Konva.Factory.afterSetFilter - ); -})(); - -(function() { - 'use strict'; - /** - * Container constructor.  Containers are used to contain nodes or other containers - * @constructor - * @memberof Konva - * @augments Konva.Node - * @abstract - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * * @param {Object} [config.clip] set clip - * @param {Number} [config.clipX] set clip x - * @param {Number} [config.clipY] set clip y - * @param {Number} [config.clipWidth] set clip width - * @param {Number} [config.clipHeight] set clip height - * @param {Function} [config.clipFunc] set clip func - - */ - Konva.Container = function(config) { - this.__init(config); - }; - - Konva.Util.addMethods(Konva.Container, { - __init: function(config) { - this.children = new Konva.Collection(); - Konva.Node.call(this, config); - }, - /** - * returns a {@link Konva.Collection} of direct descendant nodes - * @method - * @memberof Konva.Container.prototype - * @param {Function} [filterFunc] filter function - * @returns {Konva.Collection} - * @example - * // get all children - * var children = layer.getChildren(); - * - * // get only circles - * var circles = layer.getChildren(function(node){ - * return node.getClassName() === 'Circle'; - * }); - */ - getChildren: function(filterFunc) { - if (!filterFunc) { - return this.children; - } - - var results = new Konva.Collection(); - this.children.each(function(child) { - if (filterFunc(child)) { - results.push(child); - } - }); - return results; - }, - /** - * determine if node has children - * @method - * @memberof Konva.Container.prototype - * @returns {Boolean} - */ - hasChildren: function() { - return this.getChildren().length > 0; - }, - /** - * remove all children - * @method - * @memberof Konva.Container.prototype - */ - removeChildren: function() { - var children = Konva.Collection.toCollection(this.children); - var child; - for (var i = 0; i < children.length; i++) { - child = children[i]; - // reset parent to prevent many _setChildrenIndices calls - delete child.parent; - child.index = 0; - child.remove(); - } - children = null; - this.children = new Konva.Collection(); - return this; - }, - /** - * destroy all children - * @method - * @memberof Konva.Container.prototype - */ - destroyChildren: function() { - var children = Konva.Collection.toCollection(this.children); - var child; - for (var i = 0; i < children.length; i++) { - child = children[i]; - // reset parent to prevent many _setChildrenIndices calls - delete child.parent; - child.index = 0; - child.destroy(); - } - children = null; - this.children = new Konva.Collection(); - return this; - }, - /** - * Add node or nodes to container. - * @method - * @memberof Konva.Container.prototype - * @param {...Konva.Node} child - * @returns {Container} - * @example - * layer.add(shape1, shape2, shape3); - */ - add: function(child) { - if (arguments.length > 1) { - for (var i = 0; i < arguments.length; i++) { - this.add(arguments[i]); - } - return this; - } - if (child.getParent()) { - child.moveTo(this); - return this; - } - var children = this.children; - this._validateAdd(child); - child.index = children.length; - child.parent = this; - children.push(child); - this._fire('add', { - child: child - }); - - // if node under drag we need to update drag animation - if (Konva.DD && child.isDragging()) { - Konva.DD.anim.setLayers(child.getLayer()); - } - - // chainable - return this; - }, - destroy: function() { - // destroy children - if (this.hasChildren()) { - this.destroyChildren(); - } - // then destroy self - Konva.Node.prototype.destroy.call(this); - return this; - }, - /** - * return a {@link Konva.Collection} of nodes that match the selector. Use '#' for id selections - * and '.' for name selections. You can also select by type or class name. Pass multiple selectors - * separated by a space. - * @method - * @memberof Konva.Container.prototype - * @param {String} selector - * @returns {Collection} - * @example - * // select node with id foo - * var node = stage.find('#foo'); - * - * // select nodes with name bar inside layer - * var nodes = layer.find('.bar'); - * - * // select all groups inside layer - * var nodes = layer.find('Group'); - * - * // select all rectangles inside layer - * var nodes = layer.find('Rect'); - * - * // select node with an id of foo or a name of bar inside layer - * var nodes = layer.find('#foo, .bar'); - */ - find: function(selector) { - var retArr = [], - selectorArr = selector.replace(/ /g, '').split(','), - len = selectorArr.length, - n, - i, - sel, - arr, - node, - children, - clen; - - for (n = 0; n < len; n++) { - sel = selectorArr[n]; - if (!Konva.Util.isValidSelector(sel)) { - Konva.Util.warn( - 'Selector "' + - sel + - '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' - ); - Konva.Util.warn( - 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' - ); - Konva.Util.warn('Konva is awesome, right?'); - } - // id selector - if (sel.charAt(0) === '#') { - node = this._getNodeById(sel.slice(1)); - if (node) { - retArr.push(node); - } - } else if (sel.charAt(0) === '.') { - // name selector - arr = this._getNodesByName(sel.slice(1)); - retArr = retArr.concat(arr); - } else { - // unrecognized selector, pass to children - children = this.getChildren(); - clen = children.length; - for (i = 0; i < clen; i++) { - retArr = retArr.concat(children[i]._get(sel)); - } - } - } - - return Konva.Collection.toCollection(retArr); - }, - /** - * return a first node from `find` method - * @method - * @memberof Konva.Container.prototype - * @param {String} selector - * @returns {Konva.Node} - * @example - * // select node with id foo - * var node = stage.findOne('#foo'); - * - * // select node with name bar inside layer - * var nodes = layer.findOne('.bar'); - */ - findOne: function(selector) { - return this.find(selector)[0]; - }, - _getNodeById: function(key) { - var node = Konva.ids[key]; - - if (node !== undefined && this.isAncestorOf(node)) { - return node; - } - return null; - }, - _getNodesByName: function(key) { - var arr = Konva.names[key] || []; - return this._getDescendants(arr); - }, - _get: function(selector) { - var retArr = Konva.Node.prototype._get.call(this, selector); - var children = this.getChildren(); - var len = children.length; - for (var n = 0; n < len; n++) { - retArr = retArr.concat(children[n]._get(selector)); - } - return retArr; - }, - // extenders - toObject: function() { - var obj = Konva.Node.prototype.toObject.call(this); - - obj.children = []; - - var children = this.getChildren(); - var len = children.length; - for (var n = 0; n < len; n++) { - var child = children[n]; - obj.children.push(child.toObject()); - } - - return obj; - }, - _getDescendants: function(arr) { - var retArr = []; - var len = arr.length; - for (var n = 0; n < len; n++) { - var node = arr[n]; - if (this.isAncestorOf(node)) { - retArr.push(node); - } - } - - return retArr; - }, - /** - * determine if node is an ancestor - * of descendant - * @method - * @memberof Konva.Container.prototype - * @param {Konva.Node} node - */ - isAncestorOf: function(node) { - var parent = node.getParent(); - while (parent) { - if (parent._id === this._id) { - return true; - } - parent = parent.getParent(); - } - - return false; - }, - clone: function(obj) { - // call super method - var node = Konva.Node.prototype.clone.call(this, obj); - - this.getChildren().each(function(no) { - node.add(no.clone()); - }); - return node; - }, - /** - * get all shapes that intersect a point. Note: because this method must clear a temporary - * canvas and redraw every shape inside the container, it should only be used for special sitations - * because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible - * because it performs much better - * @method - * @memberof Konva.Container.prototype - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @returns {Array} array of shapes - */ - getAllIntersections: function(pos) { - var arr = []; - - this.find('Shape').each(function(shape) { - if (shape.isVisible() && shape.intersects(pos)) { - arr.push(shape); - } - }); - - return arr; - }, - _setChildrenIndices: function() { - this.children.each(function(child, n) { - child.index = n; - }); - }, - drawScene: function(can, top, caching) { - var layer = this.getLayer(), - canvas = can || (layer && layer.getCanvas()), - context = canvas && canvas.getContext(), - cachedCanvas = this._cache.canvas, - cachedSceneCanvas = cachedCanvas && cachedCanvas.scene; - - if (this.isVisible()) { - if (!caching && cachedSceneCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedSceneCanvas(context); - context.restore(); - } else { - this._drawChildren(canvas, 'drawScene', top, false, caching); - } - } - return this; - }, - drawHit: function(can, top, caching) { - var layer = this.getLayer(), - canvas = can || (layer && layer.hitCanvas), - context = canvas && canvas.getContext(), - cachedCanvas = this._cache.canvas, - cachedHitCanvas = cachedCanvas && cachedCanvas.hit; - - if (this.shouldDrawHit(canvas)) { - if (layer) { - layer.clearHitCache(); - } - if (!caching && cachedHitCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedHitCanvas(context); - context.restore(); - } else { - this._drawChildren(canvas, 'drawHit', top); - } - } - return this; - }, - _drawChildren: function(canvas, drawMethod, top, caching, skipBuffer) { - var layer = this.getLayer(), - context = canvas && canvas.getContext(), - clipWidth = this.getClipWidth(), - clipHeight = this.getClipHeight(), - clipFunc = this.getClipFunc(), - hasClip = (clipWidth && clipHeight) || clipFunc, - clipX, - clipY; - - if (hasClip && layer) { - context.save(); - var transform = this.getAbsoluteTransform(top); - var m = transform.getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - context.beginPath(); - if (clipFunc) { - clipFunc.call(this, context, this); - } else { - clipX = this.getClipX(); - clipY = this.getClipY(); - context.rect(clipX, clipY, clipWidth, clipHeight); - } - context.clip(); - m = transform.copy().invert().getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - - this.children.each(function(child) { - child[drawMethod](canvas, top, caching, skipBuffer); - }); - - if (hasClip) { - context.restore(); - } - }, - shouldDrawHit: function(canvas) { - var layer = this.getLayer(); - var dd = Konva.DD; - var layerUnderDrag = dd && - Konva.isDragging() && - Konva.DD.anim.getLayers().indexOf(layer) !== -1; - return (canvas && canvas.isCache) || - (layer && - layer.hitGraphEnabled() && - this.isVisible() && - !layerUnderDrag); - }, - getClientRect: function(skipTransform) { - var minX, minY, maxX, maxY; - var selfRect = { - x: 0, - y: 0, - width: 0, - height: 0 - }; - this.children.each(function(child) { - var rect = child.getClientRect(); - - // skip invisible children (like empty groups) - // or don't skip... hmmm... - // if (rect.width === 0 && rect.height === 0) { - // return; - // } - - if (minX === undefined) { - // initial value for first child - minX = rect.x; - minY = rect.y; - maxX = rect.x + rect.width; - maxY = rect.y + rect.height; - } else { - minX = Math.min(minX, rect.x); - minY = Math.min(minY, rect.y); - maxX = Math.max(maxX, rect.x + rect.width); - maxY = Math.max(maxY, rect.y + rect.height); - } - }); - - if (this.children.length !== 0) { - selfRect = { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY - }; - } - - if (!skipTransform) { - return this._transformedRect(selfRect); - } - return selfRect; - } - }); - - Konva.Util.extend(Konva.Container, Konva.Node); - // deprecated methods - Konva.Container.prototype.get = Konva.Container.prototype.find; - - // add getters setters - Konva.Factory.addComponentsGetterSetter(Konva.Container, 'clip', [ - 'x', - 'y', - 'width', - 'height' - ]); - /** - * get/set clip - * @method - * @name clip - * @memberof Konva.Container.prototype - * @param {Object} clip - * @param {Number} clip.x - * @param {Number} clip.y - * @param {Number} clip.width - * @param {Number} clip.height - * @returns {Object} - * @example - * // get clip - * var clip = container.clip(); - * - * // set clip - * container.setClip({ - * x: 20, - * y: 20, - * width: 20, - * height: 20 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Container, 'clipX'); - /** - * get/set clip x - * @name clipX - * @method - * @memberof Konva.Container.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get clip x - * var clipX = container.clipX(); - * - * // set clip x - * container.clipX(10); - */ - - Konva.Factory.addGetterSetter(Konva.Container, 'clipY'); - /** - * get/set clip y - * @name clipY - * @method - * @memberof Konva.Container.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get clip y - * var clipY = container.clipY(); - * - * // set clip y - * container.clipY(10); - */ - - Konva.Factory.addGetterSetter(Konva.Container, 'clipWidth'); - /** - * get/set clip width - * @name clipWidth - * @method - * @memberof Konva.Container.prototype - * @param {Number} width - * @returns {Number} - * @example - * // get clip width - * var clipWidth = container.clipWidth(); - * - * // set clip width - * container.clipWidth(100); - */ - - Konva.Factory.addGetterSetter(Konva.Container, 'clipHeight'); - /** - * get/set clip height - * @name clipHeight - * @method - * @memberof Konva.Container.prototype - * @param {Number} height - * @returns {Number} - * @example - * // get clip height - * var clipHeight = container.clipHeight(); - * - * // set clip height - * container.clipHeight(100); - */ - - Konva.Factory.addGetterSetter(Konva.Container, 'clipFunc'); - /** - * get/set clip function - * @name clipFunc - * @method - * @memberof Konva.Container.prototype - * @param {Function} function - * @returns {Function} - * @example - * // get clip function - * var clipFunction = container.clipFunc(); - * - * // set clip height - * container.clipFunc(function(ctx) { - * ctx.rect(0, 0, 100, 100); - * }); - */ - - Konva.Collection.mapMethods(Konva.Container); -})(); - -(function(Konva) { - 'use strict'; - var HAS_SHADOW = 'hasShadow'; - var SHADOW_RGBA = 'shadowRGBA'; - - function _fillFunc(context) { - context.fill(); - } - function _strokeFunc(context) { - context.stroke(); - } - function _fillFuncHit(context) { - context.fill(); - } - function _strokeFuncHit(context) { - context.stroke(); - } - - function _clearHasShadowCache() { - this._clearCache(HAS_SHADOW); - } - - function _clearGetShadowRGBACache() { - this._clearCache(SHADOW_RGBA); - } - - /** - * Shape constructor. Shapes are primitive objects such as rectangles, - * circles, text, lines, etc. - * @constructor - * @memberof Konva - * @augments Konva.Node - * @param {Object} config - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var customShape = new Konva.Shape({ - * x: 5, - * y: 10, - * fill: 'red', - * // a Konva.Canvas renderer is passed into the drawFunc function - * drawFunc: function(context) { - * context.beginPath(); - * context.moveTo(200, 50); - * context.lineTo(420, 80); - * context.quadraticCurveTo(300, 100, 260, 170); - * context.closePath(); - * context.fillStrokeShape(this); - * } - *}); - */ - Konva.Shape = function(config) { - this.__init(config); - }; - - Konva.Util.addMethods(Konva.Shape, { - __init: function(config) { - this.nodeType = 'Shape'; - this._fillFunc = _fillFunc; - this._strokeFunc = _strokeFunc; - this._fillFuncHit = _fillFuncHit; - this._strokeFuncHit = _strokeFuncHit; - - // set colorKey - var shapes = Konva.shapes; - var key; - - while (true) { - key = Konva.Util.getRandomColor(); - if (key && !(key in shapes)) { - break; - } - } - - this.colorKey = key; - shapes[key] = this; - - // call super constructor - Konva.Node.call(this, config); - - this.on( - 'shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', - _clearHasShadowCache - ); - - this.on( - 'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', - _clearGetShadowRGBACache - ); - }, - hasChildren: function() { - return false; - }, - getChildren: function() { - return []; - }, - /** - * get canvas context tied to the layer - * @method - * @memberof Konva.Shape.prototype - * @returns {Konva.Context} - */ - getContext: function() { - return this.getLayer().getContext(); - }, - /** - * get canvas renderer tied to the layer. Note that this returns a canvas renderer, not a canvas element - * @method - * @memberof Konva.Shape.prototype - * @returns {Konva.Canvas} - */ - getCanvas: function() { - return this.getLayer().getCanvas(); - }, - /** - * returns whether or not a shadow will be rendered - * @method - * @memberof Konva.Shape.prototype - * @returns {Boolean} - */ - hasShadow: function() { - return this._getCache(HAS_SHADOW, this._hasShadow); - }, - _hasShadow: function() { - return this.getShadowEnabled() && - (this.getShadowOpacity() !== 0 && - !!(this.getShadowColor() || - this.getShadowBlur() || - this.getShadowOffsetX() || - this.getShadowOffsetY())); - }, - getShadowRGBA: function() { - return this._getCache(SHADOW_RGBA, this._getShadowRGBA); - }, - _getShadowRGBA: function() { - if (this.hasShadow()) { - var rgba = Konva.Util.colorToRGBA(this.shadowColor()); - return 'rgba(' + - rgba.r + - ',' + - rgba.g + - ',' + - rgba.b + - ',' + - rgba.a * (this.getShadowOpacity() || 1) + - ')'; - } - }, - /** - * returns whether or not the shape will be filled - * @method - * @memberof Konva.Shape.prototype - * @returns {Boolean} - */ - hasFill: function() { - return !!(this.getFill() || - this.getFillPatternImage() || - this.getFillLinearGradientColorStops() || - this.getFillRadialGradientColorStops()); - }, - /** - * returns whether or not the shape will be stroked - * @method - * @memberof Konva.Shape.prototype - * @returns {Boolean} - */ - hasStroke: function() { - return this.strokeEnabled() && !!this.stroke(); - }, - /** - * determines if point is in the shape, regardless if other shapes are on top of it. Note: because - * this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times - * consecutively. Please use the {@link Konva.Stage#getIntersection} method if at all possible - * because it performs much better - * @method - * @memberof Konva.Shape.prototype - * @param {Object} point - * @param {Number} point.x - * @param {Number} point.y - * @returns {Boolean} - */ - intersects: function(point) { - var stage = this.getStage(), bufferHitCanvas = stage.bufferHitCanvas, p; - - bufferHitCanvas.getContext().clear(); - this.drawScene(bufferHitCanvas); - p = bufferHitCanvas.context.getImageData( - Math.round(point.x), - Math.round(point.y), - 1, - 1 - ).data; - return p[3] > 0; - }, - // extends Node.prototype.destroy - destroy: function() { - Konva.Node.prototype.destroy.call(this); - delete Konva.shapes[this.colorKey]; - return this; - }, - _useBufferCanvas: function(caching) { - return (!caching && - (this.perfectDrawEnabled() && - this.getAbsoluteOpacity() !== 1 && - this.hasFill() && - this.hasStroke() && - this.getStage())) || - (this.perfectDrawEnabled() && - this.hasShadow() && - this.getAbsoluteOpacity() !== 1 && - this.hasFill() && - this.hasStroke() && - this.getStage()); - }, - /** - * return self rectangle (x, y, width, height) of shape. - * This method are not taken into account transformation and styles. - * @method - * @memberof Konva.Shape.prototype - * @returns {Object} rect with {x, y, width, height} properties - * @example - * - * rect.getSelfRect(); // return {x:0, y:0, width:rect.width(), height:rect.height()} - * circle.getSelfRect(); // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()} - * - */ - getSelfRect: function() { - var size = this.getSize(); - return { - x: this._centroid ? Math.round((-size.width) / 2) : 0, - y: this._centroid ? Math.round((-size.height) / 2) : 0, - width: size.width, - height: size.height - }; - }, - getClientRect: function(skipTransform) { - var fillRect = this.getSelfRect(); - - var strokeWidth = (this.hasStroke() && this.strokeWidth()) || 0; - var fillAndStrokeWidth = fillRect.width + strokeWidth; - var fillAndStrokeHeight = fillRect.height + strokeWidth; - - var shadowOffsetX = this.hasShadow() ? this.shadowOffsetX() : 0; - var shadowOffsetY = this.hasShadow() ? this.shadowOffsetY() : 0; - - var preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); - var preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); - - var blurRadius = (this.hasShadow() && this.shadowBlur()) || 0; - - var width = preWidth + blurRadius * 2; - var height = preHeight + blurRadius * 2; - - // if stroke, for example = 3 - // we need to set x to 1.5, but after Math.round it will be 2 - // as we have additional offset we need to increase width and height by 1 pixel - var roundingOffset = 0; - if (Math.round(strokeWidth / 2) !== strokeWidth / 2) { - roundingOffset = 1; - } - var rect = { - width: width + roundingOffset, - height: height + roundingOffset, - x: -Math.round(strokeWidth / 2 + blurRadius) + - Math.min(shadowOffsetX, 0) + - fillRect.x, - y: -Math.round(strokeWidth / 2 + blurRadius) + - Math.min(shadowOffsetY, 0) + - fillRect.y - }; - if (!skipTransform) { - return this._transformedRect(rect); - } - return rect; - }, - drawScene: function(can, top, caching, skipBuffer) { - var layer = this.getLayer(), - canvas = can || layer.getCanvas(), - context = canvas.getContext(), - cachedCanvas = this._cache.canvas, - drawFunc = this.sceneFunc(), - hasShadow = this.hasShadow(), - hasStroke = this.hasStroke(), - stage, - bufferCanvas, - bufferContext; - - if (!this.isVisible()) { - return this; - } - if (cachedCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedSceneCanvas(context); - context.restore(); - return this; - } - if (!drawFunc) { - return this; - } - context.save(); - // if buffer canvas is needed - if (this._useBufferCanvas(caching) && !skipBuffer) { - stage = this.getStage(); - bufferCanvas = stage.bufferCanvas; - bufferContext = bufferCanvas.getContext(); - bufferContext.clear(); - bufferContext.save(); - bufferContext._applyLineJoin(this); - // layer might be undefined if we are using cache before adding to layer - if (!caching) { - if (layer) { - layer._applyTransform(this, bufferContext, top); - } else { - var m = this.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - } - - drawFunc.call(this, bufferContext); - bufferContext.restore(); - - var ratio = bufferCanvas.pixelRatio; - if (hasShadow && !canvas.hitCanvas) { - context.save(); - context._applyShadow(this); - context._applyOpacity(this); - context.drawImage( - bufferCanvas._canvas, - 0, - 0, - bufferCanvas.width / ratio, - bufferCanvas.height / ratio - ); - context.restore(); - } else { - context._applyOpacity(this); - context.drawImage( - bufferCanvas._canvas, - 0, - 0, - bufferCanvas.width / ratio, - bufferCanvas.height / ratio - ); - } - } else { - // if buffer canvas is not needed - context._applyLineJoin(this); - // layer might be undefined if we are using cache before adding to layer - if (!caching) { - if (layer) { - layer._applyTransform(this, context, top); - } else { - var o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - } - } - - if (hasShadow && hasStroke && !canvas.hitCanvas) { - context.save(); - // apply shadow - if (!caching) { - context._applyOpacity(this); - } - context._applyShadow(this); - drawFunc.call(this, context); - context.restore(); - // if shape has stroke we need to redraw shape - // otherwise we will see a shadow under stroke (and over fill) - // but I think this is unexpected behavior - if (this.hasFill() && this.getShadowForStrokeEnabled()) { - drawFunc.call(this, context); - } - } else if (hasShadow && !canvas.hitCanvas) { - context.save(); - if (!caching) { - context._applyOpacity(this); - } - context._applyShadow(this); - drawFunc.call(this, context); - context.restore(); - } else { - if (!caching) { - context._applyOpacity(this); - } - drawFunc.call(this, context); - } - } - context.restore(); - return this; - }, - drawHit: function(can, top, caching) { - var layer = this.getLayer(), - canvas = can || layer.hitCanvas, - context = canvas.getContext(), - drawFunc = this.hitFunc() || this.sceneFunc(), - cachedCanvas = this._cache.canvas, - cachedHitCanvas = cachedCanvas && cachedCanvas.hit; - - if (!this.shouldDrawHit(canvas)) { - return this; - } - if (layer) { - layer.clearHitCache(); - } - if (cachedHitCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedHitCanvas(context); - context.restore(); - return this; - } - if (!drawFunc) { - return this; - } - context.save(); - context._applyLineJoin(this); - if (!caching) { - if (layer) { - layer._applyTransform(this, context, top); - } else { - var o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - } - } - drawFunc.call(this, context); - context.restore(); - return this; - }, - /** - * draw hit graph using the cached scene canvas - * @method - * @memberof Konva.Shape.prototype - * @param {Integer} alphaThreshold alpha channel threshold that determines whether or not - * a pixel should be drawn onto the hit graph. Must be a value between 0 and 255. - * The default is 0 - * @returns {Konva.Shape} - * @example - * shape.cache(); - * shape.drawHitFromCache(); - */ - drawHitFromCache: function(alphaThreshold) { - var threshold = alphaThreshold || 0, - cachedCanvas = this._cache.canvas, - sceneCanvas = this._getCachedSceneCanvas(), - hitCanvas = cachedCanvas.hit, - hitContext = hitCanvas.getContext(), - hitWidth = hitCanvas.getWidth(), - hitHeight = hitCanvas.getHeight(), - hitImageData, - hitData, - len, - rgbColorKey, - i, - alpha; - - hitContext.clear(); - hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); - - try { - hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); - hitData = hitImageData.data; - len = hitData.length; - rgbColorKey = Konva.Util._hexToRgb(this.colorKey); - - // replace non transparent pixels with color key - for (i = 0; i < len; i += 4) { - alpha = hitData[i + 3]; - if (alpha > threshold) { - hitData[i] = rgbColorKey.r; - hitData[i + 1] = rgbColorKey.g; - hitData[i + 2] = rgbColorKey.b; - hitData[i + 3] = 255; - } else { - hitData[i + 3] = 0; - } - } - hitContext.putImageData(hitImageData, 0, 0); - } catch (e) { - Konva.Util.error( - 'Unable to draw hit graph from cached scene canvas. ' + e.message - ); - } - - return this; - } - }); - Konva.Util.extend(Konva.Shape, Konva.Node); - - // add getters and setters - Konva.Factory.addGetterSetter(Konva.Shape, 'stroke'); - - /** - * get/set stroke color - * @name stroke - * @method - * @memberof Konva.Shape.prototype - * @param {String} color - * @returns {String} - * @example - * // get stroke color - * var stroke = shape.stroke(); - * - * // set stroke color with color string - * shape.stroke('green'); - * - * // set stroke color with hex - * shape.stroke('#00ff00'); - * - * // set stroke color with rgb - * shape.stroke('rgb(0,255,0)'); - * - * // set stroke color with rgba and make it 50% opaque - * shape.stroke('rgba(0,255,0,0.5'); - */ - - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'strokeRed', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'strokeGreen', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'strokeBlue', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'strokeAlpha', - 1, - Konva.Validators.alphaComponent - ); - - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeWidth', 2); - - /** - * get/set stroke width - * @name strokeWidth - * @method - * @memberof Konva.Shape.prototype - * @param {Number} strokeWidth - * @returns {Number} - * @example - * // get stroke width - * var strokeWidth = shape.strokeWidth(); - * - * // set stroke width - * shape.strokeWidth(); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeHitEnabled', true); - - /** - * get/set strokeHitEnabled property. Useful for performance optimization. - * You may set `shape.strokeHitEnabled(false)`. In this case stroke will be no draw on hit canvas, so hit area - * of shape will be decreased (by lineWidth / 2). Remember that non closed line with `strokeHitEnabled = false` - * will be not drawn on hit canvas, that is mean line will no trigger pointer events (like mouseover) - * Default value is true - * @name strokeHitEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} strokeHitEnabled - * @returns {Boolean} - * @example - * // get strokeHitEnabled - * var strokeHitEnabled = shape.strokeHitEnabled(); - * - * // set strokeHitEnabled - * shape.strokeHitEnabled(); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'perfectDrawEnabled', true); - - /** - * get/set perfectDrawEnabled. If a shape has fill, stroke and opacity you may set `perfectDrawEnabled` to false to improve performance. - * See http://konvajs.github.io/docs/performance/Disable_Perfect_Draw.html for more information. - * Default value is true - * @name perfectDrawEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} perfectDrawEnabled - * @returns {Boolean} - * @example - * // get perfectDrawEnabled - * var perfectDrawEnabled = shape.perfectDrawEnabled(); - * - * // set perfectDrawEnabled - * shape.perfectDrawEnabled(); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowForStrokeEnabled', true); - - /** - * get/set shadowForStrokeEnabled. Useful for performance optimization. - * You may set `shape.shadowForStrokeEnabled(false)`. In this case stroke will be no draw shadow for stroke. - * Remember if you set `shadowForStrokeEnabled = false` for non closed line - that line with have no shadow!. - * Default value is true - * @name shadowForStrokeEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} shadowForStrokeEnabled - * @returns {Boolean} - * @example - * // get shadowForStrokeEnabled - * var shadowForStrokeEnabled = shape.shadowForStrokeEnabled(); - * - * // set shadowForStrokeEnabled - * shape.shadowForStrokeEnabled(); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'lineJoin'); - - /** - * get/set line join. Can be miter, round, or bevel. The - * default is miter - * @name lineJoin - * @method - * @memberof Konva.Shape.prototype - * @param {String} lineJoin - * @returns {String} - * @example - * // get line join - * var lineJoin = shape.lineJoin(); - * - * // set line join - * shape.lineJoin('round'); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'lineCap'); - - /** - * get/set line cap. Can be butt, round, or square - * @name lineCap - * @method - * @memberof Konva.Shape.prototype - * @param {String} lineCap - * @returns {String} - * @example - * // get line cap - * var lineCap = shape.lineCap(); - * - * // set line cap - * shape.lineCap('round'); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'sceneFunc'); - - /** - * get/set scene draw function - * @name sceneFunc - * @method - * @memberof Konva.Shape.prototype - * @param {Function} drawFunc drawing function - * @returns {Function} - * @example - * // get scene draw function - * var sceneFunc = shape.sceneFunc(); - * - * // set scene draw function - * shape.sceneFunc(function(context) { - * context.beginPath(); - * context.rect(0, 0, this.width(), this.height()); - * context.closePath(); - * context.fillStrokeShape(this); - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'hitFunc'); - - /** - * get/set hit draw function - * @name hitFunc - * @method - * @memberof Konva.Shape.prototype - * @param {Function} drawFunc drawing function - * @returns {Function} - * @example - * // get hit draw function - * var hitFunc = shape.hitFunc(); - * - * // set hit draw function - * shape.hitFunc(function(context) { - * context.beginPath(); - * context.rect(0, 0, this.width(), this.height()); - * context.closePath(); - * context.fillStrokeShape(this); - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'dash'); - - /** - * get/set dash array for stroke. - * @name dash - * @method - * @memberof Konva.Shape.prototype - * @param {Array} dash - * @returns {Array} - * @example - * // apply dashed stroke that is 10px long and 5 pixels apart - * line.dash([10, 5]); - * // apply dashed stroke that is made up of alternating dashed - * // lines that are 10px long and 20px apart, and dots that have - * // a radius of 5px and are 20px apart - * line.dash([10, 20, 0.001, 20]); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowColor'); - - /** - * get/set shadow color - * @name shadowColor - * @method - * @memberof Konva.Shape.prototype - * @param {String} color - * @returns {String} - * @example - * // get shadow color - * var shadow = shape.shadowColor(); - * - * // set shadow color with color string - * shape.shadowColor('green'); - * - * // set shadow color with hex - * shape.shadowColor('#00ff00'); - * - * // set shadow color with rgb - * shape.shadowColor('rgb(0,255,0)'); - * - * // set shadow color with rgba and make it 50% opaque - * shape.shadowColor('rgba(0,255,0,0.5'); - */ - - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'shadowRed', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'shadowGreen', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'shadowBlue', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'shadowAlpha', - 1, - Konva.Validators.alphaComponent - ); - - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowBlur'); - - /** - * get/set shadow blur - * @name shadowBlur - * @method - * @memberof Konva.Shape.prototype - * @param {Number} blur - * @returns {Number} - * @example - * // get shadow blur - * var shadowBlur = shape.shadowBlur(); - * - * // set shadow blur - * shape.shadowBlur(10); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOpacity'); - - /** - * get/set shadow opacity. must be a value between 0 and 1 - * @name shadowOpacity - * @method - * @memberof Konva.Shape.prototype - * @param {Number} opacity - * @returns {Number} - * @example - * // get shadow opacity - * var shadowOpacity = shape.shadowOpacity(); - * - * // set shadow opacity - * shape.shadowOpacity(0.5); - */ - - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'shadowOffset', [ - 'x', - 'y' - ]); - - /** - * get/set shadow offset - * @name shadowOffset - * @method - * @memberof Konva.Shape.prototype - * @param {Object} offset - * @param {Number} offset.x - * @param {Number} offset.y - * @returns {Object} - * @example - * // get shadow offset - * var shadowOffset = shape.shadowOffset(); - * - * // set shadow offset - * shape.shadowOffset({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetX', 0); - - /** - * get/set shadow offset x - * @name shadowOffsetX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get shadow offset x - * var shadowOffsetX = shape.shadowOffsetX(); - * - * // set shadow offset x - * shape.shadowOffsetX(5); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetY', 0); - - /** - * get/set shadow offset y - * @name shadowOffsetY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get shadow offset y - * var shadowOffsetY = shape.shadowOffsetY(); - * - * // set shadow offset y - * shape.shadowOffsetY(5); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternImage'); - - /** - * get/set fill pattern image - * @name fillPatternImage - * @method - * @memberof Konva.Shape.prototype - * @param {Image} image object - * @returns {Image} - * @example - * // get fill pattern image - * var fillPatternImage = shape.fillPatternImage(); - * - * // set fill pattern image - * var imageObj = new Image(); - * imageObj.onload = function() { - * shape.fillPatternImage(imageObj); - * }; - * imageObj.src = 'path/to/image/jpg'; - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fill'); - - /** - * get/set fill color - * @name fill - * @method - * @memberof Konva.Shape.prototype - * @param {String} color - * @returns {String} - * @example - * // get fill color - * var fill = shape.fill(); - * - * // set fill color with color string - * shape.fill('green'); - * - * // set fill color with hex - * shape.fill('#00ff00'); - * - * // set fill color with rgb - * shape.fill('rgb(0,255,0)'); - * - * // set fill color with rgba and make it 50% opaque - * shape.fill('rgba(0,255,0,0.5'); - * - * // shape without fill - * shape.fill(null); - */ - - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'fillRed', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'fillGreen', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'fillBlue', - 0, - Konva.Validators.RGBComponent - ); - Konva.Factory.addDeprecatedGetterSetter( - Konva.Shape, - 'fillAlpha', - 1, - Konva.Validators.alphaComponent - ); - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternX', 0); - - /** - * get/set fill pattern x - * @name fillPatternX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get fill pattern x - * var fillPatternX = shape.fillPatternX(); - * // set fill pattern x - * shape.fillPatternX(20); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternY', 0); - - /** - * get/set fill pattern y - * @name fillPatternY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get fill pattern y - * var fillPatternY = shape.fillPatternY(); - * // set fill pattern y - * shape.fillPatternY(20); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientColorStops'); - - /** - * get/set fill linear gradient color stops - * @name fillLinearGradientColorStops - * @method - * @memberof Konva.Shape.prototype - * @param {Array} colorStops - * @returns {Array} colorStops - * @example - * // get fill linear gradient color stops - * var colorStops = shape.fillLinearGradientColorStops(); - * - * // create a linear gradient that starts with red, changes to blue - * // halfway through, and then changes to green - * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); - */ - - Konva.Factory.addGetterSetter( - Konva.Shape, - 'fillRadialGradientStartRadius', - 0 - ); - - /** - * get/set fill radial gradient start radius - * @name fillRadialGradientStartRadius - * @method - * @memberof Konva.Shape.prototype - * @param {Number} radius - * @returns {Number} - * @example - * // get radial gradient start radius - * var startRadius = shape.fillRadialGradientStartRadius(); - * - * // set radial gradient start radius - * shape.fillRadialGradientStartRadius(0); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndRadius', 0); - - /** - * get/set fill radial gradient end radius - * @name fillRadialGradientEndRadius - * @method - * @memberof Konva.Shape.prototype - * @param {Number} radius - * @returns {Number} - * @example - * // get radial gradient end radius - * var endRadius = shape.fillRadialGradientEndRadius(); - * - * // set radial gradient end radius - * shape.fillRadialGradientEndRadius(100); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientColorStops'); - - /** - * get/set fill radial gradient color stops - * @name fillRadialGradientColorStops - * @method - * @memberof Konva.Shape.prototype - * @param {Number} colorStops - * @returns {Array} - * @example - * // get fill radial gradient color stops - * var colorStops = shape.fillRadialGradientColorStops(); - * - * // create a radial gradient that starts with red, changes to blue - * // halfway through, and then changes to green - * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRepeat', 'repeat'); - - /** - * get/set fill pattern repeat. Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'. The default is 'repeat' - * @name fillPatternRepeat - * @method - * @memberof Konva.Shape.prototype - * @param {String} repeat - * @returns {String} - * @example - * // get fill pattern repeat - * var repeat = shape.fillPatternRepeat(); - * - * // repeat pattern in x direction only - * shape.fillPatternRepeat('repeat-x'); - * - * // do not repeat the pattern - * shape.fillPatternRepeat('no repeat'); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillEnabled', true); - - /** - * get/set fill enabled flag - * @name fillEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get fill enabled flag - * var fillEnabled = shape.fillEnabled(); - * - * // disable fill - * shape.fillEnabled(false); - * - * // enable fill - * shape.fillEnabled(true); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeEnabled', true); - - /** - * get/set stroke enabled flag - * @name strokeEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get stroke enabled flag - * var strokeEnabled = shape.strokeEnabled(); - * - * // disable stroke - * shape.strokeEnabled(false); - * - * // enable stroke - * shape.strokeEnabled(true); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowEnabled', true); - - /** - * get/set shadow enabled flag - * @name shadowEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get shadow enabled flag - * var shadowEnabled = shape.shadowEnabled(); - * - * // disable shadow - * shape.shadowEnabled(false); - * - * // enable shadow - * shape.shadowEnabled(true); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'dashEnabled', true); - - /** - * get/set dash enabled flag - * @name dashEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get dash enabled flag - * var dashEnabled = shape.dashEnabled(); - * - * // disable dash - * shape.dashEnabled(false); - * - * // enable dash - * shape.dashEnabled(true); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeScaleEnabled', true); - - /** - * get/set strokeScale enabled flag - * @name strokeScaleEnabled - * @method - * @memberof Konva.Shape.prototype - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get stroke scale enabled flag - * var strokeScaleEnabled = shape.strokeScaleEnabled(); - * - * // disable stroke scale - * shape.strokeScaleEnabled(false); - * - * // enable stroke scale - * shape.strokeScaleEnabled(true); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPriority', 'color'); - - /** - * get/set fill priority. can be color, pattern, linear-gradient, or radial-gradient. The default is color. - * This is handy if you want to toggle between different fill types. - * @name fillPriority - * @method - * @memberof Konva.Shape.prototype - * @param {String} priority - * @returns {String} - * @example - * // get fill priority - * var fillPriority = shape.fillPriority(); - * - * // set fill priority - * shape.fillPriority('linear-gradient'); - */ - - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternOffset', [ - 'x', - 'y' - ]); - - /** - * get/set fill pattern offset - * @name fillPatternOffset - * @method - * @memberof Konva.Shape.prototype - * @param {Object} offset - * @param {Number} offset.x - * @param {Number} offset.y - * @returns {Object} - * @example - * // get fill pattern offset - * var patternOffset = shape.fillPatternOffset(); - * - * // set fill pattern offset - * shape.fillPatternOffset({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetX', 0); - /** - * get/set fill pattern offset x - * @name fillPatternOffsetX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get fill pattern offset x - * var patternOffsetX = shape.fillPatternOffsetX(); - * - * // set fill pattern offset x - * shape.fillPatternOffsetX(20); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetY', 0); - /** - * get/set fill pattern offset y - * @name fillPatternOffsetY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get fill pattern offset y - * var patternOffsetY = shape.fillPatternOffsetY(); - * - * // set fill pattern offset y - * shape.fillPatternOffsetY(10); - */ - - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternScale', [ - 'x', - 'y' - ]); - - /** - * get/set fill pattern scale - * @name fillPatternScale - * @method - * @memberof Konva.Shape.prototype - * @param {Object} scale - * @param {Number} scale.x - * @param {Number} scale.y - * @returns {Object} - * @example - * // get fill pattern scale - * var patternScale = shape.fillPatternScale(); - * - * // set fill pattern scale - * shape.fillPatternScale({ - * x: 2 - * y: 2 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleX', 1); - /** - * get/set fill pattern scale x - * @name fillPatternScaleX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get fill pattern scale x - * var patternScaleX = shape.fillPatternScaleX(); - * - * // set fill pattern scale x - * shape.fillPatternScaleX(2); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleY', 1); - /** - * get/set fill pattern scale y - * @name fillPatternScaleY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get fill pattern scale y - * var patternScaleY = shape.fillPatternScaleY(); - * - * // set fill pattern scale y - * shape.fillPatternScaleY(2); - */ - - Konva.Factory.addComponentsGetterSetter( - Konva.Shape, - 'fillLinearGradientStartPoint', - ['x', 'y'] - ); - - /** - * get/set fill linear gradient start point - * @name fillLinearGradientStartPoint - * @method - * @memberof Konva.Shape.prototype - * @param {Object} startPoint - * @param {Number} startPoint.x - * @param {Number} startPoint.y - * @returns {Object} - * @example - * // get fill linear gradient start point - * var startPoint = shape.fillLinearGradientStartPoint(); - * - * // set fill linear gradient start point - * shape.fillLinearGradientStartPoint({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter( - Konva.Shape, - 'fillLinearGradientStartPointX', - 0 - ); - /** - * get/set fill linear gradient start point x - * @name fillLinearGradientStartPointX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get fill linear gradient start point x - * var startPointX = shape.fillLinearGradientStartPointX(); - * - * // set fill linear gradient start point x - * shape.fillLinearGradientStartPointX(20); - */ - - Konva.Factory.addGetterSetter( - Konva.Shape, - 'fillLinearGradientStartPointY', - 0 - ); - /** - * get/set fill linear gradient start point y - * @name fillLinearGradientStartPointY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get fill linear gradient start point y - * var startPointY = shape.fillLinearGradientStartPointY(); - * - * // set fill linear gradient start point y - * shape.fillLinearGradientStartPointY(20); - */ - - Konva.Factory.addComponentsGetterSetter( - Konva.Shape, - 'fillLinearGradientEndPoint', - ['x', 'y'] - ); - - /** - * get/set fill linear gradient end point - * @name fillLinearGradientEndPoint - * @method - * @memberof Konva.Shape.prototype - * @param {Object} endPoint - * @param {Number} endPoint.x - * @param {Number} endPoint.y - * @returns {Object} - * @example - * // get fill linear gradient end point - * var endPoint = shape.fillLinearGradientEndPoint(); - * - * // set fill linear gradient end point - * shape.fillLinearGradientEndPoint({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointX', 0); - /** - * get/set fill linear gradient end point x - * @name fillLinearGradientEndPointX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get fill linear gradient end point x - * var endPointX = shape.fillLinearGradientEndPointX(); - * - * // set fill linear gradient end point x - * shape.fillLinearGradientEndPointX(20); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointY', 0); - /** - * get/set fill linear gradient end point y - * @name fillLinearGradientEndPointY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get fill linear gradient end point y - * var endPointY = shape.fillLinearGradientEndPointY(); - * - * // set fill linear gradient end point y - * shape.fillLinearGradientEndPointY(20); - */ - - Konva.Factory.addComponentsGetterSetter( - Konva.Shape, - 'fillRadialGradientStartPoint', - ['x', 'y'] - ); - - /** - * get/set fill radial gradient start point - * @name fillRadialGradientStartPoint - * @method - * @memberof Konva.Shape.prototype - * @param {Object} startPoint - * @param {Number} startPoint.x - * @param {Number} startPoint.y - * @returns {Object} - * @example - * // get fill radial gradient start point - * var startPoint = shape.fillRadialGradientStartPoint(); - * - * // set fill radial gradient start point - * shape.fillRadialGradientStartPoint({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter( - Konva.Shape, - 'fillRadialGradientStartPointX', - 0 - ); - /** - * get/set fill radial gradient start point x - * @name fillRadialGradientStartPointX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get fill radial gradient start point x - * var startPointX = shape.fillRadialGradientStartPointX(); - * - * // set fill radial gradient start point x - * shape.fillRadialGradientStartPointX(20); - */ - - Konva.Factory.addGetterSetter( - Konva.Shape, - 'fillRadialGradientStartPointY', - 0 - ); - /** - * get/set fill radial gradient start point y - * @name fillRadialGradientStartPointY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get fill radial gradient start point y - * var startPointY = shape.fillRadialGradientStartPointY(); - * - * // set fill radial gradient start point y - * shape.fillRadialGradientStartPointY(20); - */ - - Konva.Factory.addComponentsGetterSetter( - Konva.Shape, - 'fillRadialGradientEndPoint', - ['x', 'y'] - ); - - /** - * get/set fill radial gradient end point - * @name fillRadialGradientEndPoint - * @method - * @memberof Konva.Shape.prototype - * @param {Object} endPoint - * @param {Number} endPoint.x - * @param {Number} endPoint.y - * @returns {Object} - * @example - * // get fill radial gradient end point - * var endPoint = shape.fillRadialGradientEndPoint(); - * - * // set fill radial gradient end point - * shape.fillRadialGradientEndPoint({ - * x: 20 - * y: 10 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointX', 0); - /** - * get/set fill radial gradient end point x - * @name fillRadialGradientEndPointX - * @method - * @memberof Konva.Shape.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get fill radial gradient end point x - * var endPointX = shape.fillRadialGradientEndPointX(); - * - * // set fill radial gradient end point x - * shape.fillRadialGradientEndPointX(20); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointY', 0); - /** - * get/set fill radial gradient end point y - * @name fillRadialGradientEndPointY - * @method - * @memberof Konva.Shape.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get fill radial gradient end point y - * var endPointY = shape.fillRadialGradientEndPointY(); - * - * // set fill radial gradient end point y - * shape.fillRadialGradientEndPointY(20); - */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRotation', 0); - - /** - * get/set fill pattern rotation in degrees - * @name fillPatternRotation - * @method - * @memberof Konva.Shape.prototype - * @param {Number} rotation - * @returns {Konva.Shape} - * @example - * // get fill pattern rotation - * var patternRotation = shape.fillPatternRotation(); - * - * // set fill pattern rotation - * shape.fillPatternRotation(20); - */ - - Konva.Factory.backCompat(Konva.Shape, { - dashArray: 'dash', - getDashArray: 'getDash', - setDashArray: 'getDash', - - drawFunc: 'sceneFunc', - getDrawFunc: 'getSceneFunc', - setDrawFunc: 'setSceneFunc', - - drawHitFunc: 'hitFunc', - getDrawHitFunc: 'getHitFunc', - setDrawHitFunc: 'setHitFunc' - }); - - Konva.Collection.mapMethods(Konva.Shape); -})(Konva); - -(function() { - 'use strict'; - // CONSTANTS - var STAGE = 'Stage', - STRING = 'string', - PX = 'px', - MOUSEOUT = 'mouseout', - MOUSELEAVE = 'mouseleave', - MOUSEOVER = 'mouseover', - MOUSEENTER = 'mouseenter', - MOUSEMOVE = 'mousemove', - MOUSEDOWN = 'mousedown', - MOUSEUP = 'mouseup', - CONTEXTMENU = 'contextmenu', - CLICK = 'click', - DBL_CLICK = 'dblclick', - TOUCHSTART = 'touchstart', - TOUCHEND = 'touchend', - TAP = 'tap', - DBL_TAP = 'dbltap', - TOUCHMOVE = 'touchmove', - DOMMOUSESCROLL = 'DOMMouseScroll', - MOUSEWHEEL = 'mousewheel', - WHEEL = 'wheel', - CONTENT_MOUSEOUT = 'contentMouseout', - CONTENT_MOUSEOVER = 'contentMouseover', - CONTENT_MOUSEMOVE = 'contentMousemove', - CONTENT_MOUSEDOWN = 'contentMousedown', - CONTENT_MOUSEUP = 'contentMouseup', - CONTENT_CONTEXTMENU = 'contentContextmenu', - CONTENT_CLICK = 'contentClick', - CONTENT_DBL_CLICK = 'contentDblclick', - CONTENT_TOUCHSTART = 'contentTouchstart', - CONTENT_TOUCHEND = 'contentTouchend', - CONTENT_DBL_TAP = 'contentDbltap', - CONTENT_TAP = 'contentTap', - CONTENT_TOUCHMOVE = 'contentTouchmove', - CONTENT_WHEEL = 'contentWheel', - DIV = 'div', - RELATIVE = 'relative', - KONVA_CONTENT = 'konvajs-content', - SPACE = ' ', - UNDERSCORE = '_', - CONTAINER = 'container', - EMPTY_STRING = '', - EVENTS = [ - MOUSEDOWN, - MOUSEMOVE, - MOUSEUP, - MOUSEOUT, - TOUCHSTART, - TOUCHMOVE, - TOUCHEND, - MOUSEOVER, - DOMMOUSESCROLL, - MOUSEWHEEL, - WHEEL, - CONTEXTMENU - ], - // cached variables - eventsLength = EVENTS.length; - - function addEvent(ctx, eventName) { - ctx.content.addEventListener( - eventName, - function(evt) { - ctx[UNDERSCORE + eventName](evt); - }, - false - ); - } - - /** - * Stage constructor. A stage is used to contain multiple layers - * @constructor - * @memberof Konva - * @augments Konva.Container - * @param {Object} config - * @param {String|Element} config.container Container selector or DOM element - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var stage = new Konva.Stage({ - * width: 500, - * height: 800, - * container: 'containerId' // or "#containerId" or ".containerClass" - * }); - */ - Konva.Stage = function(config) { - this.___init(config); - }; - - Konva.Util.addMethods(Konva.Stage, { - ___init: function(config) { - this.nodeType = STAGE; - // call super constructor - Konva.Container.call(this, config); - this._id = Konva.idCounter++; - this._buildDOM(); - this._bindContentEvents(); - this._enableNestedTransforms = false; - Konva.stages.push(this); - }, - _validateAdd: function(child) { - if (child.getType() !== 'Layer') { - Konva.Util.throw('You may only add layers to the stage.'); - } - }, - /** - * set container dom element which contains the stage wrapper div element - * @method - * @memberof Konva.Stage.prototype - * @param {DomElement} container can pass in a dom element or id string - */ - setContainer: function(container) { - if (typeof container === STRING) { - if (container.charAt(0) === '.') { - var className = container.slice(1); - container = Konva.document.getElementsByClassName(className)[0]; - } else { - var id; - if (container.charAt(0) !== '#') { - id = container; - } else { - id = container.slice(1); - } - container = Konva.document.getElementById(id); - } - if (!container) { - throw 'Can not find container in document with id ' + id; - } - } - this._setAttr(CONTAINER, container); - return this; - }, - shouldDrawHit: function() { - return true; - }, - draw: function() { - Konva.Node.prototype.draw.call(this); - return this; - }, - /** - * draw layer scene graphs - * @name draw - * @method - * @memberof Konva.Stage.prototype - */ - - /** - * draw layer hit graphs - * @name drawHit - * @method - * @memberof Konva.Stage.prototype - */ - - /** - * set height - * @method - * @memberof Konva.Stage.prototype - * @param {Number} height - */ - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - this._resizeDOM(); - return this; - }, - /** - * set width - * @method - * @memberof Konva.Stage.prototype - * @param {Number} width - */ - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - this._resizeDOM(); - return this; - }, - /** - * clear all layers - * @method - * @memberof Konva.Stage.prototype - */ - clear: function() { - var layers = this.children, len = layers.length, n; - - for (n = 0; n < len; n++) { - layers[n].clear(); - } - return this; - }, - clone: function(obj) { - if (!obj) { - obj = {}; - } - obj.container = Konva.document.createElement(DIV); - return Konva.Container.prototype.clone.call(this, obj); - }, - /** - * destroy stage - * @method - * @memberof Konva.Stage.prototype - */ - destroy: function() { - var content = this.content; - Konva.Container.prototype.destroy.call(this); - - if (content && Konva.Util._isInDocument(content)) { - this.getContainer().removeChild(content); - } - var index = Konva.stages.indexOf(this); - if (index > -1) { - Konva.stages.splice(index, 1); - } - return this; - }, - /** - * get pointer position which can be a touch position or mouse position - * @method - * @memberof Konva.Stage.prototype - * @returns {Object} - */ - getPointerPosition: function() { - return this.pointerPos; - }, - getStage: function() { - return this; - }, - /** - * get stage content div element which has the - * the class name "konvajs-content" - * @method - * @memberof Konva.Stage.prototype - */ - getContent: function() { - return this.content; - }, - /** - * Creates a composite data URL - * @method - * @memberof Konva.Stage.prototype - * @param {Object} config - * @param {Function} [config.callback] function executed when the composite has completed. Deprecated as method is sync now. - * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toDataURL: function(config) { - config = config || {}; - - var mimeType = config.mimeType || null, - quality = config.quality || null, - x = config.x || 0, - y = config.y || 0, - canvas = new Konva.SceneCanvas({ - width: config.width || this.getWidth(), - height: config.height || this.getHeight(), - pixelRatio: config.pixelRatio - }), - _context = canvas.getContext()._context, - layers = this.children; - - if (x || y) { - _context.translate((-1) * x, (-1) * y); - } - - layers.each(function(layer) { - var width = layer.getCanvas().getWidth(); - var height = layer.getCanvas().getHeight(); - var ratio = layer.getCanvas().getPixelRatio(); - _context.drawImage( - layer.getCanvas()._canvas, - 0, - 0, - width / ratio, - height / ratio - ); - }); - var src = canvas.toDataURL(mimeType, quality); - - if (config.callback) { - config.callback(src); - } - - return src; - }, - /** - * converts stage into an image. - * @method - * @memberof Konva.Stage.prototype - * @param {Object} config - * @param {Function} config.callback function executed when the composite has completed - * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". - * "image/png" is the default - * @param {Number} [config.x] x position of canvas section - * @param {Number} [config.y] y position of canvas section - * @param {Number} [config.width] width of canvas section - * @param {Number} [config.height] height of canvas section - * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, - * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 - * is very high quality - */ - toImage: function(config) { - var cb = config.callback; - - config.callback = function(dataUrl) { - Konva.Util._getImage(dataUrl, function(img) { - cb(img); - }); - }; - this.toDataURL(config); - }, - /** - * get visible intersection shape. This is the preferred - * method for determining if a point intersects a shape or not - * @method - * @memberof Konva.Stage.prototype - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @param {String} [selector] - * @returns {Konva.Node} - * @example - * var shape = stage.getIntersection({x: 50, y: 50}); - * // or if you interested in shape parent: - * var group = stage.getIntersection({x: 50, y: 50}, 'Group'); - */ - getIntersection: function(pos, selector) { - var layers = this.getChildren(), - len = layers.length, - end = len - 1, - n, - shape; - - for (n = end; n >= 0; n--) { - shape = layers[n].getIntersection(pos, selector); - if (shape) { - return shape; - } - } - - return null; - }, - _resizeDOM: function() { - if (this.content) { - var width = this.getWidth(), - height = this.getHeight(), - layers = this.getChildren(), - len = layers.length, - n, - layer; - - // set content dimensions - this.content.style.width = width + PX; - this.content.style.height = height + PX; - - this.bufferCanvas.setSize(width, height); - this.bufferHitCanvas.setSize(width, height); - - // set layer dimensions - for (n = 0; n < len; n++) { - layer = layers[n]; - layer.setSize(width, height); - layer.batchDraw(); - } - } - }, - /** - * add layer or layers to stage - * @method - * @memberof Konva.Stage.prototype - * @param {...Konva.Layer} layer - * @example - * stage.add(layer1, layer2, layer3); - */ - add: function(layer) { - if (arguments.length > 1) { - for (var i = 0; i < arguments.length; i++) { - this.add(arguments[i]); - } - return this; - } - Konva.Container.prototype.add.call(this, layer); - layer._setCanvasSize(this.width(), this.height()); - - // draw layer and append canvas to container - layer.draw(); - this.content.appendChild(layer.canvas._canvas); - - // chainable - return this; - }, - getParent: function() { - return null; - }, - getLayer: function() { - return null; - }, - /** - * returns a {@link Konva.Collection} of layers - * @method - * @memberof Konva.Stage.prototype - */ - getLayers: function() { - return this.getChildren(); - }, - _bindContentEvents: function() { - for (var n = 0; n < eventsLength; n++) { - addEvent(this, EVENTS[n]); - } - }, - _mouseover: function(evt) { - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - this._fire(CONTENT_MOUSEOVER, { evt: evt }); - } - }, - _mouseout: function(evt) { - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var targetShape = this.targetShape; - - if (targetShape && !Konva.isDragging()) { - targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); - targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); - this.targetShape = null; - } - this.pointerPos = undefined; - - this._fire(CONTENT_MOUSEOUT, { evt: evt }); - } - }, - _mousemove: function(evt) { - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchmove(evt); - } - // workaround fake mousemove event in chrome browser https://code.google.com/p/chromium/issues/detail?id=161464 - if ( - (typeof evt.movementX !== 'undefined' || - typeof evt.movementY !== 'undefined') && - evt.movementY === 0 && - evt.movementX === 0 - ) { - return null; - } - if (Konva.UA.mobile) { - return null; - } - this._setPointerPosition(evt); - var shape; - - if (!Konva.isDragging()) { - shape = this.getIntersection(this.getPointerPosition()); - if (shape && shape.isListening()) { - if ( - !Konva.isDragging() && - (!this.targetShape || this.targetShape._id !== shape._id) - ) { - if (this.targetShape) { - this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }, shape); - this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }, shape); - } - shape._fireAndBubble(MOUSEOVER, { evt: evt }, this.targetShape); - shape._fireAndBubble(MOUSEENTER, { evt: evt }, this.targetShape); - this.targetShape = shape; - } else { - shape._fireAndBubble(MOUSEMOVE, { evt: evt }); - } - } else { - /* - * if no shape was detected, clear target shape and try - * to run mouseout from previous target shape - */ - if (this.targetShape && !Konva.isDragging()) { - this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); - this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); - this.targetShape = null; - } - } - - // content event - this._fire(CONTENT_MOUSEMOVE, { evt: evt }); - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _mousedown: function(evt) { - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchstart(evt); - } - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - Konva.listenClickTap = true; - - if (shape && shape.isListening()) { - this.clickStartShape = shape; - shape._fireAndBubble(MOUSEDOWN, { evt: evt }); - } - - // content event - this._fire(CONTENT_MOUSEDOWN, { evt: evt }); - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _mouseup: function(evt) { - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchend(evt); - } - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()), - clickStartShape = this.clickStartShape, - fireDblClick = false, - dd = Konva.DD; - - if (Konva.inDblClickWindow) { - fireDblClick = true; - Konva.inDblClickWindow = false; - } else if (!dd || !dd.justDragged) { - // don't set inDblClickWindow after dragging - Konva.inDblClickWindow = true; - } else if (dd) { - dd.justDragged = false; - } - - setTimeout( - function() { - Konva.inDblClickWindow = false; - }, - Konva.dblClickWindow - ); - - if (shape && shape.isListening()) { - shape._fireAndBubble(MOUSEUP, { evt: evt }); - - // detect if click or double click occurred - if ( - Konva.listenClickTap && - clickStartShape && - clickStartShape._id === shape._id - ) { - shape._fireAndBubble(CLICK, { evt: evt }); - - if (fireDblClick) { - shape._fireAndBubble(DBL_CLICK, { evt: evt }); - } - } - } - // content events - this._fire(CONTENT_MOUSEUP, { evt: evt }); - if (Konva.listenClickTap) { - this._fire(CONTENT_CLICK, { evt: evt }); - if (fireDblClick) { - this._fire(CONTENT_DBL_CLICK, { evt: evt }); - } - } - - Konva.listenClickTap = false; - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _contextmenu: function(evt) { - this._fire(CONTENT_CONTEXTMENU, { evt: evt }); - }, - _touchstart: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - Konva.listenClickTap = true; - - if (shape && shape.isListening()) { - this.tapStartShape = shape; - shape._fireAndBubble(TOUCHSTART, { evt: evt }); - - // only call preventDefault if the shape is listening for events - if ( - shape.isListening() && shape.preventDefault() && evt.preventDefault - ) { - evt.preventDefault(); - } - } - // content event - this._fire(CONTENT_TOUCHSTART, { evt: evt }); - }, - _touchend: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()), - fireDblClick = false; - - if (Konva.inDblClickWindow) { - fireDblClick = true; - Konva.inDblClickWindow = false; - } else { - Konva.inDblClickWindow = true; - } - - setTimeout( - function() { - Konva.inDblClickWindow = false; - }, - Konva.dblClickWindow - ); - - if (shape && shape.isListening()) { - shape._fireAndBubble(TOUCHEND, { evt: evt }); - - // detect if tap or double tap occurred - if ( - Konva.listenClickTap && - this.tapStartShape && - shape._id === this.tapStartShape._id - ) { - shape._fireAndBubble(TAP, { evt: evt }); - - if (fireDblClick) { - shape._fireAndBubble(DBL_TAP, { evt: evt }); - } - } - // only call preventDefault if the shape is listening for events - if ( - shape.isListening() && shape.preventDefault() && evt.preventDefault - ) { - evt.preventDefault(); - } - } - // content events - this._fire(CONTENT_TOUCHEND, { evt: evt }); - if (Konva.listenClickTap) { - this._fire(CONTENT_TAP, { evt: evt }); - if (fireDblClick) { - this._fire(CONTENT_DBL_TAP, { evt: evt }); - } - } - - Konva.listenClickTap = false; - }, - _touchmove: function(evt) { - this._setPointerPosition(evt); - var dd = Konva.DD, shape; - if (!Konva.isDragging()) { - shape = this.getIntersection(this.getPointerPosition()); - if (shape && shape.isListening()) { - shape._fireAndBubble(TOUCHMOVE, { evt: evt }); - // only call preventDefault if the shape is listening for events - if ( - shape.isListening() && shape.preventDefault() && evt.preventDefault - ) { - evt.preventDefault(); - } - } - this._fire(CONTENT_TOUCHMOVE, { evt: evt }); - } - if (dd) { - if (Konva.isDragging() && Konva.DD.node.preventDefault()) { - evt.preventDefault(); - } - } - }, - _DOMMouseScroll: function(evt) { - this._mousewheel(evt); - }, - _mousewheel: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - if (shape && shape.isListening()) { - shape._fireAndBubble(WHEEL, { evt: evt }); - } - this._fire(CONTENT_WHEEL, { evt: evt }); - }, - _wheel: function(evt) { - this._mousewheel(evt); - }, - _setPointerPosition: function(evt) { - var contentPosition = this._getContentPosition(), x = null, y = null; - evt = evt ? evt : window.event; - - // touch events - if (evt.touches !== undefined) { - // currently, only handle one finger - if (evt.touches.length > 0) { - var touch = evt.touches[0]; - // get the information for finger #1 - x = touch.clientX - contentPosition.left; - y = touch.clientY - contentPosition.top; - } - } else { - // mouse events - x = evt.clientX - contentPosition.left; - y = evt.clientY - contentPosition.top; - } - if (x !== null && y !== null) { - this.pointerPos = { - x: x, - y: y - }; - } - }, - _getContentPosition: function() { - var rect = this.content.getBoundingClientRect - ? this.content.getBoundingClientRect() - : { top: 0, left: 0 }; - return { - top: rect.top, - left: rect.left - }; - }, - _buildDOM: function() { - var container = this.getContainer(); - if (!container) { - if (Konva.Util.isBrowser()) { - throw 'Stage has no container. A container is required.'; - } else { - // automatically create element for jsdom in nodejs env - container = Konva.document.createElement(DIV); - } - } - // clear content inside container - container.innerHTML = EMPTY_STRING; - - // content - this.content = Konva.document.createElement(DIV); - this.content.style.position = RELATIVE; - this.content.className = KONVA_CONTENT; - this.content.setAttribute('role', 'presentation'); - container.appendChild(this.content); - - // the buffer canvas pixel ratio must be 1 because it is used as an - // intermediate canvas before copying the result onto a scene canvas. - // not setting it to 1 will result in an over compensation - this.bufferCanvas = new Konva.SceneCanvas(); - this.bufferHitCanvas = new Konva.HitCanvas({ pixelRatio: 1 }); - - this._resizeDOM(); - }, - _onContent: function(typesStr, handler) { - var types = typesStr.split(SPACE), len = types.length, n, baseEvent; - - for (n = 0; n < len; n++) { - baseEvent = types[n]; - this.content.addEventListener(baseEvent, handler, false); - } - }, - // currently cache function is now working for stage, because stage has no its own canvas element - // TODO: may be it is better to cache all children layers? - cache: function() { - Konva.Util.warn( - 'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.' - ); - }, - clearCache: function() {} - }); - Konva.Util.extend(Konva.Stage, Konva.Container); - - // add getters and setters - Konva.Factory.addGetter(Konva.Stage, 'container'); - Konva.Factory.addOverloadedGetterSetter(Konva.Stage, 'container'); - - /** - * get container DOM element - * @name container - * @method - * @memberof Konva.Stage.prototype - * @returns {DomElement} container - * @example - * // get container - * var container = stage.container(); - * // set container - * var container = document.createElement('div'); - * body.appendChild(container); - * stage.container(container); - */ -})(); - -(function() { - 'use strict'; - /** - * BaseLayer constructor. - * @constructor - * @memberof Konva - * @augments Konva.Container - * @param {Object} config - * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want - * to clear the canvas before each layer draw. The default value is true. - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * * @param {Object} [config.clip] set clip - * @param {Number} [config.clipX] set clip x - * @param {Number} [config.clipY] set clip y - * @param {Number} [config.clipWidth] set clip width - * @param {Number} [config.clipHeight] set clip height - * @param {Function} [config.clipFunc] set clip func - - * @example - * var layer = new Konva.Layer(); - */ - Konva.BaseLayer = function(config) { - this.___init(config); - }; - - Konva.Util.addMethods(Konva.BaseLayer, { - ___init: function(config) { - this.nodeType = 'Layer'; - Konva.Container.call(this, config); - }, - createPNGStream: function() { - return this.canvas._canvas.createPNGStream(); - }, - /** - * get layer canvas - * @method - * @memberof Konva.BaseLayer.prototype - */ - getCanvas: function() { - return this.canvas; - }, - /** - * get layer hit canvas - * @method - * @memberof Konva.BaseLayer.prototype - */ - getHitCanvas: function() { - return this.hitCanvas; - }, - /** - * get layer canvas context - * @method - * @memberof Konva.BaseLayer.prototype - */ - getContext: function() { - return this.getCanvas().getContext(); - }, - /** - * clear scene and hit canvas contexts tied to the layer - * @method - * @memberof Konva.BaseLayer.prototype - * @param {Object} [bounds] - * @param {Number} [bounds.x] - * @param {Number} [bounds.y] - * @param {Number} [bounds.width] - * @param {Number} [bounds.height] - * @example - * layer.clear(); - * layer.clear({ - * x : 0, - * y : 0, - * width : 100, - * height : 100 - * }); - */ - clear: function(bounds) { - this.getContext().clear(bounds); - return this; - }, - clearHitCache: function() { - this._hitImageData = undefined; - }, - // extend Node.prototype.setZIndex - setZIndex: function(index) { - Konva.Node.prototype.setZIndex.call(this, index); - var stage = this.getStage(); - if (stage) { - stage.content.removeChild(this.getCanvas()._canvas); - - if (index < stage.getChildren().length - 1) { - stage.content.insertBefore( - this.getCanvas()._canvas, - stage.getChildren()[index + 1].getCanvas()._canvas - ); - } else { - stage.content.appendChild(this.getCanvas()._canvas); - } - } - return this; - }, - // extend Node.prototype.moveToTop - moveToTop: function() { - Konva.Node.prototype.moveToTop.call(this); - var stage = this.getStage(); - if (stage) { - stage.content.removeChild(this.getCanvas()._canvas); - stage.content.appendChild(this.getCanvas()._canvas); - } - return this; - }, - // extend Node.prototype.moveUp - moveUp: function() { - var moved = Konva.Node.prototype.moveUp.call(this); - if (!moved) { - return this; - } - var stage = this.getStage(); - if (!stage) { - return this; - } - stage.content.removeChild(this.getCanvas()._canvas); - - if (this.index < stage.getChildren().length - 1) { - stage.content.insertBefore( - this.getCanvas()._canvas, - stage.getChildren()[this.index + 1].getCanvas()._canvas - ); - } else { - stage.content.appendChild(this.getCanvas()._canvas); - } - return this; - }, - // extend Node.prototype.moveDown - moveDown: function() { - if (Konva.Node.prototype.moveDown.call(this)) { - var stage = this.getStage(); - if (stage) { - var children = stage.getChildren(); - stage.content.removeChild(this.getCanvas()._canvas); - stage.content.insertBefore( - this.getCanvas()._canvas, - children[this.index + 1].getCanvas()._canvas - ); - } - } - return this; - }, - // extend Node.prototype.moveToBottom - moveToBottom: function() { - if (Konva.Node.prototype.moveToBottom.call(this)) { - var stage = this.getStage(); - if (stage) { - var children = stage.getChildren(); - stage.content.removeChild(this.getCanvas()._canvas); - stage.content.insertBefore( - this.getCanvas()._canvas, - children[1].getCanvas()._canvas - ); - } - } - return this; - }, - getLayer: function() { - return this; - }, - remove: function() { - var _canvas = this.getCanvas()._canvas; - - Konva.Node.prototype.remove.call(this); - - if (_canvas && _canvas.parentNode && Konva.Util._isInDocument(_canvas)) { - _canvas.parentNode.removeChild(_canvas); - } - return this; - }, - getStage: function() { - return this.parent; - }, - setSize: function(width, height) { - this.canvas.setSize(width, height); - return this; - }, - /** - * get/set width of layer.getter return width of stage. setter doing nothing. - * if you want change width use `stage.width(value);` - * @name width - * @method - * @memberof Konva.BaseLayer.prototype - * @returns {Number} - * @example - * var width = layer.width(); - */ - getWidth: function() { - if (this.parent) { - return this.parent.getWidth(); - } - }, - setWidth: function() { - Konva.Util.warn( - 'Can not change width of layer. Use "stage.width(value)" function instead.' - ); - }, - /** - * get/set height of layer.getter return height of stage. setter doing nothing. - * if you want change height use `stage.height(value);` - * @name height - * @method - * @memberof Konva.BaseLayer.prototype - * @returns {Number} - * @example - * var height = layer.height(); - */ - getHeight: function() { - if (this.parent) { - return this.parent.getHeight(); - } - }, - setHeight: function() { - Konva.Util.warn( - 'Can not change height of layer. Use "stage.height(value)" function instead.' - ); - }, - // the apply transform method is handled by the Layer and FastLayer class - // because it is up to the layer to decide if an absolute or relative transform - // should be used - _applyTransform: function(shape, context, top) { - var m = shape.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - }); - Konva.Util.extend(Konva.BaseLayer, Konva.Container); - - // add getters and setters - Konva.Factory.addGetterSetter(Konva.BaseLayer, 'clearBeforeDraw', true); - /** - * get/set clearBeforeDraw flag which determines if the layer is cleared or not - * before drawing - * @name clearBeforeDraw - * @method - * @memberof Konva.BaseLayer.prototype - * @param {Boolean} clearBeforeDraw - * @returns {Boolean} - * @example - * // get clearBeforeDraw flag - * var clearBeforeDraw = layer.clearBeforeDraw(); - * - * // disable clear before draw - * layer.clearBeforeDraw(false); - * - * // enable clear before draw - * layer.clearBeforeDraw(true); - */ - - Konva.Collection.mapMethods(Konva.BaseLayer); -})(); - -(function() { - 'use strict'; - // constants - var HASH = '#', - BEFORE_DRAW = 'beforeDraw', - DRAW = 'draw', - /* - * 2 - 3 - 4 - * | | - * 1 - 0 5 - * | - * 8 - 7 - 6 - */ - INTERSECTION_OFFSETS = [ - { x: 0, y: 0 }, // 0 - { x: -1, y: 0 }, // 1 - { x: -1, y: -1 }, // 2 - { x: 0, y: -1 }, // 3 - { x: 1, y: -1 }, // 4 - { x: 1, y: 0 }, // 5 - { x: 1, y: 1 }, // 6 - { x: 0, y: 1 }, // 7 - { x: -1, y: 1 } // 8 - ], - INTERSECTION_OFFSETS_LEN = INTERSECTION_OFFSETS.length; - - /** - * Layer constructor. Layers are tied to their own canvas element and are used - * to contain groups or shapes. - * @constructor - * @memberof Konva - * @augments Konva.BaseLayer - * @param {Object} config - * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want - * to clear the canvas before each layer draw. The default value is true. - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * * @param {Object} [config.clip] set clip - * @param {Number} [config.clipX] set clip x - * @param {Number} [config.clipY] set clip y - * @param {Number} [config.clipWidth] set clip width - * @param {Number} [config.clipHeight] set clip height - * @param {Function} [config.clipFunc] set clip func - - * @example - * var layer = new Konva.Layer(); - */ - Konva.Layer = function(config) { - this.____init(config); - }; - - Konva.Util.addMethods(Konva.Layer, { - ____init: function(config) { - this.nodeType = 'Layer'; - this.canvas = new Konva.SceneCanvas(); - this.hitCanvas = new Konva.HitCanvas({ - pixelRatio: 1 - }); - // call super constructor - Konva.BaseLayer.call(this, config); - }, - _setCanvasSize: function(width, height) { - this.canvas.setSize(width, height); - this.hitCanvas.setSize(width, height); - }, - _validateAdd: function(child) { - var type = child.getType(); - if (type !== 'Group' && type !== 'Shape') { - Konva.Util.throw('You may only add groups and shapes to a layer.'); - } - }, - /** - * get visible intersection shape. This is the preferred - * method for determining if a point intersects a shape or not - * also you may pass optional selector parametr to return ancestor of intersected shape - * @method - * @memberof Konva.Layer.prototype - * @param {Object} pos - * @param {Number} pos.x - * @param {Number} pos.y - * @param {String} [selector] - * @returns {Konva.Node} - * @example - * var shape = layer.getIntersection({x: 50, y: 50}); - * // or if you interested in shape parent: - * var group = layer.getIntersection({x: 50, y: 50}, 'Group'); - */ - getIntersection: function(pos, selector) { - var obj, i, intersectionOffset, shape; - - if (!this.hitGraphEnabled() || !this.isVisible()) { - return null; - } - // in some cases antialiased area may be bigger than 1px - // it is possible if we will cache node, then scale it a lot - // TODO: check { 0; 0 } point before loop, and remove it from INTERSECTION_OFFSETS. - var spiralSearchDistance = 1; - var continueSearch = false; - while (true) { - for (i = 0; i < INTERSECTION_OFFSETS_LEN; i++) { - intersectionOffset = INTERSECTION_OFFSETS[i]; - obj = this._getIntersection({ - x: pos.x + intersectionOffset.x * spiralSearchDistance, - y: pos.y + intersectionOffset.y * spiralSearchDistance - }); - shape = obj.shape; - if (shape && selector) { - return shape.findAncestor(selector, true); - } else if (shape) { - return shape; - } - // we should continue search if we found antialiased pixel - // that means our node somewhere very close - continueSearch = !!obj.antialiased; - // stop search if found empty pixel - if (!obj.antialiased) { - break; - } - } - // if no shape, and no antialiased pixel, we should end searching - if (continueSearch) { - spiralSearchDistance += 1; - } else { - return null; - } - } - }, - _getImageData: function(x, y) { - var width = this.hitCanvas.width || 1, - height = this.hitCanvas.height || 1, - index = Math.round(y) * width + Math.round(x); - - if (!this._hitImageData) { - this._hitImageData = this.hitCanvas.context.getImageData( - 0, - 0, - width, - height - ); - } - - return [ - this._hitImageData.data[4 * index + 0], // Red - this._hitImageData.data[4 * index + 1], // Green - this._hitImageData.data[4 * index + 2], // Blue - this._hitImageData.data[4 * index + 3] // Alpha - ]; - }, - _getIntersection: function(pos) { - var ratio = this.hitCanvas.pixelRatio; - var p = this.hitCanvas.context.getImageData( - Math.round(pos.x * ratio), - Math.round(pos.y * ratio), - 1, - 1 - ).data, - p3 = p[3], - colorKey, - shape; - // fully opaque pixel - if (p3 === 255) { - colorKey = Konva.Util._rgbToHex(p[0], p[1], p[2]); - shape = Konva.shapes[HASH + colorKey]; - if (shape) { - return { - shape: shape - }; - } - return { - antialiased: true - }; - } else if (p3 > 0) { - // antialiased pixel - return { - antialiased: true - }; - } - // empty pixel - return {}; - }, - drawScene: function(can, top) { - var layer = this.getLayer(), canvas = can || (layer && layer.getCanvas()); - - this._fire(BEFORE_DRAW, { - node: this - }); - - if (this.getClearBeforeDraw()) { - canvas.getContext().clear(); - } - - Konva.Container.prototype.drawScene.call(this, canvas, top); - - this._fire(DRAW, { - node: this - }); - - return this; - }, - drawHit: function(can, top) { - var layer = this.getLayer(), canvas = can || (layer && layer.hitCanvas); - - if (layer && layer.getClearBeforeDraw()) { - layer.getHitCanvas().getContext().clear(); - } - - Konva.Container.prototype.drawHit.call(this, canvas, top); - this.imageData = null; // Clear imageData cache - return this; - }, - clear: function(bounds) { - Konva.BaseLayer.prototype.clear.call(this, bounds); - this.getHitCanvas().getContext().clear(bounds); - this.imageData = null; // Clear getImageData cache - return this; - }, - // extend Node.prototype.setVisible - setVisible: function(visible) { - Konva.Node.prototype.setVisible.call(this, visible); - if (visible) { - this.getCanvas()._canvas.style.display = 'block'; - this.hitCanvas._canvas.style.display = 'block'; - } else { - this.getCanvas()._canvas.style.display = 'none'; - this.hitCanvas._canvas.style.display = 'none'; - } - return this; - }, - /** - * enable hit graph - * @name enableHitGraph - * @method - * @memberof Konva.Layer.prototype - * @returns {Layer} - */ - enableHitGraph: function() { - this.setHitGraphEnabled(true); - return this; - }, - /** - * disable hit graph - * @name disableHitGraph - * @method - * @memberof Konva.Layer.prototype - * @returns {Layer} - */ - disableHitGraph: function() { - this.setHitGraphEnabled(false); - return this; - }, - setSize: function(width, height) { - Konva.BaseLayer.prototype.setSize.call(this, width, height); - this.hitCanvas.setSize(width, height); - return this; - } - }); - Konva.Util.extend(Konva.Layer, Konva.BaseLayer); - - Konva.Factory.addGetterSetter(Konva.Layer, 'hitGraphEnabled', true); - /** - * get/set hitGraphEnabled flag. Disabling the hit graph will greatly increase - * draw performance because the hit graph will not be redrawn each time the layer is - * drawn. This, however, also disables mouse/touch event detection - * @name hitGraphEnabled - * @method - * @memberof Konva.Layer.prototype - * @param {Boolean} enabled - * @returns {Boolean} - * @example - * // get hitGraphEnabled flag - * var hitGraphEnabled = layer.hitGraphEnabled(); - * - * // disable hit graph - * layer.hitGraphEnabled(false); - * - * // enable hit graph - * layer.hitGraphEnabled(true); - */ - Konva.Collection.mapMethods(Konva.Layer); -})(); - -(function() { - 'use strict'; - /** - * FastLayer constructor. Layers are tied to their own canvas element and are used - * to contain shapes only. If you don't need node nesting, mouse and touch interactions, - * or event pub/sub, you should use FastLayer instead of Layer to create your layers. - * It renders about 2x faster than normal layers. - * @constructor - * @memberof Konva - * @augments Konva.BaseLayer - * @param {Object} config - * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want - * to clear the canvas before each layer draw. The default value is true. - * @param {Boolean} [config.visible] - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * * @param {Object} [config.clip] set clip - * @param {Number} [config.clipX] set clip x - * @param {Number} [config.clipY] set clip y - * @param {Number} [config.clipWidth] set clip width - * @param {Number} [config.clipHeight] set clip height - * @param {Function} [config.clipFunc] set clip func - - * @example - * var layer = new Konva.FastLayer(); - */ - Konva.FastLayer = function(config) { - this.____init(config); - }; - - Konva.Util.addMethods(Konva.FastLayer, { - ____init: function(config) { - this.nodeType = 'Layer'; - this.canvas = new Konva.SceneCanvas(); - // call super constructor - Konva.BaseLayer.call(this, config); - }, - _validateAdd: function(child) { - var type = child.getType(); - if (type !== 'Shape') { - Konva.Util.throw('You may only add shapes to a fast layer.'); - } - }, - _setCanvasSize: function(width, height) { - this.canvas.setSize(width, height); - }, - hitGraphEnabled: function() { - return false; - }, - getIntersection: function() { - return null; - }, - drawScene: function(can) { - var layer = this.getLayer(), canvas = can || (layer && layer.getCanvas()); - - if (this.getClearBeforeDraw()) { - canvas.getContext().clear(); - } - - Konva.Container.prototype.drawScene.call(this, canvas); - - return this; - }, - draw: function() { - this.drawScene(); - return this; - }, - // extend Node.prototype.setVisible - setVisible: function(visible) { - Konva.Node.prototype.setVisible.call(this, visible); - if (visible) { - this.getCanvas()._canvas.style.display = 'block'; - } else { - this.getCanvas()._canvas.style.display = 'none'; - } - return this; - } - }); - Konva.Util.extend(Konva.FastLayer, Konva.BaseLayer); - - Konva.Collection.mapMethods(Konva.FastLayer); -})(); - -(function() { - 'use strict'; - /** - * Group constructor. Groups are used to contain shapes or other groups. - * @constructor - * @memberof Konva - * @augments Konva.Container - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * * @param {Object} [config.clip] set clip - * @param {Number} [config.clipX] set clip x - * @param {Number} [config.clipY] set clip y - * @param {Number} [config.clipWidth] set clip width - * @param {Number} [config.clipHeight] set clip height - * @param {Function} [config.clipFunc] set clip func - - * @example - * var group = new Konva.Group(); - */ - Konva.Group = function(config) { - this.___init(config); - }; - - Konva.Util.addMethods(Konva.Group, { - ___init: function(config) { - this.nodeType = 'Group'; - // call super constructor - Konva.Container.call(this, config); - }, - _validateAdd: function(child) { - var type = child.getType(); - if (type !== 'Group' && type !== 'Shape') { - Konva.Util.throw('You may only add groups and shapes to groups.'); - } - } - }); - Konva.Util.extend(Konva.Group, Konva.Container); - - Konva.Collection.mapMethods(Konva.Group); -})(); - -(function(Konva) { - 'use strict'; - var now = (function() { - if (Konva.global.performance && Konva.global.performance.now) { - return function() { - return Konva.global.performance.now(); - }; - } - - return function() { - return new Date().getTime(); - }; - })(); - - function FRAF(callback) { - setTimeout(callback, 1000 / 60); - } - - var RAF = (function() { - return Konva.global.requestAnimationFrame || - Konva.global.webkitRequestAnimationFrame || - Konva.global.mozRequestAnimationFrame || - Konva.global.oRequestAnimationFrame || - Konva.global.msRequestAnimationFrame || - FRAF; - })(); - - function requestAnimFrame() { - return RAF.apply(Konva.global, arguments); - } - - /** - * Animation constructor. A stage is used to contain multiple layers and handle - * @constructor - * @memberof Konva - * @param {Function} func function executed on each animation frame. The function is passed a frame object, which contains - * timeDiff, lastTime, time, and frameRate properties. The timeDiff property is the number of milliseconds that have passed - * since the last animation frame. The lastTime property is time in milliseconds that elapsed from the moment the animation started - * to the last animation frame. The time property is the time in milliseconds that ellapsed from the moment the animation started - * to the current animation frame. The frameRate property is the current frame rate in frames / second. Return false from function, - * if you don't need to redraw layer/layers on some frames. - * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn on each animation frame. Can be a layer, an array of layers, or null. - * Not specifying a node will result in no redraw. - * @example - * // move a node to the right at 50 pixels / second - * var velocity = 50; - * - * var anim = new Konva.Animation(function(frame) { - * var dist = velocity * (frame.timeDiff / 1000); - * node.move(dist, 0); - * }, layer); - * - * anim.start(); - */ - Konva.Animation = function(func, layers) { - var Anim = Konva.Animation; - this.func = func; - this.setLayers(layers); - this.id = Anim.animIdCounter++; - this.frame = { - time: 0, - timeDiff: 0, - lastTime: now() - }; - }; - /* - * Animation methods - */ - Konva.Animation.prototype = { - /** - * set layers to be redrawn on each animation frame - * @method - * @memberof Konva.Animation.prototype - * @param {Konva.Layer|Array} [layers] layer(s) to be redrawn.  Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw. - * @return {Konva.Animation} this - */ - setLayers: function(layers) { - var lays = []; - // if passing in no layers - if (!layers) { - lays = []; - } else if (layers.length > 0) { - // if passing in an array of Layers - // NOTE: layers could be an array or Konva.Collection. for simplicity, I'm just inspecting - // the length property to check for both cases - lays = layers; - } else { - // if passing in a Layer - lays = [layers]; - } - - this.layers = lays; - return this; - }, - /** - * get layers - * @method - * @memberof Konva.Animation.prototype - * @return {Array} Array of Konva.Layer - */ - getLayers: function() { - return this.layers; - }, - /** - * add layer. Returns true if the layer was added, and false if it was not - * @method - * @memberof Konva.Animation.prototype - * @param {Konva.Layer} layer to add - * @return {Bool} true if layer is added to animation, otherwise false - */ - addLayer: function(layer) { - var layers = this.layers, len = layers.length, n; - - // don't add the layer if it already exists - for (n = 0; n < len; n++) { - if (layers[n]._id === layer._id) { - return false; - } - } - - this.layers.push(layer); - return true; - }, - /** - * determine if animation is running or not. returns true or false - * @method - * @memberof Konva.Animation.prototype - * @return {Bool} is animation running? - */ - isRunning: function() { - var a = Konva.Animation, - animations = a.animations, - len = animations.length, - n; - - for (n = 0; n < len; n++) { - if (animations[n].id === this.id) { - return true; - } - } - return false; - }, - /** - * start animation - * @method - * @memberof Konva.Animation.prototype - * @return {Konva.Animation} this - */ - start: function() { - var Anim = Konva.Animation; - this.stop(); - this.frame.timeDiff = 0; - this.frame.lastTime = now(); - Anim._addAnimation(this); - return this; - }, - /** - * stop animation - * @method - * @memberof Konva.Animation.prototype - * @return {Konva.Animation} this - */ - stop: function() { - Konva.Animation._removeAnimation(this); - return this; - }, - _updateFrameObject: function(time) { - this.frame.timeDiff = time - this.frame.lastTime; - this.frame.lastTime = time; - this.frame.time += this.frame.timeDiff; - this.frame.frameRate = 1000 / this.frame.timeDiff; - } - }; - Konva.Animation.animations = []; - Konva.Animation.animIdCounter = 0; - Konva.Animation.animRunning = false; - - Konva.Animation._addAnimation = function(anim) { - this.animations.push(anim); - this._handleAnimation(); - }; - Konva.Animation._removeAnimation = function(anim) { - var id = anim.id, animations = this.animations, len = animations.length, n; - - for (n = 0; n < len; n++) { - if (animations[n].id === id) { - this.animations.splice(n, 1); - break; - } - } - }; - - Konva.Animation._runFrames = function() { - var layerHash = {}, - animations = this.animations, - anim, - layers, - func, - n, - i, - layersLen, - layer, - key, - needRedraw; - /* - * loop through all animations and execute animation - * function. if the animation object has specified node, - * we can add the node to the nodes hash to eliminate - * drawing the same node multiple times. The node property - * can be the stage itself or a layer - */ - /* - * WARNING: don't cache animations.length because it could change while - * the for loop is running, causing a JS error - */ - - for (n = 0; n < animations.length; n++) { - anim = animations[n]; - layers = anim.layers; - func = anim.func; - - anim._updateFrameObject(now()); - layersLen = layers.length; - - // if animation object has a function, execute it - if (func) { - // allow anim bypassing drawing - needRedraw = func.call(anim, anim.frame) !== false; - } else { - needRedraw = true; - } - if (!needRedraw) { - continue; - } - for (i = 0; i < layersLen; i++) { - layer = layers[i]; - - if (layer._id !== undefined) { - layerHash[layer._id] = layer; - } - } - } - - for (key in layerHash) { - if (!layerHash.hasOwnProperty(key)) { - continue; - } - layerHash[key].draw(); - } - }; - Konva.Animation._animationLoop = function() { - var Anim = Konva.Animation; - if (Anim.animations.length) { - Anim._runFrames(); - requestAnimFrame(Anim._animationLoop); - } else { - Anim.animRunning = false; - } - }; - Konva.Animation._handleAnimation = function() { - if (!this.animRunning) { - this.animRunning = true; - requestAnimFrame(this._animationLoop); - } - }; - - /** - * batch draw. this function will not do immediate draw - * but it will schedule drawing to next tick (requestAnimFrame) - * @method - * @return {Konva.Layer} this - * @memberof Konva.Base.prototype - */ - Konva.BaseLayer.prototype.batchDraw = function() { - var that = this, Anim = Konva.Animation; - - if (!this.batchAnim) { - this.batchAnim = new Anim( - function() { - // stop animation after first tick - that.batchAnim.stop(); - }, - this - ); - } - - this.lastBatchDrawTime = now(); - - if (!this.batchAnim.isRunning()) { - this.batchAnim.start(); - } - return this; - }; - - /** - * batch draw - * @method - * @return {Konva.Stage} this - * @memberof Konva.Stage.prototype - */ - Konva.Stage.prototype.batchDraw = function() { - this.getChildren().each(function(layer) { - layer.batchDraw(); - }); - return this; - }; -})(Konva); - -(function() { - 'use strict'; - var blacklist = { - node: 1, - duration: 1, - easing: 1, - onFinish: 1, - yoyo: 1 - }, - PAUSED = 1, - PLAYING = 2, - REVERSING = 3, - idCounter = 0, - colorAttrs = ['fill', 'stroke', 'shadowColor']; - - var Tween = function(prop, propFunc, func, begin, finish, duration, yoyo) { - this.prop = prop; - this.propFunc = propFunc; - this.begin = begin; - this._pos = begin; - this.duration = duration; - this._change = 0; - this.prevPos = 0; - this.yoyo = yoyo; - this._time = 0; - this._position = 0; - this._startTime = 0; - this._finish = 0; - this.func = func; - this._change = finish - this.begin; - this.pause(); - }; - /* - * Tween methods - */ - Tween.prototype = { - fire: function(str) { - var handler = this[str]; - if (handler) { - handler(); - } - }, - setTime: function(t) { - if (t > this.duration) { - if (this.yoyo) { - this._time = this.duration; - this.reverse(); - } else { - this.finish(); - } - } else if (t < 0) { - if (this.yoyo) { - this._time = 0; - this.play(); - } else { - this.reset(); - } - } else { - this._time = t; - this.update(); - } - }, - getTime: function() { - return this._time; - }, - setPosition: function(p) { - this.prevPos = this._pos; - this.propFunc(p); - this._pos = p; - }, - getPosition: function(t) { - if (t === undefined) { - t = this._time; - } - return this.func(t, this.begin, this._change, this.duration); - }, - play: function() { - this.state = PLAYING; - this._startTime = this.getTimer() - this._time; - this.onEnterFrame(); - this.fire('onPlay'); - }, - reverse: function() { - this.state = REVERSING; - this._time = this.duration - this._time; - this._startTime = this.getTimer() - this._time; - this.onEnterFrame(); - this.fire('onReverse'); - }, - seek: function(t) { - this.pause(); - this._time = t; - this.update(); - this.fire('onSeek'); - }, - reset: function() { - this.pause(); - this._time = 0; - this.update(); - this.fire('onReset'); - }, - finish: function() { - this.pause(); - this._time = this.duration; - this.update(); - this.fire('onFinish'); - }, - update: function() { - this.setPosition(this.getPosition(this._time)); - }, - onEnterFrame: function() { - var t = this.getTimer() - this._startTime; - if (this.state === PLAYING) { - this.setTime(t); - } else if (this.state === REVERSING) { - this.setTime(this.duration - t); - } - }, - pause: function() { - this.state = PAUSED; - this.fire('onPause'); - }, - getTimer: function() { - return new Date().getTime(); - } - }; - - /** - * Tween constructor. Tweens enable you to animate a node between the current state and a new state. - * You can play, pause, reverse, seek, reset, and finish tweens. By default, tweens are animated using - * a linear easing. For more tweening options, check out {@link Konva.Easings} - * @constructor - * @memberof Konva - * @example - * // instantiate new tween which fully rotates a node in 1 second - * var tween = new Konva.Tween({ - * node: node, - * rotationDeg: 360, - * duration: 1, - * easing: Konva.Easings.EaseInOut - * }); - * - * // play tween - * tween.play(); - * - * // pause tween - * tween.pause(); - */ - Konva.Tween = function(config) { - var that = this, - node = config.node, - nodeId = node._id, - duration, - easing = config.easing || Konva.Easings.Linear, - yoyo = !!config.yoyo, - key; - - if (typeof config.duration === 'undefined') { - duration = 1; - } else if (config.duration === 0) { - // zero is bad value for duration - duration = 0.001; - } else { - duration = config.duration; - } - this.node = node; - this._id = idCounter++; - - var layers = node.getLayer() || - (node instanceof Konva.Stage ? node.getLayers() : null); - if (!layers) { - Konva.Util.error( - 'Tween constructor have `node` that is not in a layer. Please add node into layer first.' - ); - } - this.anim = new Konva.Animation( - function() { - that.tween.onEnterFrame(); - }, - layers - ); - - this.tween = new Tween( - key, - function(i) { - that._tweenFunc(i); - }, - easing, - 0, - 1, - duration * 1000, - yoyo - ); - - this._addListeners(); - - // init attrs map - if (!Konva.Tween.attrs[nodeId]) { - Konva.Tween.attrs[nodeId] = {}; - } - if (!Konva.Tween.attrs[nodeId][this._id]) { - Konva.Tween.attrs[nodeId][this._id] = {}; - } - // init tweens map - if (!Konva.Tween.tweens[nodeId]) { - Konva.Tween.tweens[nodeId] = {}; - } - - for (key in config) { - if (blacklist[key] === undefined) { - this._addAttr(key, config[key]); - } - } - - this.reset(); - - // callbacks - this.onFinish = config.onFinish; - this.onReset = config.onReset; - }; - - // start/diff object = attrs.nodeId.tweenId.attr - Konva.Tween.attrs = {}; - // tweenId = tweens.nodeId.attr - Konva.Tween.tweens = {}; - - Konva.Tween.prototype = { - _addAttr: function(key, end) { - var node = this.node, - nodeId = node._id, - start, - diff, - tweenId, - n, - len, - trueEnd, - trueStart; - - // remove conflict from tween map if it exists - tweenId = Konva.Tween.tweens[nodeId][key]; - - if (tweenId) { - delete Konva.Tween.attrs[nodeId][tweenId][key]; - } - - // add to tween map - start = node.getAttr(key); - - if (Konva.Util._isArray(end)) { - diff = []; - len = Math.max(end.length, start.length); - - if (key === 'points' && end.length !== start.length) { - // before tweening points we need to make sure that start.length === end.length - // Konva.Util._prepareArrayForTween thinking that end.length > start.length - - if (end.length > start.length) { - // so in this case we will increase number of starting points - trueStart = start; - start = Konva.Util._prepareArrayForTween(start, end, node.closed()); - } else { - // in this case we will increase number of eding points - trueEnd = end; - end = Konva.Util._prepareArrayForTween(end, start, node.closed()); - } - } - - for (n = 0; n < len; n++) { - diff.push(end[n] - start[n]); - } - } else if (colorAttrs.indexOf(key) !== -1) { - start = Konva.Util.colorToRGBA(start); - var endRGBA = Konva.Util.colorToRGBA(end); - diff = { - r: endRGBA.r - start.r, - g: endRGBA.g - start.g, - b: endRGBA.b - start.b, - a: endRGBA.a - start.a - }; - } else { - diff = end - start; - } - - Konva.Tween.attrs[nodeId][this._id][key] = { - start: start, - diff: diff, - end: end, - trueEnd: trueEnd, - trueStart: trueStart - }; - Konva.Tween.tweens[nodeId][key] = this._id; - }, - _tweenFunc: function(i) { - var node = this.node, - attrs = Konva.Tween.attrs[node._id][this._id], - key, - attr, - start, - diff, - newVal, - n, - len, - end; - - for (key in attrs) { - attr = attrs[key]; - start = attr.start; - diff = attr.diff; - end = attr.end; - - if (Konva.Util._isArray(start)) { - newVal = []; - len = Math.max(start.length, end.length); - for (n = 0; n < len; n++) { - newVal.push((start[n] || 0) + diff[n] * i); - } - } else if (colorAttrs.indexOf(key) !== -1) { - newVal = 'rgba(' + - Math.round(start.r + diff.r * i) + - ',' + - Math.round(start.g + diff.g * i) + - ',' + - Math.round(start.b + diff.b * i) + - ',' + - (start.a + diff.a * i) + - ')'; - } else { - newVal = start + diff * i; - } - - node.setAttr(key, newVal); - } - }, - _addListeners: function() { - var that = this; - - // start listeners - this.tween.onPlay = function() { - that.anim.start(); - }; - this.tween.onReverse = function() { - that.anim.start(); - }; - - // stop listeners - this.tween.onPause = function() { - that.anim.stop(); - }; - this.tween.onFinish = function() { - var node = that.node; - - // after tweening points of line we need to set original end - var attrs = Konva.Tween.attrs[node._id][that._id]; - if (attrs.points && attrs.points.trueEnd) { - node.points(attrs.points.trueEnd); - } - - if (that.onFinish) { - that.onFinish.call(that); - } - }; - this.tween.onReset = function() { - var node = that.node; - // after tweening points of line we need to set original start - var attrs = Konva.Tween.attrs[node._id][that._id]; - if (attrs.points && attrs.points.trueStart) { - node.points(attrs.points.trueStart); - } - - if (that.onReset) { - that.onReset(); - } - }; - }, - /** - * play - * @method - * @memberof Konva.Tween.prototype - * @returns {Tween} - */ - play: function() { - this.tween.play(); - return this; - }, - /** - * reverse - * @method - * @memberof Konva.Tween.prototype - * @returns {Tween} - */ - reverse: function() { - this.tween.reverse(); - return this; - }, - /** - * reset - * @method - * @memberof Konva.Tween.prototype - * @returns {Tween} - */ - reset: function() { - this.tween.reset(); - return this; - }, - /** - * seek - * @method - * @memberof Konva.Tween.prototype - * @param {Integer} t time in seconds between 0 and the duration - * @returns {Tween} - */ - seek: function(t) { - this.tween.seek(t * 1000); - return this; - }, - /** - * pause - * @method - * @memberof Konva.Tween.prototype - * @returns {Tween} - */ - pause: function() { - this.tween.pause(); - return this; - }, - /** - * finish - * @method - * @memberof Konva.Tween.prototype - * @returns {Tween} - */ - finish: function() { - this.tween.finish(); - return this; - }, - /** - * destroy - * @method - * @memberof Konva.Tween.prototype - */ - destroy: function() { - var nodeId = this.node._id, - thisId = this._id, - attrs = Konva.Tween.tweens[nodeId], - key; - - this.pause(); - - for (key in attrs) { - delete Konva.Tween.tweens[nodeId][key]; - } - - delete Konva.Tween.attrs[nodeId][thisId]; - } - }; - - /** - * Tween node properties. Shorter usage of {@link Konva.Tween} object. - * - * @method Konva.Node#to - * @memberof Konva.Node - * @param {Object} [params] tween params - * @example - * - * circle.to({ - * x : 50, - * duration : 0.5 - * }); - */ - Konva.Node.prototype.to = function(params) { - var onFinish = params.onFinish; - params.node = this; - params.onFinish = function() { - this.destroy(); - if (onFinish) { - onFinish(); - } - }; - var tween = new Konva.Tween(params); - tween.play(); - }; - - /* - * These eases were ported from an Adobe Flash tweening library to JavaScript - * by Xaric - */ - - /** - * @namespace Easings - * @memberof Konva - */ - Konva.Easings = { - /** - * back ease in - * @function - * @memberof Konva.Easings - */ - BackEaseIn: function(t, b, c, d) { - var s = 1.70158; - return c * (t /= d) * t * ((s + 1) * t - s) + b; - }, - /** - * back ease out - * @function - * @memberof Konva.Easings - */ - BackEaseOut: function(t, b, c, d) { - var s = 1.70158; - return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; - }, - /** - * back ease in out - * @function - * @memberof Konva.Easings - */ - BackEaseInOut: function(t, b, c, d) { - var s = 1.70158; - if ((t /= d / 2) < 1) { - return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b; - } - return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; - }, - /** - * elastic ease in - * @function - * @memberof Konva.Easings - */ - ElasticEaseIn: function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if (t === 0) { - return b; - } - if ((t /= d) === 1) { - return b + c; - } - if (!p) { - p = d * 0.3; - } - if (!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - return -(a * - Math.pow(2, 10 * (t -= 1)) * - Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; - }, - /** - * elastic ease out - * @function - * @memberof Konva.Easings - */ - ElasticEaseOut: function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if (t === 0) { - return b; - } - if ((t /= d) === 1) { - return b + c; - } - if (!p) { - p = d * 0.3; - } - if (!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - return a * - Math.pow(2, (-10) * t) * - Math.sin((t * d - s) * (2 * Math.PI) / p) + - c + - b; - }, - /** - * elastic ease in out - * @function - * @memberof Konva.Easings - */ - ElasticEaseInOut: function(t, b, c, d, a, p) { - // added s = 0 - var s = 0; - if (t === 0) { - return b; - } - if ((t /= d / 2) === 2) { - return b + c; - } - if (!p) { - p = d * (0.3 * 1.5); - } - if (!a || a < Math.abs(c)) { - a = c; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - if (t < 1) { - return (-0.5) * - (a * - Math.pow(2, 10 * (t -= 1)) * - Math.sin((t * d - s) * (2 * Math.PI) / p)) + - b; - } - return a * - Math.pow(2, (-10) * (t -= 1)) * - Math.sin((t * d - s) * (2 * Math.PI) / p) * - 0.5 + - c + - b; - }, - /** - * bounce ease out - * @function - * @memberof Konva.Easings - */ - BounceEaseOut: function(t, b, c, d) { - if ((t /= d) < 1 / 2.75) { - return c * (7.5625 * t * t) + b; - } else if (t < 2 / 2.75) { - return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b; - } else if (t < 2.5 / 2.75) { - return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b; - } else { - return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b; - } - }, - /** - * bounce ease in - * @function - * @memberof Konva.Easings - */ - BounceEaseIn: function(t, b, c, d) { - return c - Konva.Easings.BounceEaseOut(d - t, 0, c, d) + b; - }, - /** - * bounce ease in out - * @function - * @memberof Konva.Easings - */ - BounceEaseInOut: function(t, b, c, d) { - if (t < d / 2) { - return Konva.Easings.BounceEaseIn(t * 2, 0, c, d) * 0.5 + b; - } else { - return Konva.Easings.BounceEaseOut(t * 2 - d, 0, c, d) * 0.5 + - c * 0.5 + - b; - } - }, - /** - * ease in - * @function - * @memberof Konva.Easings - */ - EaseIn: function(t, b, c, d) { - return c * (t /= d) * t + b; - }, - /** - * ease out - * @function - * @memberof Konva.Easings - */ - EaseOut: function(t, b, c, d) { - return (-c) * (t /= d) * (t - 2) + b; - }, - /** - * ease in out - * @function - * @memberof Konva.Easings - */ - EaseInOut: function(t, b, c, d) { - if ((t /= d / 2) < 1) { - return c / 2 * t * t + b; - } - return (-c) / 2 * (--t * (t - 2) - 1) + b; - }, - /** - * strong ease in - * @function - * @memberof Konva.Easings - */ - StrongEaseIn: function(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - }, - /** - * strong ease out - * @function - * @memberof Konva.Easings - */ - StrongEaseOut: function(t, b, c, d) { - return c * ((t = t / d - 1) * t * t * t * t + 1) + b; - }, - /** - * strong ease in out - * @function - * @memberof Konva.Easings - */ - StrongEaseInOut: function(t, b, c, d) { - if ((t /= d / 2) < 1) { - return c / 2 * t * t * t * t * t + b; - } - return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; - }, - /** - * linear - * @function - * @memberof Konva.Easings - */ - Linear: function(t, b, c, d) { - return c * t / d + b; - } - }; -})(); - -(function() { - 'use strict'; - Konva.DD = { - // properties - anim: new Konva.Animation(function() { - var b = this.dirty; - this.dirty = false; - return b; - }), - isDragging: false, - justDragged: false, - offset: { - x: 0, - y: 0 - }, - node: null, - - // methods - _drag: function(evt) { - var dd = Konva.DD, node = dd.node; - - if (node) { - if (!dd.isDragging) { - var pos = node.getStage().getPointerPosition(); - var dragDistance = node.dragDistance(); - var distance = Math.max( - Math.abs(pos.x - dd.startPointerPos.x), - Math.abs(pos.y - dd.startPointerPos.y) - ); - if (distance < dragDistance) { - return; - } - } - - node.getStage()._setPointerPosition(evt); - node._setDragPosition(evt); - if (!dd.isDragging) { - dd.isDragging = true; - node.fire( - 'dragstart', - { - type: 'dragstart', - target: node, - evt: evt - }, - true - ); - } - - // execute ondragmove if defined - node.fire( - 'dragmove', - { - type: 'dragmove', - target: node, - evt: evt - }, - true - ); - } - }, - _endDragBefore: function(evt) { - var dd = Konva.DD, node = dd.node, layer; - - if (node) { - layer = node.getLayer(); - dd.anim.stop(); - - // only fire dragend event if the drag and drop - // operation actually started. - if (dd.isDragging) { - dd.isDragging = false; - dd.justDragged = true; - Konva.listenClickTap = false; - - if (evt) { - evt.dragEndNode = node; - } - } - - delete dd.node; - - if (node.getLayer() || layer || node instanceof Konva.Stage) { - (layer || node).draw(); - } - } - }, - _endDragAfter: function(evt) { - evt = evt || {}; - var dragEndNode = evt.dragEndNode; - - if (evt && dragEndNode) { - dragEndNode.fire( - 'dragend', - { - type: 'dragend', - target: dragEndNode, - evt: evt - }, - true - ); - } - } - }; - - // Node extenders - - /** - * initiate drag and drop - * @method - * @memberof Konva.Node.prototype - */ - Konva.Node.prototype.startDrag = function() { - var dd = Konva.DD, - stage = this.getStage(), - layer = this.getLayer(), - pos = stage.getPointerPosition(), - ap = this.getAbsolutePosition(); - - if (pos) { - if (dd.node) { - dd.node.stopDrag(); - } - - dd.node = this; - dd.startPointerPos = pos; - dd.offset.x = pos.x - ap.x; - dd.offset.y = pos.y - ap.y; - dd.anim.setLayers(layer || this.getLayers()); - dd.anim.start(); - - this._setDragPosition(); - } - }; - - Konva.Node.prototype._setDragPosition = function(evt) { - var dd = Konva.DD, - pos = this.getStage().getPointerPosition(), - dbf = this.getDragBoundFunc(); - if (!pos) { - return; - } - var newNodePos = { - x: pos.x - dd.offset.x, - y: pos.y - dd.offset.y - }; - - if (dbf !== undefined) { - newNodePos = dbf.call(this, newNodePos, evt); - } - this.setAbsolutePosition(newNodePos); - - if ( - !this._lastPos || - this._lastPos.x !== newNodePos.x || - this._lastPos.y !== newNodePos.y - ) { - dd.anim.dirty = true; - } - - this._lastPos = newNodePos; - }; - - /** - * stop drag and drop - * @method - * @memberof Konva.Node.prototype - */ - Konva.Node.prototype.stopDrag = function() { - var dd = Konva.DD, evt = {}; - dd._endDragBefore(evt); - dd._endDragAfter(evt); - }; - - Konva.Node.prototype.setDraggable = function(draggable) { - this._setAttr('draggable', draggable); - this._dragChange(); - }; - - var origRemove = Konva.Node.prototype.remove; - - Konva.Node.prototype.__originalRemove = origRemove; - Konva.Node.prototype.remove = function() { - var dd = Konva.DD; - - // stop DD - if (dd.node && dd.node._id === this._id) { - this.stopDrag(); - } - - origRemove.call(this); - }; - - /** - * determine if node is currently in drag and drop mode - * @method - * @memberof Konva.Node.prototype - */ - Konva.Node.prototype.isDragging = function() { - var dd = Konva.DD; - return !!(dd.node && dd.node._id === this._id && dd.isDragging); - }; - - Konva.Node.prototype._listenDrag = function() { - var that = this; - - this._dragCleanup(); - - if (this.getClassName() === 'Stage') { - this.on('contentMousedown.konva contentTouchstart.konva', function(evt) { - if (!Konva.DD.node) { - that.startDrag(evt); - } - }); - } else { - this.on('mousedown.konva touchstart.konva', function(evt) { - // ignore right and middle buttons - if (evt.evt.button === 1 || evt.evt.button === 2) { - return; - } - if (!Konva.DD.node) { - that.startDrag(evt); - } - }); - } - - // listening is required for drag and drop - /* - this._listeningEnabled = true; - this._clearSelfAndAncestorCache('listeningEnabled'); - */ - }; - - Konva.Node.prototype._dragChange = function() { - if (this.attrs.draggable) { - this._listenDrag(); - } else { - // remove event listeners - this._dragCleanup(); - - /* - * force drag and drop to end - * if this node is currently in - * drag and drop mode - */ - var stage = this.getStage(); - var dd = Konva.DD; - if (stage && dd.node && dd.node._id === this._id) { - dd.node.stopDrag(); - } - } - }; - - Konva.Node.prototype._dragCleanup = function() { - if (this.getClassName() === 'Stage') { - this.off('contentMousedown.konva'); - this.off('contentTouchstart.konva'); - } else { - this.off('mousedown.konva'); - this.off('touchstart.konva'); - } - }; - - Konva.Factory.addGetterSetter(Konva.Node, 'dragBoundFunc'); - - /** - * get/set drag bound function. This is used to override the default - * drag and drop position - * @name dragBoundFunc - * @method - * @memberof Konva.Node.prototype - * @param {Function} dragBoundFunc - * @returns {Function} - * @example - * // get drag bound function - * var dragBoundFunc = node.dragBoundFunc(); - * - * // create vertical drag and drop - * node.dragBoundFunc(function(pos){ - * return { - * x: this.getAbsolutePosition().x, - * y: pos.y - * }; - * }); - */ - - Konva.Factory.addGetter(Konva.Node, 'draggable', false); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'draggable'); - - /** - * get/set draggable flag - * @name draggable - * @method - * @memberof Konva.Node.prototype - * @param {Boolean} draggable - * @returns {Boolean} - * @example - * // get draggable flag - * var draggable = node.draggable(); - * - * // enable drag and drop - * node.draggable(true); - * - * // disable drag and drop - * node.draggable(false); - */ - - var html = Konva.document.documentElement; - html.addEventListener('mouseup', Konva.DD._endDragBefore, true); - html.addEventListener('touchend', Konva.DD._endDragBefore, true); - - html.addEventListener('mousemove', Konva.DD._drag); - html.addEventListener('touchmove', Konva.DD._drag); - - html.addEventListener('mouseup', Konva.DD._endDragAfter, false); - html.addEventListener('touchend', Konva.DD._endDragAfter, false); -})(); - -(function() { - 'use strict'; - /** - * Rect constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} [config.cornerRadius] - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var rect = new Konva.Rect({ - * width: 100, - * height: 50, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 5 - * }); - */ - Konva.Rect = function(config) { - this.___init(config); - }; - - Konva.Rect.prototype = { - ___init: function(config) { - Konva.Shape.call(this, config); - this.className = 'Rect'; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - var cornerRadius = this.getCornerRadius(), - width = this.getWidth(), - height = this.getHeight(); - - context.beginPath(); - - if (!cornerRadius) { - // simple rect - don't bother doing all that complicated maths stuff. - context.rect(0, 0, width, height); - } else { - // arcTo would be nicer, but browser support is patchy (Opera) - cornerRadius = Math.min(cornerRadius, width / 2, height / 2); - context.moveTo(cornerRadius, 0); - context.lineTo(width - cornerRadius, 0); - context.arc( - width - cornerRadius, - cornerRadius, - cornerRadius, - Math.PI * 3 / 2, - 0, - false - ); - context.lineTo(width, height - cornerRadius); - context.arc( - width - cornerRadius, - height - cornerRadius, - cornerRadius, - 0, - Math.PI / 2, - false - ); - context.lineTo(cornerRadius, height); - context.arc( - cornerRadius, - height - cornerRadius, - cornerRadius, - Math.PI / 2, - Math.PI, - false - ); - context.lineTo(0, cornerRadius); - context.arc( - cornerRadius, - cornerRadius, - cornerRadius, - Math.PI, - Math.PI * 3 / 2, - false - ); - } - context.closePath(); - context.fillStrokeShape(this); - } - }; - - Konva.Util.extend(Konva.Rect, Konva.Shape); - - Konva.Factory.addGetterSetter(Konva.Rect, 'cornerRadius', 0); - /** - * get/set corner radius - * @name cornerRadius - * @method - * @memberof Konva.Rect.prototype - * @param {Number} cornerRadius - * @returns {Number} - * @example - * // get corner radius - * var cornerRadius = rect.cornerRadius(); - * - * // set corner radius - * rect.cornerRadius(10); - */ - - Konva.Collection.mapMethods(Konva.Rect); -})(); - -(function() { - 'use strict'; - // the 0.0001 offset fixes a bug in Chrome 27 - var PIx2 = Math.PI * 2 - 0.0001, CIRCLE = 'Circle'; - - /** - * Circle constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.radius - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * // create circle - * var circle = new Konva.Circle({ - * radius: 40, - * fill: 'red', - * stroke: 'black' - * strokeWidth: 5 - * }); - */ - Konva.Circle = function(config) { - this.___init(config); - }; - - Konva.Circle.prototype = { - _centroid: true, - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = CIRCLE; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - context.beginPath(); - context.arc(0, 0, this.getRadius(), 0, PIx2, false); - context.closePath(); - context.fillStrokeShape(this); - }, - // implements Shape.prototype.getWidth() - getWidth: function() { - return this.getRadius() * 2; - }, - // implements Shape.prototype.getHeight() - getHeight: function() { - return this.getRadius() * 2; - }, - // implements Shape.prototype.setWidth() - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - if (this.radius() !== width / 2) { - this.setRadius(width / 2); - } - }, - // implements Shape.prototype.setHeight() - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - if (this.radius() !== height / 2) { - this.setRadius(height / 2); - } - } - }; - Konva.Util.extend(Konva.Circle, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Circle, 'radius', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Circle, 'radius'); - - /** - * get/set radius - * @name radius - * @method - * @memberof Konva.Circle.prototype - * @param {Number} radius - * @returns {Number} - * @example - * // get radius - * var radius = circle.radius(); - * - * // set radius - * circle.radius(10); - */ - - Konva.Collection.mapMethods(Konva.Circle); -})(); - -(function() { - 'use strict'; - // the 0.0001 offset fixes a bug in Chrome 27 - var PIx2 = Math.PI * 2 - 0.0001, ELLIPSE = 'Ellipse'; - - /** - * Ellipse constructor - * @constructor - * @augments Konva.Shape - * @param {Object} config - * @param {Object} config.radius defines x and y radius - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var ellipse = new Konva.Ellipse({ - * radius : { - * x : 50, - * y : 50 - * }, - * fill: 'red' - * }); - */ - Konva.Ellipse = function(config) { - this.___init(config); - }; - - Konva.Ellipse.prototype = { - _centroid: true, - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = ELLIPSE; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - var rx = this.getRadiusX(), ry = this.getRadiusY(); - - context.beginPath(); - context.save(); - if (rx !== ry) { - context.scale(1, ry / rx); - } - context.arc(0, 0, rx, 0, PIx2, false); - context.restore(); - context.closePath(); - context.fillStrokeShape(this); - }, - // implements Shape.prototype.getWidth() - getWidth: function() { - return this.getRadiusX() * 2; - }, - // implements Shape.prototype.getHeight() - getHeight: function() { - return this.getRadiusY() * 2; - }, - // implements Shape.prototype.setWidth() - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - this.setRadius({ - x: width / 2 - }); - }, - // implements Shape.prototype.setHeight() - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - this.setRadius({ - y: height / 2 - }); - } - }; - Konva.Util.extend(Konva.Ellipse, Konva.Shape); - - // add getters setters - Konva.Factory.addComponentsGetterSetter(Konva.Ellipse, 'radius', ['x', 'y']); - - /** - * get/set radius - * @name radius - * @method - * @memberof Konva.Ellipse.prototype - * @param {Object} radius - * @param {Number} radius.x - * @param {Number} radius.y - * @returns {Object} - * @example - * // get radius - * var radius = ellipse.radius(); - * - * // set radius - * ellipse.radius({ - * x: 200, - * y: 100 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Ellipse, 'radiusX', 0); - /** - * get/set radius x - * @name radiusX - * @method - * @memberof Konva.Ellipse.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get radius x - * var radiusX = ellipse.radiusX(); - * - * // set radius x - * ellipse.radiusX(200); - */ - - Konva.Factory.addGetterSetter(Konva.Ellipse, 'radiusY', 0); - /** - * get/set radius y - * @name radiusY - * @method - * @memberof Konva.Ellipse.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get radius y - * var radiusY = ellipse.radiusY(); - * - * // set radius y - * ellipse.radiusY(200); - */ - - Konva.Collection.mapMethods(Konva.Ellipse); -})(); - -(function() { - 'use strict'; - // the 0.0001 offset fixes a bug in Chrome 27 - var PIx2 = Math.PI * 2 - 0.0001; - /** - * Ring constructor - * @constructor - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.innerRadius - * @param {Number} config.outerRadius - * @param {Boolean} [config.clockwise] - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var ring = new Konva.Ring({ - * innerRadius: 40, - * outerRadius: 80, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 5 - * }); - */ - Konva.Ring = function(config) { - this.___init(config); - }; - - Konva.Ring.prototype = { - _centroid: true, - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = 'Ring'; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - context.beginPath(); - context.arc(0, 0, this.getInnerRadius(), 0, PIx2, false); - context.moveTo(this.getOuterRadius(), 0); - context.arc(0, 0, this.getOuterRadius(), PIx2, 0, true); - context.closePath(); - context.fillStrokeShape(this); - }, - // implements Shape.prototype.getWidth() - getWidth: function() { - return this.getOuterRadius() * 2; - }, - // implements Shape.prototype.getHeight() - getHeight: function() { - return this.getOuterRadius() * 2; - }, - // implements Shape.prototype.setWidth() - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - if (this.outerRadius() !== width / 2) { - this.setOuterRadius(width / 2); - } - }, - // implements Shape.prototype.setHeight() - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - if (this.outerRadius() !== height / 2) { - this.setOuterRadius(height / 2); - } - }, - setOuterRadius: function(val) { - this._setAttr('outerRadius', val); - this.setWidth(val * 2); - this.setHeight(val * 2); - } - }; - Konva.Util.extend(Konva.Ring, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Ring, 'innerRadius', 0); - - /** - * get/set innerRadius - * @name innerRadius - * @method - * @memberof Konva.Ring.prototype - * @param {Number} innerRadius - * @returns {Number} - * @example - * // get inner radius - * var innerRadius = ring.innerRadius(); - * - * // set inner radius - * ring.innerRadius(20); - */ - Konva.Factory.addGetter(Konva.Ring, 'outerRadius', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Ring, 'outerRadius'); - - /** - * get/set outerRadius - * @name outerRadius - * @method - * @memberof Konva.Ring.prototype - * @param {Number} outerRadius - * @returns {Number} - * @example - * // get outer radius - * var outerRadius = ring.outerRadius(); - * - * // set outer radius - * ring.outerRadius(20); - */ - - Konva.Collection.mapMethods(Konva.Ring); -})(); - -(function() { - 'use strict'; - /** - * Wedge constructor - * @constructor - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.angle in degrees - * @param {Number} config.radius - * @param {Boolean} [config.clockwise] - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * // draw a wedge that's pointing downwards - * var wedge = new Konva.Wedge({ - * radius: 40, - * fill: 'red', - * stroke: 'black' - * strokeWidth: 5, - * angleDeg: 60, - * rotationDeg: -120 - * }); - */ - Konva.Wedge = function(config) { - this.___init(config); - }; - - Konva.Wedge.prototype = { - _centroid: true, - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = 'Wedge'; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - context.beginPath(); - context.arc( - 0, - 0, - this.getRadius(), - 0, - Konva.getAngle(this.getAngle()), - this.getClockwise() - ); - context.lineTo(0, 0); - context.closePath(); - context.fillStrokeShape(this); - }, - // implements Shape.prototype.getWidth() - getWidth: function() { - return this.getRadius() * 2; - }, - // implements Shape.prototype.getHeight() - getHeight: function() { - return this.getRadius() * 2; - }, - // implements Shape.prototype.setWidth() - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - if (this.radius() !== width / 2) { - this.setRadius(width / 2); - } - }, - // implements Shape.prototype.setHeight() - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - if (this.radius() !== height / 2) { - this.setRadius(height / 2); - } - } - }; - Konva.Util.extend(Konva.Wedge, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Wedge, 'radius', 0); - - /** - * get/set radius - * @name radius - * @method - * @memberof Konva.Wedge.prototype - * @param {Number} radius - * @returns {Number} - * @example - * // get radius - * var radius = wedge.radius(); - * - * // set radius - * wedge.radius(10); - */ - - Konva.Factory.addGetterSetter(Konva.Wedge, 'angle', 0); - - /** - * get/set angle in degrees - * @name angle - * @method - * @memberof Konva.Wedge.prototype - * @param {Number} angle - * @returns {Number} - * @example - * // get angle - * var angle = wedge.angle(); - * - * // set angle - * wedge.angle(20); - */ - - Konva.Factory.addGetterSetter(Konva.Wedge, 'clockwise', false); - - /** - * get/set clockwise flag - * @name clockwise - * @method - * @memberof Konva.Wedge.prototype - * @param {Number} clockwise - * @returns {Number} - * @example - * // get clockwise flag - * var clockwise = wedge.clockwise(); - * - * // draw wedge counter-clockwise - * wedge.clockwise(false); - * - * // draw wedge clockwise - * wedge.clockwise(true); - */ - - Konva.Factory.backCompat(Konva.Wedge, { - angleDeg: 'angle', - getAngleDeg: 'getAngle', - setAngleDeg: 'setAngle' - }); - - Konva.Collection.mapMethods(Konva.Wedge); -})(); - -(function() { - 'use strict'; - /** - * Arc constructor - * @constructor - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.angle in degrees - * @param {Number} config.innerRadius - * @param {Number} config.outerRadius - * @param {Boolean} [config.clockwise] - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * // draw a Arc that's pointing downwards - * var arc = new Konva.Arc({ - * innerRadius: 40, - * outerRadius: 80, - * fill: 'red', - * stroke: 'black' - * strokeWidth: 5, - * angle: 60, - * rotationDeg: -120 - * }); - */ - Konva.Arc = function(config) { - this.___init(config); - }; - - Konva.Arc.prototype = { - _centroid: true, - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = 'Arc'; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - var angle = Konva.getAngle(this.angle()), clockwise = this.clockwise(); - - context.beginPath(); - context.arc(0, 0, this.getOuterRadius(), 0, angle, clockwise); - context.arc(0, 0, this.getInnerRadius(), angle, 0, !clockwise); - context.closePath(); - context.fillStrokeShape(this); - }, - // implements Shape.prototype.getWidth() - getWidth: function() { - return this.getOuterRadius() * 2; - }, - // implements Shape.prototype.getHeight() - getHeight: function() { - return this.getOuterRadius() * 2; - }, - // implements Shape.prototype.setWidth() - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - if (this.getOuterRadius() !== width / 2) { - this.setOuterRadius(width / 2); - } - }, - // implements Shape.prototype.setHeight() - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - if (this.getOuterRadius() !== height / 2) { - this.setOuterRadius(height / 2); - } - } - }; - Konva.Util.extend(Konva.Arc, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Arc, 'innerRadius', 0); - - /** - * get/set innerRadius - * @name innerRadius - * @method - * @memberof Konva.Arc.prototype - * @param {Number} innerRadius - * @returns {Number} - * @example - * // get inner radius - * var innerRadius = arc.innerRadius(); - * - * // set inner radius - * arc.innerRadius(20); - */ - - Konva.Factory.addGetterSetter(Konva.Arc, 'outerRadius', 0); - - /** - * get/set outerRadius - * @name outerRadius - * @method - * @memberof Konva.Arc.prototype - * @param {Number} outerRadius - * @returns {Number} - * @example - * // get outer radius - * var outerRadius = arc.outerRadius(); - * - * // set outer radius - * arc.outerRadius(20); - */ - - Konva.Factory.addGetterSetter(Konva.Arc, 'angle', 0); - - /** - * get/set angle in degrees - * @name angle - * @method - * @memberof Konva.Arc.prototype - * @param {Number} angle - * @returns {Number} - * @example - * // get angle - * var angle = arc.angle(); - * - * // set angle - * arc.angle(20); - */ - - Konva.Factory.addGetterSetter(Konva.Arc, 'clockwise', false); - - /** - * get/set clockwise flag - * @name clockwise - * @method - * @memberof Konva.Arc.prototype - * @param {Boolean} clockwise - * @returns {Boolean} - * @example - * // get clockwise flag - * var clockwise = arc.clockwise(); - * - * // draw arc counter-clockwise - * arc.clockwise(false); - * - * // draw arc clockwise - * arc.clockwise(true); - */ - - Konva.Collection.mapMethods(Konva.Arc); -})(); - -(function() { - 'use strict'; - // CONSTANTS - var IMAGE = 'Image'; - - /** - * Image constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Image} config.image - * @param {Object} [config.crop] - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var imageObj = new Image(); - * imageObj.onload = function() { - * var image = new Konva.Image({ - * x: 200, - * y: 50, - * image: imageObj, - * width: 100, - * height: 100 - * }); - * }; - * imageObj.src = '/path/to/image.jpg' - */ - Konva.Image = function(config) { - this.___init(config); - }; - - Konva.Image.prototype = { - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = IMAGE; - this.sceneFunc(this._sceneFunc); - this.hitFunc(this._hitFunc); - }, - _useBufferCanvas: function() { - return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && - this.hasStroke() && - this.getStage(); - }, - _sceneFunc: function(context) { - var width = this.getWidth(), - height = this.getHeight(), - image = this.getImage(), - cropWidth, - cropHeight, - params; - - if (image) { - cropWidth = this.getCropWidth(); - cropHeight = this.getCropHeight(); - if (cropWidth && cropHeight) { - params = [ - image, - this.getCropX(), - this.getCropY(), - cropWidth, - cropHeight, - 0, - 0, - width, - height - ]; - } else { - params = [image, 0, 0, width, height]; - } - } - - if (this.hasFill() || this.hasStroke()) { - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); - } - - if (image) { - context.drawImage.apply(context, params); - } - }, - _hitFunc: function(context) { - var width = this.getWidth(), height = this.getHeight(); - - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); - }, - getWidth: function() { - var image = this.getImage(); - return this.attrs.width || (image ? image.width : 0); - }, - getHeight: function() { - var image = this.getImage(); - return this.attrs.height || (image ? image.height : 0); - } - }; - Konva.Util.extend(Konva.Image, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Image, 'image'); - - /** - * set image - * @name setImage - * @method - * @memberof Konva.Image.prototype - * @param {Image} image - */ - - /** - * get image - * @name getImage - * @method - * @memberof Konva.Image.prototype - * @returns {Image} - */ - - Konva.Factory.addComponentsGetterSetter(Konva.Image, 'crop', [ - 'x', - 'y', - 'width', - 'height' - ]); - /** - * get/set crop - * @method - * @name crop - * @memberof Konva.Image.prototype - * @param {Object} crop - * @param {Number} crop.x - * @param {Number} crop.y - * @param {Number} crop.width - * @param {Number} crop.height - * @returns {Object} - * @example - * // get crop - * var crop = image.crop(); - * - * // set crop - * image.crop({ - * x: 20, - * y: 20, - * width: 20, - * height: 20 - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Image, 'cropX', 0); - /** - * get/set crop x - * @method - * @name cropX - * @memberof Konva.Image.prototype - * @param {Number} x - * @returns {Number} - * @example - * // get crop x - * var cropX = image.cropX(); - * - * // set crop x - * image.cropX(20); - */ - - Konva.Factory.addGetterSetter(Konva.Image, 'cropY', 0); - /** - * get/set crop y - * @name cropY - * @method - * @memberof Konva.Image.prototype - * @param {Number} y - * @returns {Number} - * @example - * // get crop y - * var cropY = image.cropY(); - * - * // set crop y - * image.cropY(20); - */ - - Konva.Factory.addGetterSetter(Konva.Image, 'cropWidth', 0); - /** - * get/set crop width - * @name cropWidth - * @method - * @memberof Konva.Image.prototype - * @param {Number} width - * @returns {Number} - * @example - * // get crop width - * var cropWidth = image.cropWidth(); - * - * // set crop width - * image.cropWidth(20); - */ - - Konva.Factory.addGetterSetter(Konva.Image, 'cropHeight', 0); - /** - * get/set crop height - * @name cropHeight - * @method - * @memberof Konva.Image.prototype - * @param {Number} height - * @returns {Number} - * @example - * // get crop height - * var cropHeight = image.cropHeight(); - * - * // set crop height - * image.cropHeight(20); - */ - - Konva.Collection.mapMethods(Konva.Image); - - /** - * load image from given url and create `Konva.Image` instance - * @method - * @memberof Konva.Image - * @param {String} url image source - * @param {Function} callback with Konva.Image instance as first argument - * @example - * Konva.Image.fromURL(imageURL, function(image){ - * // image is Konva.Image instance - * layer.add(image); - * layer.draw(); - * }); - */ - Konva.Image.fromURL = function(url, callback) { - var img = new Image(); - img.onload = function() { - var image = new Konva.Image({ - image: img - }); - callback(image); - }; - img.src = url; - }; -})(); - -/*eslint-disable max-depth */ -(function() { - 'use strict'; - // var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; - // constants - var AUTO = 'auto', - //CANVAS = 'canvas', - CENTER = 'center', - JUSTIFY = 'justify', - CHANGE_KONVA = 'Change.konva', - CONTEXT_2D = '2d', - DASH = '-', - EMPTY_STRING = '', - LEFT = 'left', - TEXT = 'text', - TEXT_UPPER = 'Text', - MIDDLE = 'middle', - NORMAL = 'normal', - PX_SPACE = 'px ', - SPACE = ' ', - RIGHT = 'right', - WORD = 'word', - CHAR = 'char', - NONE = 'none', - ATTR_CHANGE_LIST = [ - 'fontFamily', - 'fontSize', - 'fontStyle', - 'fontVariant', - 'padding', - 'align', - 'lineHeight', - 'text', - 'width', - 'height', - 'wrap', - 'letterSpacing' - ], - // cached variables - attrChangeListLen = ATTR_CHANGE_LIST.length, - dummyContext = Konva.Util.createCanvasElement().getContext(CONTEXT_2D); - - /** - * Text constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} [config.fontFamily] default is Arial - * @param {Number} [config.fontSize] in pixels. Default is 12 - * @param {String} [config.fontStyle] can be normal, bold, or italic. Default is normal - * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal - * @param {String} config.text - * @param {String} [config.align] can be left, center, or right - * @param {Number} [config.padding] - * @param {Number} [config.lineHeight] default is 1 - * @param {String} [config.wrap] can be word, char, or none. Default is word - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var text = new Konva.Text({ - * x: 10, - * y: 15, - * text: 'Simple Text', - * fontSize: 30, - * fontFamily: 'Calibri', - * fill: 'green' - * }); - */ - Konva.Text = function(config) { - this.___init(config); - }; - function _fillFunc(context) { - context.fillText(this.partialText, 0, 0); - } - function _strokeFunc(context) { - context.strokeText(this.partialText, 0, 0); - } - - Konva.Text.prototype = { - ___init: function(config) { - config = config || {}; - - // set default color to black - if ( - !config.fillLinearGradientColorStops && - !config.fillRadialGradientColorStops - ) { - config.fill = config.fill || 'black'; - } - // - // if (config.width === undefined) { - // config.width = AUTO; - // } - // if (config.height === undefined) { - // config.height = AUTO; - // } - - // call super constructor - Konva.Shape.call(this, config); - - this._fillFunc = _fillFunc; - this._strokeFunc = _strokeFunc; - this.className = TEXT_UPPER; - - // update text data for certain attr changes - for (var n = 0; n < attrChangeListLen; n++) { - this.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, this._setTextData); - } - - this._setTextData(); - this.sceneFunc(this._sceneFunc); - this.hitFunc(this._hitFunc); - }, - _sceneFunc: function(context) { - var p = this.getPadding(), - textHeight = this.getTextHeight(), - lineHeightPx = this.getLineHeight() * textHeight, - textArr = this.textArr, - textArrLen = textArr.length, - align = this.getAlign(), - totalWidth = this.getWidth(), - letterSpacing = this.getLetterSpacing(), - textDecoration = this.textDecoration(), - fill = this.fill(), - fontSize = this.fontSize(), - n; - - context.setAttr('font', this._getContextFont()); - - context.setAttr('textBaseline', MIDDLE); - context.setAttr('textAlign', LEFT); - context.save(); - if (p) { - context.translate(p, 0); - context.translate(0, p + textHeight / 2); - } else { - context.translate(0, textHeight / 2); - } - - // draw text lines - for (n = 0; n < textArrLen; n++) { - var obj = textArr[n], text = obj.text, width = obj.width; - - // horizontal alignment - context.save(); - if (align === RIGHT) { - context.translate(totalWidth - width - p * 2, 0); - } else if (align === CENTER) { - context.translate((totalWidth - width - p * 2) / 2, 0); - } - - if (textDecoration.indexOf('underline') !== -1) { - context.save(); - context.beginPath(); - context.moveTo(0, Math.round(lineHeightPx / 2)); - context.lineTo(Math.round(width), Math.round(lineHeightPx / 2)); - // TODO: I have no idea what is real ratio - // just /20 looks good enough - context.lineWidth = fontSize / 15; - context.strokeStyle = fill; - context.stroke(); - context.restore(); - } - if (textDecoration.indexOf('line-through') !== -1) { - context.save(); - context.beginPath(); - context.moveTo(0, 0); - context.lineTo(Math.round(width), 0); - context.lineWidth = fontSize / 15; - context.strokeStyle = fill; - context.stroke(); - context.restore(); - } - if (letterSpacing !== 0 || align === JUSTIFY) { - // var words = text.split(' '); - var spacesNumber = text.split(' ').length - 1; - for (var li = 0; li < text.length; li++) { - var letter = text[li]; - // skip justify for the last line - if (letter === ' ' && n !== textArrLen - 1 && align === JUSTIFY) { - context.translate( - Math.floor((totalWidth - width) / spacesNumber), - 0 - ); - } - this.partialText = letter; - context.fillStrokeShape(this); - context.translate( - Math.round(this._getTextSize(letter).width) + letterSpacing, - 0 - ); - } - } else { - this.partialText = text; - - context.fillStrokeShape(this); - } - context.restore(); - context.translate(0, lineHeightPx); - } - context.restore(); - }, - _hitFunc: function(context) { - var width = this.getWidth(), height = this.getHeight(); - - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); - }, - // _useBufferCanvas: function(caching) { - // var useIt = Konva.Shape.prototype._useBufferCanvas.call(this, caching); - // if (useIt) { - // return true; - // } - // return false; - // // return isFirefox && this.hasFill() && this.hasShadow(); - // }, - setText: function(text) { - var str = Konva.Util._isString(text) ? text : (text || '').toString(); - this._setAttr(TEXT, str); - return this; - }, - /** - * get width of text area, which includes padding - * @method - * @memberof Konva.Text.prototype - * @returns {Number} - */ - getWidth: function() { - var isAuto = this.attrs.width === AUTO || this.attrs.width === undefined; - return isAuto - ? this.getTextWidth() + this.getPadding() * 2 - : this.attrs.width; - }, - /** - * get the height of the text area, which takes into account multi-line text, line heights, and padding - * @method - * @memberof Konva.Text.prototype - * @returns {Number} - */ - getHeight: function() { - var isAuto = this.attrs.height === AUTO || - this.attrs.height === undefined; - return isAuto - ? this.getTextHeight() * this.textArr.length * this.getLineHeight() + - this.getPadding() * 2 - : this.attrs.height; - }, - /** - * get text width - * @method - * @memberof Konva.Text.prototype - * @returns {Number} - */ - getTextWidth: function() { - return this.textWidth; - }, - /** - * get text height - * @method - * @memberof Konva.Text.prototype - * @returns {Number} - */ - getTextHeight: function() { - return this.textHeight; - }, - _getTextSize: function(text) { - var _context = dummyContext, fontSize = this.getFontSize(), metrics; - - _context.save(); - _context.font = this._getContextFont(); - - metrics = _context.measureText(text); - _context.restore(); - return { - width: metrics.width, - height: parseInt(fontSize, 10) - }; - }, - _getContextFont: function() { - // IE don't want to work with usual font style - // bold was not working - // removing font variant will solve - // fix for: https://github.com/konvajs/konva/issues/94 - if (Konva.UA.isIE) { - return this.getFontStyle() + - SPACE + - this.getFontSize() + - PX_SPACE + - this.getFontFamily(); - } - return this.getFontStyle() + - SPACE + - this.getFontVariant() + - SPACE + - this.getFontSize() + - PX_SPACE + - this.getFontFamily(); - }, - _addTextLine: function(line) { - if (this.align() === JUSTIFY) { - line = line.trim(); - } - var width = this._getTextWidth(line); - return this.textArr.push({ text: line, width: width }); - }, - _getTextWidth: function(text) { - var latterSpacing = this.getLetterSpacing(); - var length = text.length; - return dummyContext.measureText(text).width + - (length ? latterSpacing * (length - 1) : 0); - }, - _setTextData: function() { - var lines = this.getText().split('\n'), - fontSize = +this.getFontSize(), - textWidth = 0, - lineHeightPx = this.getLineHeight() * fontSize, - width = this.attrs.width, - height = this.attrs.height, - fixedWidth = width !== AUTO, - fixedHeight = height !== AUTO, - padding = this.getPadding(), - maxWidth = width - padding * 2, - maxHeightPx = height - padding * 2, - currentHeightPx = 0, - wrap = this.getWrap(), - shouldWrap = wrap !== NONE, - wrapAtWord = wrap !== CHAR && shouldWrap; - - this.textArr = []; - dummyContext.save(); - dummyContext.font = this._getContextFont(); - for (var i = 0, max = lines.length; i < max; ++i) { - var line = lines[i]; - - var lineWidth = this._getTextWidth(line); - if (fixedWidth && lineWidth > maxWidth) { - /* - * if width is fixed and line does not fit entirely - * break the line into multiple fitting lines - */ - while (line.length > 0) { - /* - * use binary search to find the longest substring that - * that would fit in the specified width - */ - var low = 0, high = line.length, match = '', matchWidth = 0; - while (low < high) { - var mid = low + high >>> 1, - substr = line.slice(0, mid + 1), - substrWidth = this._getTextWidth(substr); - if (substrWidth <= maxWidth) { - low = mid + 1; - match = substr; - matchWidth = substrWidth; - } else { - high = mid; - } - } - /* - * 'low' is now the index of the substring end - * 'match' is the substring - * 'matchWidth' is the substring width in px - */ - if (match) { - // a fitting substring was found - if (wrapAtWord) { - // try to find a space or dash where wrapping could be done - var wrapIndex = Math.max( - match.lastIndexOf(SPACE), - match.lastIndexOf(DASH) - ) + 1; - if (wrapIndex > 0) { - // re-cut the substring found at the space/dash position - low = wrapIndex; - match = match.slice(0, low); - matchWidth = this._getTextWidth(match); - } - } - this._addTextLine(match); - textWidth = Math.max(textWidth, matchWidth); - currentHeightPx += lineHeightPx; - if ( - !shouldWrap || - (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) - ) { - /* - * stop wrapping if wrapping is disabled or if adding - * one more line would overflow the fixed height - */ - break; - } - line = line.slice(low); - if (line.length > 0) { - // Check if the remaining text would fit on one line - lineWidth = this._getTextWidth(line); - if (lineWidth <= maxWidth) { - // if it does, add the line and break out of the loop - this._addTextLine(line); - currentHeightPx += lineHeightPx; - textWidth = Math.max(textWidth, lineWidth); - break; - } - } - } else { - // not even one character could fit in the element, abort - break; - } - } - } else { - // element width is automatically adjusted to max line width - this._addTextLine(line); - currentHeightPx += lineHeightPx; - textWidth = Math.max(textWidth, lineWidth); - } - // if element height is fixed, abort if adding one more line would overflow - if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) { - break; - } - } - dummyContext.restore(); - this.textHeight = fontSize; - // var maxTextWidth = 0; - // for(var j = 0; j < this.textArr.length; j++) { - // maxTextWidth = Math.max(maxTextWidth, this.textArr[j].width); - // } - this.textWidth = textWidth; - } - }; - Konva.Util.extend(Konva.Text, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Text, 'fontFamily', 'Arial'); - - /** - * get/set font family - * @name fontFamily - * @method - * @memberof Konva.Text.prototype - * @param {String} fontFamily - * @returns {String} - * @example - * // get font family - * var fontFamily = text.fontFamily(); - * - * // set font family - * text.fontFamily('Arial'); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'fontSize', 12); - - /** - * get/set font size in pixels - * @name fontSize - * @method - * @memberof Konva.Text.prototype - * @param {Number} fontSize - * @returns {Number} - * @example - * // get font size - * var fontSize = text.fontSize(); - * - * // set font size to 22px - * text.fontSize(22); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'fontStyle', NORMAL); - - /** - * set font style. Can be 'normal', 'italic', or 'bold'. 'normal' is the default. - * @name fontStyle - * @method - * @memberof Konva.Text.prototype - * @param {String} fontStyle - * @returns {String} - * @example - * // get font style - * var fontStyle = text.fontStyle(); - * - * // set font style - * text.fontStyle('bold'); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'fontVariant', NORMAL); - - /** - * set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. - * @name fontVariant - * @method - * @memberof Konva.Text.prototype - * @param {String} fontVariant - * @returns {String} - * @example - * // get font variant - * var fontVariant = text.fontVariant(); - * - * // set font variant - * text.fontVariant('small-caps'); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'padding', 0); - - /** - * set padding - * @name padding - * @method - * @memberof Konva.Text.prototype - * @param {Number} padding - * @returns {Number} - * @example - * // get padding - * var padding = text.padding(); - * - * // set padding to 10 pixels - * text.padding(10); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'align', LEFT); - - /** - * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' - * @name align - * @method - * @memberof Konva.Text.prototype - * @param {String} align - * @returns {String} - * @example - * // get text align - * var align = text.align(); - * - * // center text - * text.align('center'); - * - * // align text to right - * text.align('right'); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'lineHeight', 1); - - /** - * get/set line height. The default is 1. - * @name lineHeight - * @method - * @memberof Konva.Text.prototype - * @param {Number} lineHeight - * @returns {Number} - * @example - * // get line height - * var lineHeight = text.lineHeight(); - * - * // set the line height - * text.lineHeight(2); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'wrap', WORD); - - /** - * get/set wrap. Can be word, char, or none. Default is word. - * @name wrap - * @method - * @memberof Konva.Text.prototype - * @param {String} wrap - * @returns {String} - * @example - * // get wrap - * var wrap = text.wrap(); - * - * // set wrap - * text.wrap('word'); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'letterSpacing', 0); - - /** - * set letter spacing property. Default value is 0. - * @name letterSpacing - * @method - * @memberof Konva.TextPath.prototype - * @param {Number} letterSpacing - */ - - Konva.Factory.addGetter(Konva.Text, 'text', EMPTY_STRING); - Konva.Factory.addOverloadedGetterSetter(Konva.Text, 'text'); - - /** - * get/set text - * @name getText - * @method - * @memberof Konva.Text.prototype - * @param {String} text - * @returns {String} - * @example - * // get text - * var text = text.text(); - * - * // set text - * text.text('Hello world!'); - */ - - Konva.Factory.addGetterSetter(Konva.Text, 'textDecoration', EMPTY_STRING); - - /** - * get/set text decoration of a text. Possible values are 'underline', 'line-through' or combination of these values separated by space - * @name textDecoration - * @method - * @memberof Konva.Text.prototype - * @param {String} textDecoration - * @returns {String} - * @example - * // get text decoration - * var textDecoration = text.textDecoration(); - * - * // underline text - * text.textDecoration('underline'); - * - * // strike text - * text.textDecoration('line-through'); - * - * // underline and strike text - * text.textDecoration('underline line-through'); - */ - - Konva.Collection.mapMethods(Konva.Text); -})(); - -(function() { - 'use strict'; - /** - * Line constructor.  Lines are defined by an array of points and - * a tension - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Array} config.points - * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. - * The default is 0 - * @param {Boolean} [config.closed] defines whether or not the line shape is closed, creating a polygon or blob - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var line = new Konva.Line({ - * x: 100, - * y: 50, - * points: [73, 70, 340, 23, 450, 60, 500, 20], - * stroke: 'red', - * tension: 1 - * }); - */ - Konva.Line = function(config) { - this.___init(config); - }; - - Konva.Line.prototype = { - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = 'Line'; - - this.on( - 'pointsChange.konva tensionChange.konva closedChange.konva', - function() { - this._clearCache('tensionPoints'); - } - ); - - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - var points = this.getPoints(), - length = points.length, - tension = this.getTension(), - closed = this.getClosed(), - tp, - len, - n; - - if (!length) { - return; - } - - context.beginPath(); - context.moveTo(points[0], points[1]); - - // tension - if (tension !== 0 && length > 4) { - tp = this.getTensionPoints(); - len = tp.length; - n = closed ? 0 : 4; - - if (!closed) { - context.quadraticCurveTo(tp[0], tp[1], tp[2], tp[3]); - } - - while (n < len - 2) { - context.bezierCurveTo( - tp[n++], - tp[n++], - tp[n++], - tp[n++], - tp[n++], - tp[n++] - ); - } - - if (!closed) { - context.quadraticCurveTo( - tp[len - 2], - tp[len - 1], - points[length - 2], - points[length - 1] - ); - } - } else { - // no tension - for (n = 2; n < length; n += 2) { - context.lineTo(points[n], points[n + 1]); - } - } - - // closed e.g. polygons and blobs - if (closed) { - context.closePath(); - context.fillStrokeShape(this); - } else { - // open e.g. lines and splines - context.strokeShape(this); - } - }, - getTensionPoints: function() { - return this._getCache('tensionPoints', this._getTensionPoints); - }, - _getTensionPoints: function() { - if (this.getClosed()) { - return this._getTensionPointsClosed(); - } else { - return Konva.Util._expandPoints(this.getPoints(), this.getTension()); - } - }, - _getTensionPointsClosed: function() { - var p = this.getPoints(), - len = p.length, - tension = this.getTension(), - util = Konva.Util, - firstControlPoints = util._getControlPoints( - p[len - 2], - p[len - 1], - p[0], - p[1], - p[2], - p[3], - tension - ), - lastControlPoints = util._getControlPoints( - p[len - 4], - p[len - 3], - p[len - 2], - p[len - 1], - p[0], - p[1], - tension - ), - middle = Konva.Util._expandPoints(p, tension), - tp = [firstControlPoints[2], firstControlPoints[3]] - .concat(middle) - .concat([ - lastControlPoints[0], - lastControlPoints[1], - p[len - 2], - p[len - 1], - lastControlPoints[2], - lastControlPoints[3], - firstControlPoints[0], - firstControlPoints[1], - p[0], - p[1] - ]); - - return tp; - }, - getWidth: function() { - return this.getSelfRect().width; - }, - getHeight: function() { - return this.getSelfRect().height; - }, - // overload size detection - getSelfRect: function() { - var points; - if (this.getTension() !== 0) { - points = this._getTensionPoints(); - } else { - points = this.getPoints(); - } - var minX = points[0]; - var maxX = points[0]; - var minY = points[1]; - var maxY = points[1]; - var x, y; - for (var i = 0; i < points.length / 2; i++) { - x = points[i * 2]; - y = points[i * 2 + 1]; - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - return { - x: Math.round(minX), - y: Math.round(minY), - width: Math.round(maxX - minX), - height: Math.round(maxY - minY) - }; - } - }; - Konva.Util.extend(Konva.Line, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Line, 'closed', false); - - /** - * get/set closed flag. The default is false - * @name closed - * @method - * @memberof Konva.Line.prototype - * @param {Boolean} closed - * @returns {Boolean} - * @example - * // get closed flag - * var closed = line.closed(); - * - * // close the shape - * line.closed(true); - * - * // open the shape - * line.closed(false); - */ - - Konva.Factory.addGetterSetter(Konva.Line, 'tension', 0); - - /** - * get/set tension - * @name tension - * @method - * @memberof Konva.Line.prototype - * @param {Number} Higher values will result in a more curvy line. A value of 0 will result in no interpolation. - * The default is 0 - * @returns {Number} - * @example - * // get tension - * var tension = line.tension(); - * - * // set tension - * line.tension(3); - */ - - Konva.Factory.addGetterSetter(Konva.Line, 'points', []); - /** - * get/set points array - * @name points - * @method - * @memberof Konva.Line.prototype - * @param {Array} points - * @returns {Array} - * @example - * // get points - * var points = line.points(); - * - * // set points - * line.points([10, 20, 30, 40, 50, 60]); - * - * // push a new point - * line.points(line.points().concat([70, 80])); - */ - - Konva.Collection.mapMethods(Konva.Line); -})(); - -(function() { - 'use strict'; - /** - * Sprite constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} config.animation animation key - * @param {Object} config.animations animation map - * @param {Integer} [config.frameIndex] animation frame index - * @param {Image} config.image image object - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var imageObj = new Image(); - * imageObj.onload = function() { - * var sprite = new Konva.Sprite({ - * x: 200, - * y: 100, - * image: imageObj, - * animation: 'standing', - * animations: { - * standing: [ - * // x, y, width, height (6 frames) - * 0, 0, 49, 109, - * 52, 0, 49, 109, - * 105, 0, 49, 109, - * 158, 0, 49, 109, - * 210, 0, 49, 109, - * 262, 0, 49, 109 - * ], - * kicking: [ - * // x, y, width, height (6 frames) - * 0, 109, 45, 98, - * 45, 109, 45, 98, - * 95, 109, 63, 98, - * 156, 109, 70, 98, - * 229, 109, 60, 98, - * 287, 109, 41, 98 - * ] - * }, - * frameRate: 7, - * frameIndex: 0 - * }); - * }; - * imageObj.src = '/path/to/image.jpg' - */ - Konva.Sprite = function(config) { - this.___init(config); - }; - - Konva.Sprite.prototype = { - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = 'Sprite'; - - this._updated = true; - var that = this; - this.anim = new Konva.Animation(function() { - // if we don't need to redraw layer we should return false - var updated = that._updated; - that._updated = false; - return updated; - }); - this.on('animationChange.konva', function() { - // reset index when animation changes - this.frameIndex(0); - }); - this.on('frameIndexChange.konva', function() { - this._updated = true; - }); - // smooth change for frameRate - this.on('frameRateChange.konva', function() { - if (!this.anim.isRunning()) { - return; - } - clearInterval(this.interval); - this._setInterval(); - }); - - this.sceneFunc(this._sceneFunc); - this.hitFunc(this._hitFunc); - }, - _sceneFunc: function(context) { - var anim = this.getAnimation(), - index = this.frameIndex(), - ix4 = index * 4, - set = this.getAnimations()[anim], - offsets = this.frameOffsets(), - x = set[ix4 + 0], - y = set[ix4 + 1], - width = set[ix4 + 2], - height = set[ix4 + 3], - image = this.getImage(); - - if (this.hasFill() || this.hasStroke()) { - context.beginPath(); - context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); - } - - if (image) { - if (offsets) { - var offset = offsets[anim], ix2 = index * 2; - context.drawImage( - image, - x, - y, - width, - height, - offset[ix2 + 0], - offset[ix2 + 1], - width, - height - ); - } else { - context.drawImage(image, x, y, width, height, 0, 0, width, height); - } - } - }, - _hitFunc: function(context) { - var anim = this.getAnimation(), - index = this.frameIndex(), - ix4 = index * 4, - set = this.getAnimations()[anim], - offsets = this.frameOffsets(), - width = set[ix4 + 2], - height = set[ix4 + 3]; - - context.beginPath(); - if (offsets) { - var offset = offsets[anim]; - var ix2 = index * 2; - context.rect(offset[ix2 + 0], offset[ix2 + 1], width, height); - } else { - context.rect(0, 0, width, height); - } - context.closePath(); - context.fillShape(this); - }, - _useBufferCanvas: function() { - return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && - this.hasStroke(); - }, - _setInterval: function() { - var that = this; - this.interval = setInterval( - function() { - that._updateIndex(); - }, - 1000 / this.getFrameRate() - ); - }, - /** - * start sprite animation - * @method - * @memberof Konva.Sprite.prototype - */ - start: function() { - 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.setLayers(layer); - this._setInterval(); - this.anim.start(); - }, - /** - * stop sprite animation - * @method - * @memberof Konva.Sprite.prototype - */ - stop: function() { - this.anim.stop(); - clearInterval(this.interval); - }, - /** - * determine if animation of sprite is running or not. returns true or false - * @method - * @memberof Konva.Animation.prototype - * @returns {Boolean} - */ - isRunning: function() { - return this.anim.isRunning(); - }, - _updateIndex: function() { - var index = this.frameIndex(), - animation = this.getAnimation(), - animations = this.getAnimations(), - anim = animations[animation], - len = anim.length / 4; - - if (index < len - 1) { - this.frameIndex(index + 1); - } else { - this.frameIndex(0); - } - } - }; - Konva.Util.extend(Konva.Sprite, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Sprite, 'animation'); - - /** - * get/set animation key - * @name animation - * @method - * @memberof Konva.Sprite.prototype - * @param {String} anim animation key - * @returns {String} - * @example - * // get animation key - * var animation = sprite.animation(); - * - * // set animation key - * sprite.animation('kicking'); - */ - - Konva.Factory.addGetterSetter(Konva.Sprite, 'animations'); - - /** - * get/set animations map - * @name animations - * @method - * @memberof Konva.Sprite.prototype - * @param {Object} animations - * @returns {Object} - * @example - * // get animations map - * var animations = sprite.animations(); - * - * // set animations map - * sprite.animations({ - * standing: [ - * // x, y, width, height (6 frames) - * 0, 0, 49, 109, - * 52, 0, 49, 109, - * 105, 0, 49, 109, - * 158, 0, 49, 109, - * 210, 0, 49, 109, - * 262, 0, 49, 109 - * ], - * kicking: [ - * // x, y, width, height (6 frames) - * 0, 109, 45, 98, - * 45, 109, 45, 98, - * 95, 109, 63, 98, - * 156, 109, 70, 98, - * 229, 109, 60, 98, - * 287, 109, 41, 98 - * ] - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Sprite, 'frameOffsets'); - - /** - * get/set offsets map - * @name offsets - * @method - * @memberof Konva.Sprite.prototype - * @param {Object} offsets - * @returns {Object} - * @example - * // get offsets map - * var offsets = sprite.offsets(); - * - * // set offsets map - * sprite.offsets({ - * standing: [ - * // x, y (6 frames) - * 0, 0, - * 0, 0, - * 5, 0, - * 0, 0, - * 0, 3, - * 2, 0 - * ], - * kicking: [ - * // x, y (6 frames) - * 0, 5, - * 5, 0, - * 10, 0, - * 0, 0, - * 2, 1, - * 0, 0 - * ] - * }); - */ - - Konva.Factory.addGetterSetter(Konva.Sprite, 'image'); - - /** - * get/set image - * @name image - * @method - * @memberof Konva.Sprite.prototype - * @param {Image} image - * @returns {Image} - * @example - * // get image - * var image = sprite.image(); - * - * // set image - * sprite.image(imageObj); - */ - - Konva.Factory.addGetterSetter(Konva.Sprite, 'frameIndex', 0); - - /** - * set/set animation frame index - * @name frameIndex - * @method - * @memberof Konva.Sprite.prototype - * @param {Integer} frameIndex - * @returns {Integer} - * @example - * // get animation frame index - * var frameIndex = sprite.frameIndex(); - * - * // set animation frame index - * sprite.frameIndex(3); - */ - - Konva.Factory.addGetterSetter(Konva.Sprite, 'frameRate', 17); - - /** - * get/set frame rate in frames per second. Increase this number to make the sprite - * animation run faster, and decrease the number to make the sprite animation run slower - * The default is 17 frames per second - * @name frameRate - * @method - * @memberof Konva.Sprite.prototype - * @param {Integer} frameRate - * @returns {Integer} - * @example - * // get frame rate - * var frameRate = sprite.frameRate(); - * - * // set frame rate to 2 frames per second - * sprite.frameRate(2); - */ - - Konva.Factory.backCompat(Konva.Sprite, { - index: 'frameIndex', - getIndex: 'getFrameIndex', - setIndex: 'setFrameIndex' - }); - - Konva.Collection.mapMethods(Konva.Sprite); -})(); - -/*eslint-disable no-shadow, max-len, max-depth */ -(function() { - 'use strict'; - /** - * Path constructor. - * @author Jason Follas - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} config.data SVG data string - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var path = new Konva.Path({ - * x: 240, - * y: 40, - * data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z', - * fill: 'green', - * scale: 2 - * }); - */ - Konva.Path = function(config) { - this.___init(config); - }; - - Konva.Path.prototype = { - ___init: function(config) { - this.dataArray = []; - var that = this; - - // call super constructor - Konva.Shape.call(this, config); - this.className = 'Path'; - - this.dataArray = Konva.Path.parsePathData(this.getData()); - this.on('dataChange.konva', function() { - that.dataArray = Konva.Path.parsePathData(this.getData()); - }); - - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: 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; - } - } - - context.fillStrokeShape(this); - }, - getSelfRect: function() { - var points = []; - this.dataArray.forEach(function(data) { - points = points.concat(data.points); - }); - var minX = points[0]; - var maxX = points[0]; - var minY = points[1]; - var maxY = points[1]; - var x, y; - for (var i = 0; i < points.length / 2; i++) { - x = points[i * 2]; - y = points[i * 2 + 1]; - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - return { - x: Math.round(minX), - y: Math.round(minY), - width: Math.round(maxX - minX), - height: Math.round(maxY - minY) - }; - } - }; - Konva.Util.extend(Konva.Path, Konva.Shape); - - Konva.Path.getLineLength = function(x1, y1, x2, y2) { - return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); - }; - Konva.Path.getPointOnLine = function(dist, P1x, P1y, P2x, P2y, fromX, fromY) { - if (fromX === undefined) { - fromX = P1x; - } - if (fromY === undefined) { - fromY = P1y; - } - - var m = (P2y - P1y) / (P2x - P1x + 0.00000001); - var run = Math.sqrt(dist * dist / (1 + m * m)); - if (P2x < P1x) { - run *= -1; - } - var rise = m * run; - var pt; - - if (P2x === P1x) { - // vertical line - pt = { - x: fromX, - y: fromY + rise - }; - } else if ((fromY - P1y) / (fromX - P1x + 0.00000001) === m) { - pt = { - x: fromX + run, - y: fromY + rise - }; - } else { - var ix, iy; - - var len = this.getLineLength(P1x, P1y, P2x, P2y); - if (len < 0.00000001) { - return undefined; - } - var u = (fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y); - u = u / (len * len); - ix = P1x + u * (P2x - P1x); - iy = P1y + u * (P2y - P1y); - - var pRise = this.getLineLength(fromX, fromY, ix, iy); - var pRun = Math.sqrt(dist * dist - pRise * pRise); - run = Math.sqrt(pRun * pRun / (1 + m * m)); - if (P2x < P1x) { - run *= -1; - } - rise = m * run; - pt = { - x: ix + run, - y: iy + rise - }; - } - - return pt; - }; - - Konva.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 - }; - }; - Konva.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 - }; - }; - Konva.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 - */ - Konva.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 (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; - // Move var from within the switch to up here (jshint) - var prevCmd, ctlPtx, ctlPty; // Ss, Tt - var rx, ry, psi, fa, fs, x1, y1; // Aa - - // 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': - var dx = p.shift(); - var dy = p.shift(); - cpx += dx; - cpy += dy; - cmd = 'M'; - // After closing the path move the current position - // to the the first point of the path (if any). - if (ca.length > 2 && ca[ca.length - 1].command === 'z') { - for (var idx = ca.length - 2; idx >= 0; idx--) { - if (ca[idx].command === 'M') { - cpx = ca[idx].points[0] + dx; - cpy = ca[idx].points[1] + dy; - break; - } - } - } - 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': - ctlPtx = cpx; - ctlPty = cpy; - 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': - ctlPtx = cpx; - ctlPty = cpy; - 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': - ctlPtx = cpx; - ctlPty = cpy; - 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': - ctlPtx = cpx; - ctlPty = cpy; - 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': - rx = p.shift(); - ry = p.shift(); - psi = p.shift(); - fa = p.shift(); - fs = p.shift(); - 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': - rx = p.shift(); - ry = p.shift(); - psi = p.shift(); - fa = p.shift(); - fs = p.shift(); - 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; - }; - Konva.Path.calcLength = function(x, y, cmd, points) { - var len, p1, p2, t; - var path = Konva.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; - }; - Konva.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 - Konva.Factory.addGetterSetter(Konva.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 - * @method - * @memberof Konva.Path.prototype - * @param {String} SVG path command string - */ - - /** - * get SVG path data string - * @name getData - * @method - * @memberof Konva.Path.prototype - */ - - Konva.Collection.mapMethods(Konva.Path); -})(); - -(function() { - 'use strict'; - var EMPTY_STRING = '', - //CALIBRI = 'Calibri', - NORMAL = 'normal'; - - /** - * Path constructor. - * @author Jason Follas - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {String} [config.fontFamily] default is Calibri - * @param {Number} [config.fontSize] default is 12 - * @param {String} [config.fontStyle] can be normal, bold, or italic. Default is normal - * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal - * @param {String} config.text - * @param {String} config.data SVG data string - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var textpath = new Konva.TextPath({ - * x: 100, - * y: 50, - * fill: '#333', - * fontSize: '24', - * fontFamily: 'Arial', - * text: 'All the world\'s a stage, and all the men and women merely players.', - * data: 'M10,10 C0,0 10,150 100,100 S300,150 400,50' - * }); - */ - Konva.TextPath = function(config) { - this.___init(config); - }; - - function _fillFunc(context) { - context.fillText(this.partialText, 0, 0); - } - function _strokeFunc(context) { - context.strokeText(this.partialText, 0, 0); - } - - Konva.TextPath.prototype = { - ___init: function(config) { - var that = this; - this.dummyCanvas = Konva.Util.createCanvasElement(); - this.dataArray = []; - - // call super constructor - Konva.Shape.call(this, config); - - // overrides - // TODO: shouldn't this be on the prototype? - this._fillFunc = _fillFunc; - this._strokeFunc = _strokeFunc; - this._fillFuncHit = _fillFunc; - this._strokeFuncHit = _strokeFunc; - - this.className = 'TextPath'; - - this.dataArray = Konva.Path.parsePathData(this.attrs.data); - this.on('dataChange.konva', function() { - that.dataArray = Konva.Path.parsePathData(this.attrs.data); - that._setTextData(); - }); - - // update text data for certain attr changes - this.on( - 'textChange.konva alignChange.konva letterSpacingChange.konva', - that._setTextData - ); - that._setTextData(); - this.sceneFunc(this._sceneFunc); - this.hitFunc(this._hitFunc); - }, - _sceneFunc: function(context) { - context.setAttr('font', this._getContextFont()); - context.setAttr('textBaseline', this.getTextBaseline()); - context.setAttr('textAlign', 'left'); - context.save(); - - var textDecoration = this.textDecoration(); - var fill = this.fill(); - var fontSize = this.fontSize(); - - var glyphInfo = this.glyphInfo; - if (textDecoration === 'underline') { - context.beginPath(); - } - for (var i = 0; i < glyphInfo.length; i++) { - context.save(); - - var p0 = glyphInfo[i].p0; - - context.translate(p0.x, p0.y); - context.rotate(glyphInfo[i].rotation); - this.partialText = glyphInfo[i].text; - - context.fillStrokeShape(this); - if (textDecoration === 'underline') { - if (i === 0) { - context.moveTo(0, fontSize / 2 + 1); - } - - context.lineTo(fontSize, fontSize / 2 + 1); - } - context.restore(); - - //// To assist with debugging visually, uncomment following - // - // if (i % 2) - // context.strokeStyle = 'cyan'; - // else - // context.strokeStyle = 'green'; - // var p1 = glyphInfo[i].p1; - // context.moveTo(p0.x, p0.y); - // context.lineTo(p1.x, p1.y); - // context.stroke(); - } - if (textDecoration === 'underline') { - context.strokeStyle = fill; - context.lineWidth = fontSize / 20; - context.stroke(); - } - - context.restore(); - }, - _hitFunc: function(context) { - context.beginPath(); - - var glyphInfo = this.glyphInfo; - if (glyphInfo.length >= 1) { - var p0 = glyphInfo[0].p0; - context.moveTo(p0.x, p0.y); - } - for (var i = 0; i < glyphInfo.length; i++) { - var p1 = glyphInfo[i].p1; - context.lineTo(p1.x, p1.y); - } - context.setAttr('lineWidth', this.getFontSize()); - context.setAttr('strokeStyle', this.colorKey); - context.stroke(); - }, - /** - * get text width in pixels - * @method - * @memberof Konva.TextPath.prototype - */ - getTextWidth: function() { - return this.textWidth; - }, - /** - * get text height in pixels - * @method - * @memberof Konva.TextPath.prototype - */ - getTextHeight: function() { - return this.textHeight; - }, - /** - * set text - * @method - * @memberof Konva.TextPath.prototype - * @param {String} text - */ - setText: function(text) { - Konva.Text.prototype.setText.call(this, text); - }, - _getTextSize: function(text) { - var dummyCanvas = this.dummyCanvas; - var _context = dummyCanvas.getContext('2d'); - - _context.save(); - - _context.font = this._getContextFont(); - var metrics = _context.measureText(text); - - _context.restore(); - - return { - width: metrics.width, - height: parseInt(this.attrs.fontSize, 10) - }; - }, - _setTextData: function() { - var that = this; - var size = this._getTextSize(this.attrs.text); - var letterSpacing = this.getLetterSpacing(); - var align = this.align(); - - this.textWidth = size.width; - this.textHeight = size.height; - - var textFullWidth = Math.max( - this.textWidth + ((this.attrs.text || '').length - 1) * letterSpacing, - 0 - ); - - this.glyphInfo = []; - - var fullPathWidth = 0; - for (var l = 0; l < that.dataArray.length; l++) { - if (that.dataArray[l].pathLength > 0) { - fullPathWidth += that.dataArray[l].pathLength; - } - } - - var offset = 0; - if (align === 'center') { - offset = Math.max(0, fullPathWidth / 2 - textFullWidth / 2); - } - if (align === 'right') { - offset = Math.max(0, fullPathWidth - textFullWidth); - } - - var charArr = this.getText().split(''); - var spacesNumber = this.getText().split(' ').length - 1; - - var p0, p1, pathCmd; - - var pIndex = -1; - var currentT = 0; - // var sumLength = 0; - // for(var j = 0; j < that.dataArray.length; j++) { - // if(that.dataArray[j].pathLength > 0) { - // - // if (sumLength + that.dataArray[j].pathLength > offset) {} - // fullPathWidth += that.dataArray[j].pathLength; - // } - // } - - var getNextPathSegment = function() { - currentT = 0; - var pathData = that.dataArray; - - for (var j = pIndex + 1; j < pathData.length; j++) { - if (pathData[j].pathLength > 0) { - pIndex = j; - - return pathData[j]; - } else if (pathData[j].command === 'M') { - p0 = { - x: pathData[j].points[0], - y: pathData[j].points[1] - }; - } - } - - return {}; - }; - - var findSegmentToFitCharacter = function(c) { - var glyphWidth = that._getTextSize(c).width + letterSpacing; - - if (c === ' ' && align === 'justify') { - glyphWidth += (fullPathWidth - textFullWidth) / spacesNumber; - } - - var currLen = 0; - var attempts = 0; - - 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 ( - Konva.Path.getLineLength( - p0.x, - p0.y, - pathCmd.points[0], - pathCmd.points[1] - ) > glyphWidth - ) { - p1 = Konva.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; - } else if (glyphWidth > currLen) { - // Just in case start is 0 - currentT += Math.PI / 180.0 * dTheta / Math.abs(dTheta); - } else { - currentT -= Math.PI / 360.0 * dTheta / Math.abs(dTheta); - } - - // Credit for bug fix: @therth https://github.com/ericdrowell/KonvaJS/issues/249 - // Old code failed to render text along arc of this path: "M 50 50 a 150 50 0 0 1 250 50 l 50 0" - if ( - (dTheta < 0 && currentT < end) || - (dTheta >= 0 && currentT > end) - ) { - currentT = end; - needNewSegment = true; - } - p1 = Konva.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 = Konva.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 = Konva.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 = Konva.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); - } - - if (needNewSegment) { - needNewSegment = false; - pathCmd = undefined; - } - } - }; - - // fake search for offset, this is very bad approach - // TODO: find other way to add offset from start (for align) - var testChar = 'C'; - var glyphWidth = that._getTextSize(testChar).width + letterSpacing; - for (var k = 0; k < offset / glyphWidth; k++) { - findSegmentToFitCharacter(testChar); - if (p0 === undefined || p1 === undefined) { - break; - } - p0 = p1; - } - - 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 = Konva.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 = Konva.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; - } - }, - getSelfRect: function() { - var points = []; - - this.glyphInfo.forEach(function(info) { - points.push(info.p0.x); - points.push(info.p0.y); - points.push(info.p1.x); - points.push(info.p1.y); - }); - var minX = points[0]; - var maxX = points[0]; - var minY = points[0]; - var maxY = points[0]; - var x, y; - for (var i = 0; i < points.length / 2; i++) { - x = points[i * 2]; - y = points[i * 2 + 1]; - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - var fontSize = this.fontSize(); - return { - x: Math.round(minX) - fontSize / 2, - y: Math.round(minY) - fontSize / 2, - width: Math.round(maxX - minX) + fontSize, - height: Math.round(maxY - minY) + fontSize - }; - } - }; - - // map TextPath methods to Text - Konva.TextPath.prototype._getContextFont = Konva.Text.prototype._getContextFont; - - Konva.Util.extend(Konva.TextPath, Konva.Shape); - - // add setters and getters - Konva.Factory.addGetterSetter(Konva.TextPath, 'fontFamily', 'Arial'); - - /** - * set font family - * @name setFontFamily - * @method - * @memberof Konva.TextPath.prototype - * @param {String} fontFamily - */ - - /** - * get font family - * @name getFontFamily - * @method - * @memberof Konva.TextPath.prototype - */ - - Konva.Factory.addGetterSetter(Konva.TextPath, 'fontSize', 12); - - /** - * set font size - * @name setFontSize - * @method - * @memberof Konva.TextPath.prototype - * @param {int} fontSize - */ - - /** - * get font size - * @name getFontSize - * @method - * @memberof Konva.TextPath.prototype - */ - - Konva.Factory.addGetterSetter(Konva.TextPath, 'fontStyle', NORMAL); - - /** - * set font style. Can be 'normal', 'italic', or 'bold'. 'normal' is the default. - * @name setFontStyle - * @method - * @memberof Konva.TextPath.prototype - * @param {String} fontStyle - */ - Konva.Factory.addGetterSetter(Konva.TextPath, 'align', 'left'); - - /** - * get/set horizontal align of text. Can be 'left', 'center', 'right' or 'justify' - * @name align - * @method - * @memberof Konva.Text.prototype - * @param {String} align - * @returns {String} - * @example - * // get text align - * var align = text.align(); - * - * // center text - * text.align('center'); - * - * // align text to right - * text.align('right'); - */ - - Konva.Factory.addGetterSetter(Konva.TextPath, 'letterSpacing', 0); - - /** - * set letter spacing property. Default value is 0. - * @name letterSpacing - * @method - * @memberof Konva.TextPath.prototype - * @param {Number} letterSpacing - */ - - Konva.Factory.addGetterSetter(Konva.TextPath, 'textBaseline', 'middle'); - - /** - * set textBaseline property. Default value is 'middle'. - * Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging' - * @name textBaseline - * @method - * @memberof Konva.TextPath.prototype - * @param {Number} textBaseline - */ - - /** - * get font style - * @name getFontStyle - * @method - * @memberof Konva.TextPath.prototype - */ - - Konva.Factory.addGetterSetter(Konva.TextPath, 'fontVariant', NORMAL); - - /** - * set font variant. Can be 'normal' or 'small-caps'. 'normal' is the default. - * @name setFontVariant - * @method - * @memberof Konva.TextPath.prototype - * @param {String} fontVariant - */ - - /** - * @get font variant - * @name getFontVariant - * @method - * @memberof Konva.TextPath.prototype - */ - - Konva.Factory.addGetter(Konva.TextPath, 'text', EMPTY_STRING); - - /** - * get text - * @name getText - * @method - * @memberof Konva.TextPath.prototype - */ - - Konva.Factory.addGetterSetter(Konva.TextPath, 'textDecoration', null); - - /** - * get/set text decoration of a text. Can be '' or 'underline' - * @name textDecoration - * @method - * @memberof Konva.Text.prototype - * @param {String} textDecoration - * @returns {String} - * @example - * // get text decoration - * var textDecoration = text.textDecoration(); - * - * // center text - * text.textDecoration('underline'); - */ - - Konva.Collection.mapMethods(Konva.TextPath); -})(); - -(function() { - 'use strict'; - /** - * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc. - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Number} config.sides - * @param {Number} config.radius - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var hexagon = new Konva.RegularPolygon({ - * x: 100, - * y: 200, - * sides: 6, - * radius: 70, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 4 - * }); - */ - Konva.RegularPolygon = function(config) { - this.___init(config); - }; - - Konva.RegularPolygon.prototype = { - _centroid: true, - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = 'RegularPolygon'; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - var sides = this.attrs.sides, radius = this.attrs.radius, n, x, y; - - context.beginPath(); - context.moveTo(0, 0 - radius); - - for (n = 1; n < sides; n++) { - x = radius * Math.sin(n * 2 * Math.PI / sides); - y = (-1) * radius * Math.cos(n * 2 * Math.PI / sides); - context.lineTo(x, y); - } - context.closePath(); - context.fillStrokeShape(this); - }, - getWidth: function() { - return this.getRadius() * 2; - }, - // implements Shape.prototype.getHeight() - getHeight: function() { - return this.getRadius() * 2; - }, - // implements Shape.prototype.setWidth() - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - if (this.radius() !== width / 2) { - this.setRadius(width / 2); - } - }, - // implements Shape.prototype.setHeight() - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - if (this.radius() !== height / 2) { - this.setRadius(height / 2); - } - } - }; - Konva.Util.extend(Konva.RegularPolygon, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.RegularPolygon, 'radius', 0); - - /** - * set radius - * @name setRadius - * @method - * @memberof Konva.RegularPolygon.prototype - * @param {Number} radius - */ - - /** - * get radius - * @name getRadius - * @method - * @memberof Konva.RegularPolygon.prototype - */ - - Konva.Factory.addGetterSetter(Konva.RegularPolygon, 'sides', 0); - - /** - * set number of sides - * @name setSides - * @method - * @memberof Konva.RegularPolygon.prototype - * @param {int} sides - */ - - /** - * get number of sides - * @name getSides - * @method - * @memberof Konva.RegularPolygon.prototype - */ - - Konva.Collection.mapMethods(Konva.RegularPolygon); -})(); - -(function() { - 'use strict'; - /** - * Star constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Integer} config.numPoints - * @param {Number} config.innerRadius - * @param {Number} config.outerRadius - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var star = new Konva.Star({ - * x: 100, - * y: 200, - * numPoints: 5, - * innerRadius: 70, - * outerRadius: 70, - * fill: 'red', - * stroke: 'black', - * strokeWidth: 4 - * }); - */ - Konva.Star = function(config) { - this.___init(config); - }; - - Konva.Star.prototype = { - _centroid: true, - ___init: function(config) { - // call super constructor - Konva.Shape.call(this, config); - this.className = 'Star'; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - var innerRadius = this.innerRadius(), - outerRadius = this.outerRadius(), - numPoints = this.numPoints(); - - context.beginPath(); - context.moveTo(0, 0 - outerRadius); - - for (var n = 1; n < numPoints * 2; n++) { - var radius = n % 2 === 0 ? outerRadius : innerRadius; - var x = radius * Math.sin(n * Math.PI / numPoints); - var y = (-1) * radius * Math.cos(n * Math.PI / numPoints); - context.lineTo(x, y); - } - context.closePath(); - - context.fillStrokeShape(this); - }, - // implements Shape.prototype.getWidth() - getWidth: function() { - return this.getOuterRadius() * 2; - }, - // implements Shape.prototype.getHeight() - getHeight: function() { - return this.getOuterRadius() * 2; - }, - // implements Shape.prototype.setWidth() - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - if (this.outerRadius() !== width / 2) { - this.setOuterRadius(width / 2); - } - }, - // implements Shape.prototype.setHeight() - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - if (this.outerRadius() !== height / 2) { - this.setOuterRadius(height / 2); - } - } - }; - Konva.Util.extend(Konva.Star, Konva.Shape); - - // add getters setters - Konva.Factory.addGetterSetter(Konva.Star, 'numPoints', 5); - - /** - * set number of points - * @name setNumPoints - * @method - * @memberof Konva.Star.prototype - * @param {Integer} points - */ - - /** - * get number of points - * @name getNumPoints - * @method - * @memberof Konva.Star.prototype - */ - - Konva.Factory.addGetterSetter(Konva.Star, 'innerRadius', 0); - - /** - * set inner radius - * @name setInnerRadius - * @method - * @memberof Konva.Star.prototype - * @param {Number} radius - */ - - /** - * get inner radius - * @name getInnerRadius - * @method - * @memberof Konva.Star.prototype - */ - - Konva.Factory.addGetterSetter(Konva.Star, 'outerRadius', 0); - - /** - * set outer radius - * @name setOuterRadius - * @method - * @memberof Konva.Star.prototype - * @param {Number} radius - */ - - /** - * get outer radius - * @name getOuterRadius - * @method - * @memberof Konva.Star.prototype - */ - - Konva.Collection.mapMethods(Konva.Star); -})(); - -(function() { - 'use strict'; - // constants - var ATTR_CHANGE_LIST = [ - 'fontFamily', - 'fontSize', - 'fontStyle', - 'padding', - 'lineHeight', - 'text', - 'width' - ], - CHANGE_KONVA = 'Change.konva', - NONE = 'none', - UP = 'up', - RIGHT = 'right', - DOWN = 'down', - LEFT = 'left', - LABEL = 'Label', - // cached variables - attrChangeListLen = ATTR_CHANGE_LIST.length; - - /** - * Label constructor.  Labels are groups that contain a Text and Tag shape - * @constructor - * @memberof Konva - * @param {Object} config - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * // create label - * var label = new Konva.Label({ - * x: 100, - * y: 100, - * draggable: true - * }); - * - * // add a tag to the label - * label.add(new Konva.Tag({ - * fill: '#bbb', - * stroke: '#333', - * shadowColor: 'black', - * shadowBlur: 10, - * shadowOffset: [10, 10], - * shadowOpacity: 0.2, - * lineJoin: 'round', - * pointerDirection: 'up', - * pointerWidth: 20, - * pointerHeight: 20, - * cornerRadius: 5 - * })); - * - * // add text to the label - * label.add(new Konva.Text({ - * text: 'Hello World!', - * fontSize: 50, - * lineHeight: 1.2, - * padding: 10, - * fill: 'green' - * })); - */ - Konva.Label = function(config) { - this.____init(config); - }; - - Konva.Label.prototype = { - ____init: function(config) { - var that = this; - - Konva.Group.call(this, config); - this.className = LABEL; - - this.on('add.konva', function(evt) { - that._addListeners(evt.child); - that._sync(); - }); - }, - /** - * get Text shape for the label. You need to access the Text shape in order to update - * the text properties - * @name getText - * @method - * @memberof Konva.Label.prototype - */ - getText: function() { - return this.find('Text')[0]; - }, - /** - * get Tag shape for the label. You need to access the Tag shape in order to update - * the pointer properties and the corner radius - * @name getTag - * @method - * @memberof Konva.Label.prototype - */ - getTag: function() { - return this.find('Tag')[0]; - }, - _addListeners: function(text) { - var that = this, n; - var func = function() { - that._sync(); - }; - - // update text data for certain attr changes - for (n = 0; n < attrChangeListLen; n++) { - text.on(ATTR_CHANGE_LIST[n] + CHANGE_KONVA, func); - } - }, - getWidth: function() { - return this.getText().getWidth(); - }, - getHeight: function() { - return this.getText().getHeight(); - }, - _sync: function() { - var text = this.getText(), - tag = this.getTag(), - width, - height, - pointerDirection, - pointerWidth, - x, - y, - pointerHeight; - - if (text && tag) { - width = text.getWidth(); - height = text.getHeight(); - pointerDirection = tag.getPointerDirection(); - pointerWidth = tag.getPointerWidth(); - pointerHeight = tag.getPointerHeight(); - x = 0; - y = 0; - - switch (pointerDirection) { - case UP: - x = width / 2; - y = (-1) * pointerHeight; - break; - case RIGHT: - x = width + pointerWidth; - y = height / 2; - break; - case DOWN: - x = width / 2; - y = height + pointerHeight; - break; - case LEFT: - x = (-1) * pointerWidth; - y = height / 2; - break; - } - - tag.setAttrs({ - x: (-1) * x, - y: (-1) * y, - width: width, - height: height - }); - - text.setAttrs({ - x: (-1) * x, - y: (-1) * y - }); - } - } - }; - - Konva.Util.extend(Konva.Label, Konva.Group); - - Konva.Collection.mapMethods(Konva.Label); - - /** - * Tag constructor.  A Tag can be configured - * to have a pointer element that points up, right, down, or left - * @constructor - * @memberof Konva - * @param {Object} config - * @param {String} [config.pointerDirection] can be up, right, down, left, or none; the default - * is none. When a pointer is present, the positioning of the label is relative to the tip of the pointer. - * @param {Number} [config.pointerWidth] - * @param {Number} [config.pointerHeight] - * @param {Number} [config.cornerRadius] - */ - Konva.Tag = function(config) { - this.___init(config); - }; - - Konva.Tag.prototype = { - ___init: function(config) { - Konva.Shape.call(this, config); - this.className = 'Tag'; - this.sceneFunc(this._sceneFunc); - }, - _sceneFunc: function(context) { - var width = this.getWidth(), - height = this.getHeight(), - pointerDirection = this.getPointerDirection(), - pointerWidth = this.getPointerWidth(), - pointerHeight = this.getPointerHeight(), - cornerRadius = Math.min(this.getCornerRadius(), width / 2, height / 2); - - context.beginPath(); - if (!cornerRadius) { - context.moveTo(0, 0); - } else { - context.moveTo(cornerRadius, 0); - } - - if (pointerDirection === UP) { - context.lineTo((width - pointerWidth) / 2, 0); - context.lineTo(width / 2, (-1) * pointerHeight); - context.lineTo((width + pointerWidth) / 2, 0); - } - - if (!cornerRadius) { - context.lineTo(width, 0); - } else { - context.lineTo(width - cornerRadius, 0); - context.arc( - width - cornerRadius, - cornerRadius, - cornerRadius, - Math.PI * 3 / 2, - 0, - false - ); - } - - if (pointerDirection === RIGHT) { - context.lineTo(width, (height - pointerHeight) / 2); - context.lineTo(width + pointerWidth, height / 2); - context.lineTo(width, (height + pointerHeight) / 2); - } - - if (!cornerRadius) { - context.lineTo(width, height); - } else { - context.lineTo(width, height - cornerRadius); - context.arc( - width - cornerRadius, - height - cornerRadius, - cornerRadius, - 0, - Math.PI / 2, - false - ); - } - - if (pointerDirection === DOWN) { - context.lineTo((width + pointerWidth) / 2, height); - context.lineTo(width / 2, height + pointerHeight); - context.lineTo((width - pointerWidth) / 2, height); - } - - if (!cornerRadius) { - context.lineTo(0, height); - } else { - context.lineTo(cornerRadius, height); - context.arc( - cornerRadius, - height - cornerRadius, - cornerRadius, - Math.PI / 2, - Math.PI, - false - ); - } - - if (pointerDirection === LEFT) { - context.lineTo(0, (height + pointerHeight) / 2); - context.lineTo((-1) * pointerWidth, height / 2); - context.lineTo(0, (height - pointerHeight) / 2); - } - - if (cornerRadius) { - context.lineTo(0, cornerRadius); - context.arc( - cornerRadius, - cornerRadius, - cornerRadius, - Math.PI, - Math.PI * 3 / 2, - false - ); - } - - context.closePath(); - context.fillStrokeShape(this); - }, - getSelfRect: function() { - var x = 0, - y = 0, - pointerWidth = this.getPointerWidth(), - pointerHeight = this.getPointerHeight(), - direction = this.pointerDirection(), - width = this.getWidth(), - height = this.getHeight(); - - if (direction === UP) { - y -= pointerHeight; - height += pointerHeight; - } else if (direction === DOWN) { - height += pointerHeight; - } else if (direction === LEFT) { - // ARGH!!! I have no idea why should I used magic 1.5!!!!!!!!! - x -= pointerWidth * 1.5; - width += pointerWidth; - } else if (direction === RIGHT) { - width += pointerWidth * 1.5; - } - return { - x: x, - y: y, - width: width, - height: height - }; - } - }; - - Konva.Util.extend(Konva.Tag, Konva.Shape); - Konva.Factory.addGetterSetter(Konva.Tag, 'pointerDirection', NONE); - - /** - * set pointer Direction - * @name setPointerDirection - * @method - * @memberof Konva.Tag.prototype - * @param {String} pointerDirection can be up, right, down, left, or none. The - * default is none - */ - - /** - * get pointer Direction - * @name getPointerDirection - * @method - * @memberof Konva.Tag.prototype - */ - - Konva.Factory.addGetterSetter(Konva.Tag, 'pointerWidth', 0); - - /** - * set pointer width - * @name setPointerWidth - * @method - * @memberof Konva.Tag.prototype - * @param {Number} pointerWidth - */ - - /** - * get pointer width - * @name getPointerWidth - * @method - * @memberof Konva.Tag.prototype - */ - - Konva.Factory.addGetterSetter(Konva.Tag, 'pointerHeight', 0); - - /** - * set pointer height - * @name setPointerHeight - * @method - * @memberof Konva.Tag.prototype - * @param {Number} pointerHeight - */ - - /** - * get pointer height - * @name getPointerHeight - * @method - * @memberof Konva.Tag.prototype - */ - - Konva.Factory.addGetterSetter(Konva.Tag, 'cornerRadius', 0); - - /** - * set corner radius - * @name setCornerRadius - * @method - * @memberof Konva.Tag.prototype - * @param {Number} corner radius - */ - - /** - * get corner radius - * @name getCornerRadius - * @method - * @memberof Konva.Tag.prototype - */ - - Konva.Collection.mapMethods(Konva.Tag); -})(); - -(function() { - 'use strict'; - /** - * Arrow constructor - * @constructor - * @memberof Konva - * @augments Konva.Shape - * @param {Object} config - * @param {Array} config.points - * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. - * The default is 0 - * @param {Number} config.pointerLength - * @param {Number} config.pointerWidth - * @param {String} [config.fill] fill color - * @param {Image} [config.fillPatternImage] fill pattern image - * @param {Number} [config.fillPatternX] - * @param {Number} [config.fillPatternY] - * @param {Object} [config.fillPatternOffset] object with x and y component - * @param {Number} [config.fillPatternOffsetX] - * @param {Number} [config.fillPatternOffsetY] - * @param {Object} [config.fillPatternScale] object with x and y component - * @param {Number} [config.fillPatternScaleX] - * @param {Number} [config.fillPatternScaleY] - * @param {Number} [config.fillPatternRotation] - * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" - * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component - * @param {Number} [config.fillLinearGradientStartPointX] - * @param {Number} [config.fillLinearGradientStartPointY] - * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component - * @param {Number} [config.fillLinearGradientEndPointX] - * @param {Number} [config.fillLinearGradientEndPointY] - * @param {Array} [config.fillLinearGradientColorStops] array of color stops - * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component - * @param {Number} [config.fillRadialGradientStartPointX] - * @param {Number} [config.fillRadialGradientStartPointY] - * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component - * @param {Number} [config.fillRadialGradientEndPointX] - * @param {Number} [config.fillRadialGradientEndPointY] - * @param {Number} [config.fillRadialGradientStartRadius] - * @param {Number} [config.fillRadialGradientEndRadius] - * @param {Array} [config.fillRadialGradientColorStops] array of color stops - * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true - * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration - * @param {String} [config.stroke] stroke color - * @param {Number} [config.strokeWidth] stroke width - * @param {Boolean} [config.strokeHitEnabled] flag which enables or disables stroke hit region. The default is true - * @param {Boolean} [config.perfectDrawEnabled] flag which enables or disables using buffer canvas. The default is true - * @param {Boolean} [config.shadowForStrokeEnabled] flag which enables or disables shasow for stroke. The default is true - * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true - * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true - * @param {String} [config.lineJoin] can be miter, round, or bevel. The default - * is miter - * @param {String} [config.lineCap] can be butt, round, or sqare. The default - * is butt - * @param {String} [config.shadowColor] - * @param {Number} [config.shadowBlur] - * @param {Object} [config.shadowOffset] object with x and y component - * @param {Number} [config.shadowOffsetX] - * @param {Number} [config.shadowOffsetY] - * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number - * between 0 and 1 - * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true - * @param {Array} [config.dash] - * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true - * @param {Number} [config.x] - * @param {Number} [config.y] - * @param {Number} [config.width] - * @param {Number} [config.height] - * @param {Boolean} [config.visible] - * @param {Boolean} [config.listening] whether or not the node is listening for events - * @param {String} [config.id] unique id - * @param {String} [config.name] non-unique name - * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 - * @param {Object} [config.scale] set scale - * @param {Number} [config.scaleX] set scale x - * @param {Number} [config.scaleY] set scale y - * @param {Number} [config.rotation] rotation in degrees - * @param {Object} [config.offset] offset from center point and rotation point - * @param {Number} [config.offsetX] set offset x - * @param {Number} [config.offsetY] set offset y - * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop - * the entire stage by dragging any portion of the stage - * @param {Number} [config.dragDistance] - * @param {Function} [config.dragBoundFunc] - * @example - * var line = new Konva.Line({ - * points: [73, 70, 340, 23, 450, 60, 500, 20], - * stroke: 'red', - * tension: 1, - * pointerLength : 10, - * pointerWidth : 12 - * }); - */ - Konva.Arrow = function(config) { - this.____init(config); - }; - - Konva.Arrow.prototype = { - ____init: function(config) { - // call super constructor - Konva.Line.call(this, config); - this.className = 'Arrow'; - }, - _sceneFunc: function(ctx) { - Konva.Line.prototype._sceneFunc.apply(this, arguments); - var PI2 = Math.PI * 2; - var points = this.points(); - var n = points.length; - var dx = points[n - 2] - points[n - 4]; - var dy = points[n - 1] - points[n - 3]; - var radians = (Math.atan2(dy, dx) + PI2) % PI2; - var length = this.pointerLength(); - var width = this.pointerWidth(); - - ctx.save(); - ctx.beginPath(); - ctx.translate(points[n - 2], points[n - 1]); - ctx.rotate(radians); - ctx.moveTo(0, 0); - ctx.lineTo(-length, width / 2); - ctx.lineTo(-length, (-width) / 2); - ctx.closePath(); - ctx.restore(); - - if (this.pointerAtBeginning()) { - ctx.save(); - ctx.translate(points[0], points[1]); - dx = points[2] - points[0]; - dy = points[3] - points[1]; - ctx.rotate((Math.atan2(-dy, -dx) + PI2) % PI2); - ctx.moveTo(0, 0); - ctx.lineTo(-length, width / 2); - ctx.lineTo(-length, (-width) / 2); - ctx.closePath(); - ctx.restore(); - } - ctx.fillStrokeShape(this); - } - }; - - Konva.Util.extend(Konva.Arrow, Konva.Line); - /** - * get/set pointerLength - * @name pointerLength - * @method - * @memberof Konva.Arrow.prototype - * @param {Number} Length of pointer of arrow. - * The default is 10. - * @returns {Number} - * @example - * // get tension - * var pointerLength = line.pointerLength(); - * - * // set tension - * line.pointerLength(15); - */ - - Konva.Factory.addGetterSetter(Konva.Arrow, 'pointerLength', 10); - /** - * get/set pointerWidth - * @name pointerWidth - * @method - * @memberof Konva.Arrow.prototype - * @param {Number} Width of pointer of arrow. - * The default is 10. - * @returns {Number} - * @example - * // get tension - * var pointerWidth = line.pointerWidth(); - * - * // set tension - * line.pointerWidth(15); - */ - - Konva.Factory.addGetterSetter(Konva.Arrow, 'pointerWidth', 10); - /** - * get/set pointerAtBeginning - * @name pointerAtBeginning - * @method - * @memberof Konva.Arrow.prototype - * @param {Number} Should pointer displayed at beginning of arrow. - * The default is false. - * @returns {Boolean} - * @example - * // get tension - * var pointerAtBeginning = line.pointerAtBeginning(); - * - * // set tension - * line.pointerAtBeginning(true); - */ - - Konva.Factory.addGetterSetter(Konva.Arrow, 'pointerAtBeginning', false); - Konva.Collection.mapMethods(Konva.Arrow); -})();