/*! * Paper.js v0.22 * * This file is part of Paper.js, a JavaScript Vector Graphics Library, * based on Scriptographer.org and designed to be largely API compatible. * http://paperjs.org/ * http://scriptographer.org/ * * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey * http://lehni.org/ & http://jonathanpuckey.com/ * * Distributed under the MIT license. See LICENSE file for details. * * All rights reserved. * * Date: Thu Nov 10 19:19:25 2011 +0100 * *** * * Bootstrap.js JavaScript Framework. * http://bootstrapjs.org/ * * Copyright (c) 2006 - 2011 Juerg Lehni * http://lehni.org/ * * Distributed under the MIT license. * *** * * Parse-js * * A JavaScript tokenizer / parser / generator, originally written in Lisp. * Copyright (c) Marijn Haverbeke * http://marijn.haverbeke.nl/parse-js/ * * Ported by to JavaScript by Mihai Bazon * Copyright (c) 2010, Mihai Bazon * http://mihai.bazon.net/blog/ * * Modifications and adaptions to browser (c) 2011, Juerg Lehni * http://lehni.org/ * * Distributed under the BSD license. */ var paper = new function() { var Base = new function() { var fix = !this.__proto__, hidden = /^(statics|generics|preserve|enumerable|prototype|__proto__|toString|valueOf)$/, proto = Object.prototype, has = fix ? function(name) { return name !== '__proto__' && this.hasOwnProperty(name); } : proto.hasOwnProperty, toString = proto.toString, proto = Array.prototype, isArray = Array.isArray = Array.isArray || function(obj) { return toString.call(obj) === '[object Array]'; }, slice = proto.slice, forEach = proto.forEach = proto.forEach || function(iter, bind) { for (var i = 0, l = this.length; i < l; i++) iter.call(bind, this[i], i, this); }, forIn = function(iter, bind) { for (var i in this) if (this.hasOwnProperty(i)) iter.call(bind, this[i], i, this); }, _define = Object.defineProperty, _describe = Object.getOwnPropertyDescriptor; function define(obj, name, desc) { if (_define) { try { delete obj[name]; return _define(obj, name, desc); } catch (e) {} } if ((desc.get || desc.set) && obj.__defineGetter__) { desc.get && obj.__defineGetter__(name, desc.get); desc.set && obj.__defineSetter__(name, desc.set); } else { obj[name] = desc.value; } return obj; } function describe(obj, name) { if (_describe) { try { return _describe(obj, name); } catch (e) {} } var get = obj.__lookupGetter__ && obj.__lookupGetter__(name); return get ? { get: get, set: obj.__lookupSetter__(name), enumerable: true, configurable: true } : has.call(obj, name) ? { value: obj[name], enumerable: true, configurable: true, writable: true } : null; } function inject(dest, src, enumerable, base, preserve, generics) { var beans, bean; function field(name, val, dontCheck, generics) { var val = val || (val = describe(src, name)) && (val.get ? val : val.value), func = typeof val === 'function', res = val, prev = preserve || func ? (val && val.get ? name in dest : dest[name]) : null; if (generics && func && (!preserve || !generics[name])) { generics[name] = function(bind) { return bind && dest[name].apply(bind, slice.call(arguments, 1)); } } if ((dontCheck || val !== undefined && has.call(src, name)) && (!preserve || !prev)) { if (func) { if (prev && /\bthis\.base\b/.test(val)) { var fromBase = base && base[name] == prev; res = function() { var tmp = describe(this, 'base'); define(this, 'base', { value: fromBase ? base[name] : prev, configurable: true }); try { return val.apply(this, arguments); } finally { tmp ? define(this, 'base', tmp) : delete this.base; } }; res.toString = function() { return val.toString(); } res.valueOf = function() { return val.valueOf(); } } if (beans && val.length == 0 && (bean = name.match(/^(get|is)(([A-Z])(.*))$/))) beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]); } if (!res || func || !res.get && !res.set) res = { value: res, writable: true }; if ((describe(dest, name) || { configurable: true }).configurable) { res.configurable = true; res.enumerable = enumerable; } define(dest, name, res); } } if (src) { beans = []; for (var name in src) if (has.call(src, name) && !hidden.test(name)) field(name, null, true, generics); field('toString'); field('valueOf'); for (var i = 0, l = beans && beans.length; i < l; i++) try { var bean = beans[i], part = bean[1]; field(bean[0], { get: dest['get' + part] || dest['is' + part], set: dest['set' + part] }, true); } catch (e) {} } return dest; } function extend(obj) { var ctor = function(dont) { if (fix) define(this, '__proto__', { value: obj }); if (this.initialize && dont !== ctor.dont) return this.initialize.apply(this, arguments); } ctor.prototype = obj; ctor.toString = function() { return (this.prototype.initialize || function() {}).toString(); } return ctor; } function iterator(iter) { return !iter ? function(val) { return val } : typeof iter !== 'function' ? function(val) { return val == iter } : iter; } function each(obj, iter, bind, asArray) { try { if (obj) (asArray || asArray === undefined && isArray(obj) ? forEach : forIn).call(obj, iterator(iter), bind = bind || obj); } catch (e) { if (e !== Base.stop) throw e; } return bind; } function clone(obj) { return each(obj, function(val, i) { this[i] = val; }, new obj.constructor()); } return inject(function() {}, { inject: function(src) { if (src) { var proto = this.prototype, base = proto.__proto__ && proto.__proto__.constructor, statics = src.statics == true ? src : src.statics; if (statics != src) inject(proto, src, src.enumerable, base && base.prototype, src.preserve, src.generics && this); inject(this, statics, true, base, src.preserve); } for (var i = 1, l = arguments.length; i < l; i++) this.inject(arguments[i]); return this; }, extend: function(src) { var proto = new this(this.dont), ctor = extend(proto); define(proto, 'constructor', { value: ctor, writable: true, configurable: true }); ctor.dont = {}; inject(ctor, this, true); return arguments.length ? this.inject.apply(ctor, arguments) : ctor; } }, true).inject({ has: has, each: each, inject: function() { for (var i = 0, l = arguments.length; i < l; i++) inject(this, arguments[i]); return this; }, extend: function() { var res = new (extend(this)); return res.inject.apply(res, arguments); }, each: function(iter, bind) { return each(this, iter, bind); }, clone: function() { return clone(this); }, statics: { each: each, clone: clone, define: define, describe: describe, iterator: iterator, has: function(obj, name) { return has.call(obj, name); }, type: function(obj) { return (obj || obj === 0) && (obj._type || typeof obj) || null; }, check: function(obj) { return !!(obj || obj === 0); }, pick: function() { for (var i = 0, l = arguments.length; i < l; i++) if (arguments[i] !== undefined) return arguments[i]; return null; }, stop: {} } }); } this.Base = Base.inject({ generics: true, clone: function() { return new this.constructor(this); }, toString: function() { return '{ ' + Base.each(this, function(value, key) { if (key.charAt(0) != '_') { var type = typeof value; this.push(key + ': ' + (type === 'number' ? Base.formatNumber(value) : type === 'string' ? "'" + value + "'" : value)); } }, []).join(', ') + ' }'; }, statics: { read: function(list, start, length) { var start = start || 0, length = length || list.length - start; var obj = list[start]; if (obj instanceof this || this.prototype._readNull && obj == null && length <= 1) return obj; obj = new this(this.dont); return obj.initialize.apply(obj, start > 0 || length < list.length ? Array.prototype.slice.call(list, start, start + length) : list) || obj; }, readAll: function(list, start) { var res = [], entry; for (var i = start || 0, l = list.length; i < l; i++) { res.push(Array.isArray(entry = list[i]) ? this.read(entry, 0) : this.read(list, i, 1)); } return res; }, splice: function(list, items, index, remove) { var amount = items && items.length, append = index === undefined; index = append ? list.length : index; for (var i = 0; i < amount; i++) items[i]._index = index + i; if (append) { list.push.apply(list, items); return []; } else { var args = [index, remove]; if (items) args.push.apply(args, items); var removed = list.splice.apply(list, args); for (var i = 0, l = removed.length; i < l; i++) delete removed[i]._index; for (var i = index + amount, l = list.length; i < l; i++) list[i]._index = i; return removed; } }, merge: function() { return Base.each(arguments, function(hash) { Base.each(hash, function(value, key) { this[key] = value; }, this); }, new Base(), true); }, capitalize: function(str) { return str.replace(/\b[a-z]/g, function(match) { return match.toUpperCase(); }); }, camelize: function(str) { return str.replace(/-(\w)/g, function(all, chr) { return chr.toUpperCase(); }); }, hyphenate: function(str) { return str.replace(/[a-z][A-Z0-9]|[0-9][a-zA-Z]|[A-Z]{2}[a-z]/g, function(match) { return match.charAt(0) + '-' + match.substring(1); } ).toLowerCase(); }, formatNumber: function(num) { return (Math.round(num * 100000) / 100000).toString(); } } }); var PaperScope = this.PaperScope = Base.extend({ initialize: function(script) { paper = this; this.view = null; this.views = []; this.project = null; this.projects = []; this.tool = null; this.tools = []; this._id = script && (script.getAttribute('id') || script.src) || ('paperscope-' + (PaperScope._id++)); if (script) script.setAttribute('id', this._id); PaperScope._scopes[this._id] = this; }, version: 0.22, evaluate: function(code) { var res = PaperScript.evaluate(code, this); View.updateFocus(); return res; }, install: function(scope) { var that = this; Base.each(['project', 'view', 'tool'], function(key) { Base.define(scope, key, { configurable: true, writable: true, get: function() { return that[key]; } }); }); for (var key in this) { if (!/^(version|_id|load)/.test(key) && !(key in scope)) scope[key] = this[key]; } }, setup: function(canvas) { paper = this; this.project = new Project(); if (canvas) this.view = new View(canvas); }, clear: function() { for (var i = this.projects.length - 1; i >= 0; i--) this.projects[i].remove(); for (var i = this.views.length - 1; i >= 0; i--) this.views[i].remove(); for (var i = this.tools.length - 1; i >= 0; i--) this.tools[i].remove(); }, remove: function() { this.clear(); delete PaperScope._scopes[this._id]; }, _needsRedraw: function() { if (!this._redrawNotified) { for (var i = this.views.length - 1; i >= 0; i--) this.views[i]._redrawNeeded = true; this._redrawNotified = true; } }, statics: { _scopes: {}, _id: 0, get: function(id) { if (typeof id === 'object') id = id.getAttribute('id'); return this._scopes[id] || null; }, each: function(iter) { Base.each(this._scopes, iter); } } }); var PaperScopeItem = Base.extend({ initialize: function(activate) { this._scope = paper; this._index = this._scope[this._list].push(this) - 1; if (activate || !this._scope[this._reference]) this.activate(); }, activate: function() { if (!this._scope) return false; this._scope[this._reference] = this; return true; }, remove: function() { if (this._index == null) return false; Base.splice(this._scope[this._list], null, this._index, 1); if (this._scope[this._reference] == this) this._scope[this._reference] = null; this._scope = null; return true; } }); var Point = this.Point = Base.extend({ initialize: function(arg0, arg1) { if (arg1 !== undefined) { this.x = arg0; this.y = arg1; } else if (arg0 !== undefined) { if (arg0 == null) { this.x = this.y = 0; } else if (arg0.x !== undefined) { this.x = arg0.x; this.y = arg0.y; } else if (arg0.width !== undefined) { this.x = arg0.width; this.y = arg0.height; } else if (Array.isArray(arg0)) { this.x = arg0[0]; this.y = arg0.length > 1 ? arg0[1] : arg0[0]; } else if (arg0.angle !== undefined) { this.x = arg0.length; this.y = 0; this.setAngle(arg0.angle); } else if (typeof arg0 === 'number') { this.x = this.y = arg0; } else { this.x = this.y = 0; } } else { this.x = this.y = 0; } }, set: function(x, y) { this.x = x; this.y = y; return this; }, clone: function() { return Point.create(this.x, this.y); }, toString: function() { var format = Base.formatNumber; return '{ x: ' + format(this.x) + ', y: ' + format(this.y) + ' }'; }, add: function(point) { point = Point.read(arguments); return Point.create(this.x + point.x, this.y + point.y); }, subtract: function(point) { point = Point.read(arguments); return Point.create(this.x - point.x, this.y - point.y); }, multiply: function(point) { point = Point.read(arguments); return Point.create(this.x * point.x, this.y * point.y); }, divide: function(point) { point = Point.read(arguments); return Point.create(this.x / point.x, this.y / point.y); }, modulo: function(point) { point = Point.read(arguments); return Point.create(this.x % point.x, this.y % point.y); }, negate: function() { return Point.create(-this.x, -this.y); }, transform: function(matrix) { return matrix ? matrix._transformPoint(this) : this; }, getDistance: function(point, squared) { point = Point.read(arguments); var x = point.x - this.x, y = point.y - this.y, d = x * x + y * y; return squared ? d : Math.sqrt(d); }, getLength: function() { var l = this.x * this.x + this.y * this.y; return arguments[0] ? l : Math.sqrt(l); }, setLength: function(length) { if (this.isZero()) { var angle = this._angle || 0; this.set( Math.cos(angle) * length, Math.sin(angle) * length ); } else { var scale = length / this.getLength(); if (scale == 0) this.getAngle(); this.set( this.x * scale, this.y * scale ); } return this; }, normalize: function(length) { if (length === undefined) length = 1; var current = this.getLength(), scale = current != 0 ? length / current : 0, point = Point.create(this.x * scale, this.y * scale); point._angle = this._angle; return point; }, getAngle: function() { return this.getAngleInRadians(arguments[0]) * 180 / Math.PI; }, setAngle: function(angle) { angle = this._angle = angle * Math.PI / 180; if (!this.isZero()) { var length = this.getLength(); this.set( Math.cos(angle) * length, Math.sin(angle) * length ); } return this; }, getAngleInRadians: function() { if (arguments[0] === undefined) { if (this._angle == null) this._angle = Math.atan2(this.y, this.x); return this._angle; } else { var point = Point.read(arguments), div = this.getLength() * point.getLength(); if (div == 0) { return NaN; } else { return Math.acos(this.dot(point) / div); } } }, getAngleInDegrees: function() { return this.getAngle(arguments[0]); }, getQuadrant: function() { return this.x >= 0 ? this.y >= 0 ? 1 : 4 : this.y >= 0 ? 2 : 3; }, getDirectedAngle: function(point) { point = Point.read(arguments); return Math.atan2(this.cross(point), this.dot(point)) * 180 / Math.PI; }, rotate: function(angle, center) { angle = angle * Math.PI / 180; var point = center ? this.subtract(center) : this, s = Math.sin(angle), c = Math.cos(angle); point = Point.create( point.x * c - point.y * s, point.y * c + point.x * s ); return center ? point.add(center) : point; }, equals: function(point) { point = Point.read(arguments); return this.x == point.x && this.y == point.y; }, isInside: function(rect) { return rect.contains(this); }, isClose: function(point, tolerance) { return this.getDistance(point) < tolerance; }, isColinear: function(point) { return this.cross(point) < Numerical.TOLERANCE; }, isOrthogonal: function(point) { return this.dot(point) < Numerical.TOLERANCE; }, isZero: function() { return this.x == 0 && this.y == 0; }, isNaN: function() { return isNaN(this.x) || isNaN(this.y); }, dot: function(point) { point = Point.read(arguments); return this.x * point.x + this.y * point.y; }, cross: function(point) { point = Point.read(arguments); return this.x * point.y - this.y * point.x; }, project: function(point) { point = Point.read(arguments); if (point.isZero()) { return Point.create(0, 0); } else { var scale = this.dot(point) / point.dot(point); return Point.create( point.x * scale, point.y * scale ); } }, statics: { create: function(x, y) { var point = new Point(Point.dont); point.x = x; point.y = y; return point; }, min: function(point1, point2) { point1 = Point.read(arguments, 0, 1); point2 = Point.read(arguments, 1, 1); return Point.create( Math.min(point1.x, point2.x), Math.min(point1.y, point2.y) ); }, max: function(point1, point2) { point1 = Point.read(arguments, 0, 1); point2 = Point.read(arguments, 1, 1); return Point.create( Math.max(point1.x, point2.x), Math.max(point1.y, point2.y) ); }, random: function() { return Point.create(Math.random(), Math.random()); } } }, new function() { return Base.each(['round', 'ceil', 'floor', 'abs'], function(name) { var op = Math[name]; this[name] = function() { return Point.create(op(this.x), op(this.y)); }; }, {}); }); var LinkedPoint = Point.extend({ set: function(x, y, dontNotify) { this._x = x; this._y = y; if (!dontNotify) this._owner[this._setter](this); return this; }, getX: function() { return this._x; }, setX: function(x) { this._x = x; this._owner[this._setter](this); }, getY: function() { return this._y; }, setY: function(y) { this._y = y; this._owner[this._setter](this); }, statics: { create: function(owner, setter, x, y, dontLink) { if (dontLink) return Point.create(x, y); var point = new LinkedPoint(LinkedPoint.dont); point._x = x; point._y = y; point._owner = owner; point._setter = setter; return point; } } }); var Size = this.Size = Base.extend({ initialize: function(arg0, arg1) { if (arg1 !== undefined) { this.width = arg0; this.height = arg1; } else if (arg0 !== undefined) { if (arg0 == null) { this.width = this.height = 0; } else if (arg0.width !== undefined) { this.width = arg0.width; this.height = arg0.height; } else if (arg0.x !== undefined) { this.width = arg0.x; this.height = arg0.y; } else if (Array.isArray(arg0)) { this.width = arg0[0]; this.height = arg0.length > 1 ? arg0[1] : arg0[0]; } else if (typeof arg0 === 'number') { this.width = this.height = arg0; } else { this.width = this.height = 0; } } else { this.width = this.height = 0; } }, toString: function() { var format = Base.formatNumber; return '{ width: ' + format(this.width) + ', height: ' + format(this.height) + ' }'; }, set: function(width, height) { this.width = width; this.height = height; return this; }, clone: function() { return Size.create(this.width, this.height); }, add: function(size) { size = Size.read(arguments); return Size.create(this.width + size.width, this.height + size.height); }, subtract: function(size) { size = Size.read(arguments); return Size.create(this.width - size.width, this.height - size.height); }, multiply: function(size) { size = Size.read(arguments); return Size.create(this.width * size.width, this.height * size.height); }, divide: function(size) { size = Size.read(arguments); return Size.create(this.width / size.width, this.height / size.height); }, modulo: function(size) { size = Size.read(arguments); return Size.create(this.width % size.width, this.height % size.height); }, negate: function() { return Size.create(-this.width, -this.height); }, equals: function(size) { size = Size.read(arguments); return this.width == size.width && this.height == size.height; }, isZero: function() { return this.width == 0 && this.height == 0; }, isNaN: function() { return isNaN(this.width) || isNaN(this.height); }, statics: { create: function(width, height) { return new Size(Size.dont).set(width, height); }, min: function(size1, size2) { return Size.create( Math.min(size1.width, size2.width), Math.min(size1.height, size2.height)); }, max: function(size1, size2) { return Size.create( Math.max(size1.width, size2.width), Math.max(size1.height, size2.height)); }, random: function() { return Size.create(Math.random(), Math.random()); } } }, new function() { return Base.each(['round', 'ceil', 'floor', 'abs'], function(name) { var op = Math[name]; this[name] = function() { return Size.create(op(this.width), op(this.height)); }; }, {}); }); var LinkedSize = Size.extend({ set: function(width, height, dontNotify) { this._width = width; this._height = height; if (!dontNotify) this._owner[this._setter](this); return this; }, getWidth: function() { return this._width; }, setWidth: function(width) { this._width = width; this._owner[this._setter](this); }, getHeight: function() { return this._height; }, setHeight: function(height) { this._height = height; this._owner[this._setter](this); }, statics: { create: function(owner, setter, width, height, dontLink) { if (dontLink) return Size.create(width, height); var size = new LinkedSize(LinkedSize.dont); size._width = width; size._height = height; size._owner = owner; size._setter = setter; return size; } } }); var Rectangle = this.Rectangle = Base.extend({ initialize: function(arg0, arg1, arg2, arg3) { if (arguments.length == 4) { this.x = arg0; this.y = arg1; this.width = arg2; this.height = arg3; } else if (arguments.length == 2) { if (arg1 && arg1.x !== undefined) { var point1 = Point.read(arguments, 0, 1); var point2 = Point.read(arguments, 1, 1); this.x = point1.x; this.y = point1.y; this.width = point2.x - point1.x; this.height = point2.y - point1.y; if (this.width < 0) { this.x = point2.x; this.width = -this.width; } if (this.height < 0) { this.y = point2.y; this.height = -this.height; } } else { var point = Point.read(arguments, 0, 1); var size = Size.read(arguments, 1, 1); this.x = point.x; this.y = point.y; this.width = size.width; this.height = size.height; } } else if (arg0) { this.x = arg0.x || 0; this.y = arg0.y || 0; this.width = arg0.width || 0; this.height = arg0.height || 0; } else { this.x = this.y = this.width = this.height = 0; } }, set: function(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; return this; }, getPoint: function() { return LinkedPoint.create(this, 'setPoint', this.x, this.y, arguments[0]); }, setPoint: function(point) { point = Point.read(arguments); this.x = point.x; this.y = point.y; return this; }, getSize: function() { return LinkedSize.create(this, 'setSize', this.width, this.height, arguments[0]); }, setSize: function(size) { size = Size.read(arguments); this.width = size.width; this.height = size.height; return this; }, getLeft: function() { return this.x; }, setLeft: function(left) { this.width -= left - this.x; this.x = left; return this; }, getTop: function() { return this.y; }, setTop: function(top) { this.height -= top - this.y; this.y = top; return this; }, getRight: function() { return this.x + this.width; }, setRight: function(right) { this.width = right - this.x; return this; }, getBottom: function() { return this.y + this.height; }, setBottom: function(bottom) { this.height = bottom - this.y; return this; }, getCenterX: function() { return this.x + this.width * 0.5; }, setCenterX: function(x) { this.x = x - this.width * 0.5; return this; }, getCenterY: function() { return this.y + this.height * 0.5; }, setCenterY: function(y) { this.y = y - this.height * 0.5; return this; }, getCenter: function() { return LinkedPoint.create(this, 'setCenter', this.getCenterX(), this.getCenterY(), arguments[0]); }, setCenter: function(point) { point = Point.read(arguments); return this.setCenterX(point.x).setCenterY(point.y); }, equals: function(rect) { rect = Rectangle.read(arguments); return this.x == rect.x && this.y == rect.y && this.width == rect.width && this.height == rect.height; }, isEmpty: function() { return this.width == 0 || this.height == 0; }, toString: function() { var format = Base.formatNumber; return '{ x: ' + format(this.x) + ', y: ' + format(this.y) + ', width: ' + format(this.width) + ', height: ' + format(this.height) + ' }'; }, contains: function(arg) { return arg && arg.width !== undefined || (Array.isArray(arg) ? arg : arguments).length == 4 ? this._containsRectangle(Rectangle.read(arguments)) : this._containsPoint(Point.read(arguments)); }, _containsPoint: function(point) { var x = point.x, y = point.y; return x >= this.x && y >= this.y && x <= this.x + this.width && y <= this.y + this.height; }, _containsRectangle: function(rect) { var x = rect.x, y = rect.y; return x >= this.x && y >= this.y && x + rect.width <= this.x + this.width && y + rect.height <= this.y + this.height; }, intersects: function(rect) { rect = Rectangle.read(arguments); return rect.x + rect.width > this.x && rect.y + rect.height > this.y && rect.x < this.x + this.width && rect.y < this.y + this.height; }, intersect: function(rect) { rect = Rectangle.read(arguments); var x1 = Math.max(this.x, rect.x), y1 = Math.max(this.y, rect.y), x2 = Math.min(this.x + this.width, rect.x + rect.width), y2 = Math.min(this.y + this.height, rect.y + rect.height); return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, unite: function(rect) { rect = Rectangle.read(arguments); var x1 = Math.min(this.x, rect.x), y1 = Math.min(this.y, rect.y), x2 = Math.max(this.x + this.width, rect.x + rect.width), y2 = Math.max(this.y + this.height, rect.y + rect.height); return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, include: function(point) { point = Point.read(arguments); var x1 = Math.min(this.x, point.x), y1 = Math.min(this.y, point.y), x2 = Math.max(this.x + this.width, point.x), y2 = Math.max(this.y + this.height, point.y); return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, expand: function(hor, ver) { if (ver === undefined) ver = hor; return Rectangle.create(this.x - hor / 2, this.y - ver / 2, this.width + hor, this.height + ver); }, scale: function(hor, ver) { return this.expand(this.width * hor - this.width, this.height * (ver === undefined ? hor : ver) - this.height); }, statics: { create: function(x, y, width, height) { return new Rectangle(Rectangle.dont).set(x, y, width, height); } } }, new function() { return Base.each([ ['Top', 'Left'], ['Top', 'Right'], ['Bottom', 'Left'], ['Bottom', 'Right'], ['Left', 'Center'], ['Top', 'Center'], ['Right', 'Center'], ['Bottom', 'Center'] ], function(parts, index) { var part = parts.join(''); var xFirst = /^[RL]/.test(part); if (index >= 4) parts[1] += xFirst ? 'Y' : 'X'; var x = parts[xFirst ? 0 : 1], y = parts[xFirst ? 1 : 0], getX = 'get' + x, getY = 'get' + y, setX = 'set' + x, setY = 'set' + y, get = 'get' + part, set = 'set' + part; this[get] = function() { return LinkedPoint.create(this, set, this[getX](), this[getY](), arguments[0]); }; this[set] = function(point) { point = Point.read(arguments); return this[setX](point.x)[setY](point.y); }; }, {}); }); var LinkedRectangle = Rectangle.extend({ set: function(x, y, width, height, dontNotify) { this._x = x; this._y = y; this._width = width; this._height = height; if (!dontNotify) this._owner[this._setter](this); return this; }, statics: { create: function(owner, setter, x, y, width, height) { var rect = new LinkedRectangle(LinkedRectangle.dont).set( x, y, width, height, true); rect._owner = owner; rect._setter = setter; return rect; } } }, new function() { var proto = Rectangle.prototype; return Base.each(['x', 'y', 'width', 'height'], function(key) { var part = Base.capitalize(key); var internal = '_' + key; this['get' + part] = function() { return this[internal]; }; this['set' + part] = function(value) { this[internal] = value; if (!this._dontNotify) this._owner[this._setter](this); }; }, Base.each(['Point', 'Size', 'Center', 'Left', 'Top', 'Right', 'Bottom', 'CenterX', 'CenterY', 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'], function(key) { var name = 'set' + key; this[name] = function(value) { this._dontNotify = true; proto[name].apply(this, arguments); delete this._dontNotify; this._owner[this._setter](this); return this; }; }, {}) ); }); var Matrix = this.Matrix = Base.extend({ initialize: function(arg) { var count = arguments.length, ok = true; if (count == 6) { this.set.apply(this, arguments); } else if (count == 1) { if (arg instanceof Matrix) { this.set(arg._a, arg._c, arg._b, arg._d, arg._tx, arg._ty); } else if (Array.isArray(arg)) { this.set.apply(this, arg); } else { ok = false; } } else if (count == 0) { this._a = this._d = 1; this._c = this._b = this._tx = this._ty = 0; } else { ok = false; } if (!ok) throw new Error('Unsupported matrix parameters'); }, clone: function() { return Matrix.create(this._a, this._c, this._b, this._d, this._tx, this._ty); }, set: function(a, c, b, d, tx, ty) { this._a = a; this._c = c; this._b = b; this._d = d; this._tx = tx; this._ty = ty; return this; }, scale: function( hor, ver, center) { if (arguments.length < 2 || typeof ver === 'object') { center = Point.read(arguments, 1); ver = hor; } else { center = Point.read(arguments, 2); } if (center) this.translate(center); this._a *= hor; this._c *= hor; this._b *= ver; this._d *= ver; if (center) this.translate(center.negate()); return this; }, translate: function(point) { point = Point.read(arguments); var x = point.x, y = point.y; this._tx += x * this._a + y * this._b; this._ty += x * this._c + y * this._d; return this; }, rotate: function(angle, center) { return this.concatenate( Matrix.getRotateInstance.apply(Matrix, arguments)); }, shear: function( hor, ver, center) { if (arguments.length < 2 || typeof ver === 'object') { center = Point.read(arguments, 1); ver = hor; } else { center = Point.read(arguments, 2); } if (center) this.translate(center); var a = this._a, c = this._c; this._a += ver * this._b; this._c += ver * this._d; this._b += hor * a; this._d += hor * c; if (center) this.translate(center.negate()); return this; }, toString: function() { var format = Base.formatNumber; return '[[' + [format(this._a), format(this._b), format(this._tx)].join(', ') + '], [' + [format(this._c), format(this._d), format(this._ty)].join(', ') + ']]'; }, getValues: function() { return [ this._a, this._c, this._b, this._d, this._tx, this._ty ]; }, concatenate: function(mx) { var a = this._a, b = this._b, c = this._c, d = this._d; this._a = mx._a * a + mx._c * b; this._b = mx._b * a + mx._d * b; this._tx += mx._tx * a + mx._ty * b; this._c = mx._a * c + mx._c * d; this._d = mx._b * c + mx._d * d; this._ty += mx._tx * c + mx._ty * d; return this; }, preConcatenate: function(mx) { var a = this._a, b = this._b, c = this._c, d = this._d, tx = this._tx, ty = this._ty; this._a = mx._a * a + mx._b * c; this._c = mx._c * a + mx._d * c; this._b = mx._a * b + mx._b * d; this._d = mx._c * b + mx._d * d; this._tx = mx._a * tx + mx._b * ty + mx._tx; this._ty = mx._c * tx + mx._d * ty + mx._ty; return this; }, transform: function( src, srcOff, dst, dstOff, numPts) { return arguments.length < 5 ? this._transformPoint(Point.read(arguments)) : this._transformCoordinates(src, srcOff, dst, dstOff, numPts); }, _transformPoint: function(point, dest, dontNotify) { var x = point.x, y = point.y; if (!dest) dest = new Point(Point.dont); return dest.set( x * this._a + y * this._b + this._tx, x * this._c + y * this._d + this._ty, dontNotify ); }, _transformCoordinates: function(src, srcOff, dst, dstOff, numPts) { var i = srcOff, j = dstOff, srcEnd = srcOff + 2 * numPts; while (i < srcEnd) { var x = src[i++]; var y = src[i++]; dst[j++] = x * this._a + y * this._b + this._tx; dst[j++] = x * this._c + y * this._d + this._ty; } return dst; }, _transformCorners: function(rect) { var x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height, coords = [ x1, y1, x2, y1, x2, y2, x1, y2 ]; return this._transformCoordinates(coords, 0, coords, 0, 4); }, _transformBounds: function(bounds) { var coords = this._transformCorners(bounds), min = coords.slice(0, 2), max = coords.slice(0); for (var i = 2; i < 8; i++) { var val = coords[i], j = i & 1; if (val < min[j]) min[j] = val; else if (val > max[j]) max[j] = val; } return Rectangle.create(min[0], min[1], max[0] - min[0], max[1] - min[1]); }, inverseTransform: function(point) { return this._inverseTransform(Point.read(arguments)); }, _getDeterminant: function() { var det = this._a * this._d - this._b * this._c; return isFinite(det) && Math.abs(det) > Numerical.EPSILON && isFinite(this._tx) && isFinite(this._ty) ? det : null; }, _inverseTransform: function(point, dest, dontNotify) { var det = this._getDeterminant(); if (!det) return null; var x = point.x - this._tx, y = point.y - this._ty; if (!dest) dest = new Point(Point.dont); return dest.set( (x * this._d - y * this._b) / det, (y * this._a - x * this._c) / det, dontNotify ); }, getTranslation: function() { return new Point(this._tx, this._ty); }, getScaling: function() { var hor = Math.sqrt(this._a * this._a + this._c * this._c), ver = Math.sqrt(this._b * this._b + this._d * this._d); return new Point(this._a < 0 ? -hor : hor, this._b < 0 ? -ver : ver); }, getRotation: function() { var angle1 = -Math.atan2(this._b, this._d), angle2 = Math.atan2(this._c, this._a); return Math.abs(angle1 - angle2) < Numerical.TOLERANCE ? angle1 * 180 / Math.PI : undefined; }, isIdentity: function() { return this._a == 1 && this._c == 0 && this._b == 0 && this._d == 1 && this._tx == 0 && this._ty == 0; }, isInvertible: function() { return !!this._getDeterminant(); }, isSingular: function() { return !this._getDeterminant(); }, createInverse: function() { var det = this._getDeterminant(); return det && Matrix.create( this._d / det, -this._c / det, -this._b / det, this._a / det, (this._b * this._ty - this._d * this._tx) / det, (this._c * this._tx - this._a * this._ty) / det); }, createShiftless: function() { return Matrix.create(this._a, this._c, this._b, this._d, 0, 0); }, setToScale: function(hor, ver) { return this.set(hor, 0, 0, ver, 0, 0); }, setToTranslation: function(delta) { delta = Point.read(arguments); return this.set(1, 0, 0, 1, delta.x, delta.y); }, setToShear: function(hor, ver) { return this.set(1, ver, hor, 1, 0, 0); }, setToRotation: function(angle, center) { center = Point.read(arguments, 1); angle = angle * Math.PI / 180; var x = center.x, y = center.y, cos = Math.cos(angle), sin = Math.sin(angle); return this.set(cos, sin, -sin, cos, x - x * cos + y * sin, y - x * sin - y * cos); }, applyToContext: function(ctx, reset) { ctx[reset ? 'setTransform' : 'transform']( this._a, this._c, this._b, this._d, this._tx, this._ty); return this; }, statics: { create: function(a, c, b, d, tx, ty) { return new Matrix(Matrix.dont).set(a, c, b, d, tx, ty); }, getScaleInstance: function(hor, ver) { var mx = new Matrix(); return mx.setToScale.apply(mx, arguments); }, getTranslateInstance: function(delta) { var mx = new Matrix(); return mx.setToTranslation.apply(mx, arguments); }, getShearInstance: function(hor, ver, center) { var mx = new Matrix(); return mx.setToShear.apply(mx, arguments); }, getRotateInstance: function(angle, center) { var mx = new Matrix(); return mx.setToRotation.apply(mx, arguments); } } }, new function() { return Base.each({ scaleX: '_a', scaleY: '_d', translateX: '_tx', translateY: '_ty', shearX: '_b', shearY: '_c' }, function(prop, name) { name = Base.capitalize(name); this['get' + name] = function() { return this[prop]; }; this['set' + name] = function(value) { this[prop] = value; }; }, {}); }); var Line = this.Line = Base.extend({ initialize: function(point1, point2, infinite) { point1 = Point.read(arguments, 0, 1); point2 = Point.read(arguments, 1, 1); if (arguments.length == 3) { this.point = point1; this.vector = point2.subtract(point1); this.infinite = infinite; } else { this.point = point1; this.vector = point2; this.infinite = true; } }, intersect: function(line) { var cross = this.vector.cross(line.vector); if (Math.abs(cross) <= Numerical.EPSILON) return null; var v = line.point.subtract(this.point), t1 = v.cross(line.vector) / cross, t2 = v.cross(this.vector) / cross; return (this.infinite || 0 <= t1 && t1 <= 1) && (line.infinite || 0 <= t2 && t2 <= 1) ? this.point.add(this.vector.multiply(t1)) : null; }, getSide: function(point) { var v1 = this.vector, v2 = point.subtract(this.point), ccw = v2.cross(v1); if (ccw == 0) { ccw = v2.dot(v1); if (ccw > 0) { ccw = v2.subtract(v1).dot(v1); if (ccw < 0) ccw = 0; } } return ccw < 0 ? -1 : ccw > 0 ? 1 : 0; }, getDistance: function(point) { var m = this.vector.y / this.vector.x, b = this.point.y - (m * this.point.x); var dist = Math.abs(point.y - (m * point.x) - b) / Math.sqrt(m * m + 1); return this.infinite ? dist : Math.min(dist, point.getDistance(this.point), point.getDistance(this.point.add(this.vector))); } }); var Project = this.Project = PaperScopeItem.extend({ _list: 'projects', _reference: 'project', initialize: function() { this.base(true); this._currentStyle = new PathStyle(); this._selectedItems = {}; this._selectedItemCount = 0; this.layers = []; this.symbols = []; this.activeLayer = new Layer(); }, _needsRedraw: function() { if (this._scope) this._scope._needsRedraw(); }, getCurrentStyle: function() { return this._currentStyle; }, setCurrentStyle: function(style) { this._currentStyle.initialize(style); }, getIndex: function() { return this._index; }, getSelectedItems: function() { var items = []; Base.each(this._selectedItems, function(item) { items.push(item); }); return items; }, _updateSelection: function(item) { if (item._selected) { this._selectedItemCount++; this._selectedItems[item.getId()] = item; } else { this._selectedItemCount--; delete this._selectedItems[item.getId()]; } }, selectAll: function() { for (var i = 0, l = this.layers.length; i < l; i++) this.layers[i].setSelected(true); }, deselectAll: function() { for (var i in this._selectedItems) this._selectedItems[i].setSelected(false); }, hitTest: function(point, options) { options = HitResult.getOptions(point, options); point = options.point; for (var i = this.layers.length - 1; i >= 0; i--) { var res = this.layers[i].hitTest(point, options); if (res) return res; } return null; }, draw: function(ctx) { ctx.save(); var param = { offset: new Point(0, 0) }; for (var i = 0, l = this.layers.length; i < l; i++) Item.draw(this.layers[i], ctx, param); ctx.restore(); if (this._selectedItemCount > 0) { ctx.save(); ctx.strokeWidth = 1; ctx.strokeStyle = ctx.fillStyle = '#009dec'; param = { selection: true }; Base.each(this._selectedItems, function(item) { item.draw(ctx, param); }); ctx.restore(); } } }); var Symbol = this.Symbol = Base.extend({ initialize: function(item) { this.project = paper.project; this.project.symbols.push(this); this.setDefinition(item); this._instances = {}; }, _changed: function(flags) { Base.each(this._instances, function(item) { item._changed(flags); }); }, getDefinition: function() { return this._definition; }, setDefinition: function(item) { if (item._parentSymbol) item = item.clone(); if (this._definition) delete this._definition._parentSymbol; this._definition = item; item.remove(); item.setPosition(new Point()); item._parentSymbol = this; this._changed(Change.GEOMETRY); }, place: function(position) { return new PlacedSymbol(this, position); }, clone: function() { return new Symbol(this._definition.clone()); } }); var ChangeFlag = { APPEARANCE: 1, HIERARCHY: 2, GEOMETRY: 4, STROKE: 8, STYLE: 16, ATTRIBUTE: 32, CONTENT: 64, PIXELS: 128, CLIPPING: 256 }; var Change = { HIERARCHY: ChangeFlag.HIERARCHY | ChangeFlag.APPEARANCE, GEOMETRY: ChangeFlag.GEOMETRY | ChangeFlag.APPEARANCE, STROKE: ChangeFlag.STROKE | ChangeFlag.STYLE | ChangeFlag.APPEARANCE, STYLE: ChangeFlag.STYLE | ChangeFlag.APPEARANCE, ATTRIBUTE: ChangeFlag.ATTRIBUTE | ChangeFlag.APPEARANCE, CONTENT: ChangeFlag.CONTENT | ChangeFlag.APPEARANCE, PIXELS: ChangeFlag.PIXELS | ChangeFlag.APPEARANCE }; var Item = this.Item = Base.extend({ initialize: function() { this._id = ++Item._id; if (!this._project) paper.project.activeLayer.addChild(this); this._style = PathStyle.create(this); this.setStyle(this._project.getCurrentStyle()); }, _changed: function(flags) { if (flags & ChangeFlag.GEOMETRY) { delete this._bounds; delete this._position; } if (flags & ChangeFlag.APPEARANCE) { this._project._needsRedraw(); } if (this._parentSymbol) this._parentSymbol._changed(flags); if (this._project._changes) { var entry = this._project._changesById[this._id]; if (entry) { entry.flags |= flags; } else { entry = { item: this, flags: flags }; this._project._changesById[this._id] = entry; this._project._changes.push(entry); } } }, getId: function() { return this._id; }, getName: function() { return this._name; }, setName: function(name) { if (this._name) this._removeFromNamed(); this._name = name || undefined; if (name) { var children = this._parent._children, namedChildren = this._parent._namedChildren; (namedChildren[name] = namedChildren[name] || []).push(this); children[name] = this; } this._changed(ChangeFlag.ATTRIBUTE); }, getPosition: function() { var pos = this._position || (this._position = this.getBounds().getCenter()); return LinkedPoint.create(this, 'setPosition', pos._x, pos._y); }, setPosition: function(point) { this.translate(Point.read(arguments).subtract(this.getPosition())); }, getStyle: function() { return this._style; }, setStyle: function(style) { this._style.initialize(style); }, statics: { _id: 0 } }, new function() { return Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'], function(name) { var part = Base.capitalize(name), name = '_' + name; this['get' + part] = function() { return this[name]; }; this['set' + part] = function(value) { if (value != this[name]) { this[name] = value; this._changed(name === '_locked' ? ChangeFlag.ATTRIBUTE : Change.ATTRIBUTE); } }; }, {}); }, { _locked: false, _visible: true, _blendMode: 'normal', _opacity: 1, _guide: false, isSelected: function() { if (this._children) { for (var i = 0, l = this._children.length; i < l; i++) if (this._children[i].isSelected()) return true; } return this._selected; }, setSelected: function(selected) { if (this._children) { for (var i = 0, l = this._children.length; i < l; i++) { this._children[i].setSelected(selected); } } else if ((selected = !!selected) != this._selected) { this._selected = selected; this._project._updateSelection(this); this._changed(Change.ATTRIBUTE); } }, _selected: false, isFullySelected: function() { if (this._children && this._selected) { for (var i = 0, l = this._children.length; i < l; i++) if (!this._children[i].isFullySelected()) return false; return true; } return this._selected; }, setFullySelected: function(selected) { if (this._children) { for (var i = 0, l = this._children.length; i < l; i++) { this._children[i].setFullySelected(selected); } } this.setSelected(selected); }, isClipMask: function() { return this._clipMask; }, setClipMask: function(clipMask) { if (this._clipMask != (clipMask = !!clipMask)) { this._clipMask = clipMask; if (clipMask) { this.setFillColor(null); this.setStrokeColor(null); } this._changed(Change.ATTRIBUTE); if (this._parent) this._parent._changed(ChangeFlag.CLIPPING); } }, _clipMask: false, getProject: function() { return this._project; }, _setProject: function(project) { if (this._project != project) { this._project = project; if (this._children) { for (var i = 0, l = this._children.length; i < l; i++) { this._children[i]._setProject(project); } } } }, getLayer: function() { var parent = this; while (parent = parent._parent) { if (parent instanceof Layer) return parent; } return null; }, getParent: function() { return this._parent; }, getChildren: function() { return this._children; }, setChildren: function(items) { this.removeChildren(); this.addChildren(items); }, getFirstChild: function() { return this._children && this._children[0] || null; }, getLastChild: function() { return this._children && this._children[this._children.length - 1] || null; }, getNextSibling: function() { return this._parent && this._parent._children[this._index + 1] || null; }, getPreviousSibling: function() { return this._parent && this._parent._children[this._index - 1] || null; }, getIndex: function() { return this._index; }, clone: function() { return this._clone(new this.constructor()); }, _clone: function(copy) { copy.setStyle(this._style); if (this._children) { for (var i = 0, l = this._children.length; i < l; i++) copy.addChild(this._children[i].clone()); } var keys = ['_locked', '_visible', '_blendMode', '_opacity', '_clipMask', '_guide']; for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; if (this.hasOwnProperty(key)) copy[key] = this[key]; } copy.setSelected(this._selected); if (this._name) copy.setName(this._name); return copy; }, copyTo: function(itemOrProject) { var copy = this.clone(); if (itemOrProject.layers) { itemOrProject.activeLayer.addChild(copy); } else { itemOrProject.addChild(copy); } return copy; }, rasterize: function(resolution) { var bounds = this.getStrokeBounds(), scale = (resolution || 72) / 72, canvas = CanvasProvider.getCanvas(bounds.getSize().multiply(scale)), ctx = canvas.getContext('2d'), matrix = new Matrix().scale(scale).translate(-bounds.x, -bounds.y); matrix.applyToContext(ctx); this.draw(ctx, {}); var raster = new Raster(canvas); raster.setBounds(bounds); return raster; }, hitTest: function(point, options, matrix) { options = HitResult.getOptions(point, options); point = options.point; if (!this._children && !this.getRoughBounds(matrix) .expand(options.tolerance)._containsPoint(point)) return null; if ((options.center || options.bounds) && !(this instanceof Layer && !this._parent)) { var bounds = this.getBounds(), that = this, points = ['TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'], res; function checkBounds(type, part) { var pt = bounds['get' + part]().transform(matrix); if (point.getDistance(pt) < options.tolerance) return new HitResult(type, that, { name: Base.hyphenate(part), point: pt }); } if (options.center && (res = checkBounds('center', 'Center'))) return res; if (options.bounds) { for (var i = 0; i < 8; i++) if (res = checkBounds('bounds', points[i])) return res; } } return this._children || !(options.guides && !this._guide || options.selected && !this._selected) ? this._hitTest(point, options, matrix) : null; }, _hitTest: function(point, options, matrix) { if (this._children) { for (var i = this._children.length - 1; i >= 0; i--) { var res = this._children[i].hitTest(point, options, matrix); if (res) return res; } } }, addChild: function(item) { return this.insertChild(undefined, item); }, insertChild: function(index, item) { if (this._children) { item._remove(false, true); Base.splice(this._children, [item], index, 0); item._parent = this; item._setProject(this._project); if (item._name) item.setName(item._name); this._changed(Change.HIERARCHY); return true; } return false; }, addChildren: function(items) { for (var i = 0, l = items && items.length; i < l; i++) this.insertChild(undefined, items[i]); }, insertChildren: function(index, items) { for (var i = 0, l = items && items.length; i < l; i++) { if (this.insertChild(index, items[i])) index++; } }, insertAbove: function(item) { return item._parent && item._parent.insertChild( item._index + 1, this); }, insertBelow: function(item) { return item._parent && item._parent.insertChild( item._index - 1, this); }, appendTop: function(item) { return this.addChild(item); }, appendBottom: function(item) { return this.insertChild(0, item); }, moveAbove: function(item) { return this.insertAbove(item); }, moveBelow: function(item) { return this.insertBelow(item); }, _removeFromNamed: function() { var children = this._parent._children, namedChildren = this._parent._namedChildren, name = this._name, namedArray = namedChildren[name], index = namedArray ? namedArray.indexOf(this) : -1; if (index == -1) return; if (children[name] == this) delete children[name]; namedArray.splice(index, 1); if (namedArray.length) { children[name] = namedArray[namedArray.length - 1]; } else { delete namedChildren[name]; } }, _remove: function(deselect, notify) { if (this._parent) { if (deselect) this.setSelected(false); if (this._name) this._removeFromNamed(); Base.splice(this._parent._children, null, this._index, 1); if (notify) this._parent._changed(Change.HIERARCHY); this._parent = null; return true; } return false; }, remove: function() { return this._remove(true, true); }, removeChildren: function(from, to) { if (!this._children) return null; from = from || 0; to = Base.pick(to, this._children.length); var removed = this._children.splice(from, to - from); for (var i = removed.length - 1; i >= 0; i--) removed[i]._remove(true, false); if (removed.length > 0) this._changed(Change.HIERARCHY); return removed; }, reverseChildren: function() { if (this._children) { this._children.reverse(); for (var i = 0, l = this._children.length; i < l; i++) this._children[i]._index = i; this._changed(Change.HIERARCHY); } }, isEditable: function() { var item = this; while (item) { if (!item._visible || item._locked) return false; item = item._parent; } return true; }, _getOrder: function(item) { function getList(item) { var list = []; do { list.unshift(item); } while (item = item._parent) return list; } var list1 = getList(this), list2 = getList(item); for (var i = 0, l = Math.min(list1.length, list2.length); i < l; i++) { if (list1[i] != list2[i]) { return list1[i]._index < list2[i]._index ? 1 : -1; } } return 0; }, hasChildren: function() { return this._children && this._children.length > 0; }, isAbove: function(item) { return this._getOrder(item) == -1; }, isBelow: function(item) { return this._getOrder(item) == 1; }, isParent: function(item) { return this._parent == item; }, isChild: function(item) { return item && item._parent == this; }, isDescendant: function(item) { var parent = this; while (parent = parent._parent) { if (parent == item) return true; } return false; }, isAncestor: function(item) { return item ? item.isDescendant(this) : false; }, isGroupedWith: function(item) { var parent = this._parent; while (parent) { if (parent._parent && (parent instanceof Group || parent instanceof CompoundPath) && item.isDescendant(parent)) return true; parent = parent._parent; } return false; }, _getBounds: function(getter, cacheName, args) { var children = this._children; if (!children || children.length == 0) return new Rectangle(); var x1 = Infinity, x2 = -x1, y1 = x1, y2 = x2; for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; if (child._visible) { var rect = child[getter](args[0]); x1 = Math.min(rect.x, x1); y1 = Math.min(rect.y, y1); x2 = Math.max(rect.x + rect.width, x2); y2 = Math.max(rect.y + rect.height, y2); } } var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1); return getter == 'getBounds' ? this._createBounds(bounds) : bounds; }, _createBounds: function(rect) { return LinkedRectangle.create(this, 'setBounds', rect.x, rect.y, rect.width, rect.height); }, getBounds: function() { return this._getBounds('getBounds', '_bounds', arguments); }, setBounds: function(rect) { rect = Rectangle.read(arguments); var bounds = this.getBounds(), matrix = new Matrix(), center = rect.getCenter(); matrix.translate(center); if (rect.width != bounds.width || rect.height != bounds.height) { matrix.scale( bounds.width != 0 ? rect.width / bounds.width : 1, bounds.height != 0 ? rect.height / bounds.height : 1); } center = bounds.getCenter(); matrix.translate(-center.x, -center.y); this.transform(matrix); }, getStrokeBounds: function() { return this._getBounds('getStrokeBounds', '_strokeBounds', arguments); }, getHandleBounds: function() { return this._getBounds('getHandleBounds', '_handleBounds', arguments); }, getRoughBounds: function() { return this._getBounds('getRoughBounds', '_roughBounds', arguments); }, scale: function(hor, ver , center) { if (arguments.length < 2 || typeof ver === 'object') { center = ver; ver = hor; } return this.transform(new Matrix().scale(hor, ver, center || this.getPosition())); }, translate: function(delta) { var mx = new Matrix(); return this.transform(mx.translate.apply(mx, arguments)); }, rotate: function(angle, center) { return this.transform(new Matrix().rotate(angle, center || this.getPosition())); }, shear: function(hor, ver, center) { if (arguments.length < 2 || typeof ver === 'object') { center = ver; ver = hor; } return this.transform(new Matrix().shear(hor, ver, center || this.getPosition())); }, transform: function(matrix, flags) { var bounds = this._bounds, position = this._position, children = this._children; if (this._transform) { this._transform(matrix, flags); this._changed(Change.GEOMETRY); } if (bounds && matrix.getRotation() % 90 === 0) { this._bounds = this._createBounds( matrix._transformBounds(bounds)); this._position = this._bounds.getCenter(); } else if (position) { this._position = matrix._transformPoint(position, position, true); } for (var i = 0, l = children && children.length; i < l; i++) children[i].transform(matrix, flags); return this; }, fitBounds: function(rectangle, fill) { rectangle = Rectangle.read(arguments); var bounds = this.getBounds(), itemRatio = bounds.height / bounds.width, rectRatio = rectangle.height / rectangle.width, scale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio) ? rectangle.width / bounds.width : rectangle.height / bounds.height, delta = rectangle.getCenter().subtract(bounds.getCenter()), newBounds = new Rectangle(new Point(), new Size(bounds.width * scale, bounds.height * scale)); newBounds.setCenter(rectangle.getCenter()); this.setBounds(newBounds); }, toString: function() { return (this.constructor._name || 'Item') + (this._name ? " '" + this._name + "'" : ' @' + this._id); }, statics: { drawSelectedBounds: function(bounds, ctx, matrix) { var coords = matrix._transformCorners(bounds); ctx.beginPath(); for (var i = 0; i < 8; i++) ctx[i == 0 ? 'moveTo' : 'lineTo'](coords[i], coords[++i]); ctx.closePath(); ctx.stroke(); for (var i = 0; i < 8; i++) { ctx.beginPath(); ctx.rect(coords[i] - 2, coords[++i] - 2, 4, 4); ctx.fill(); } }, draw: function(item, ctx, param) { if (!item._visible || item._opacity == 0) return; var tempCanvas, parentCtx; if (item._blendMode !== 'normal' || item._opacity < 1 && !(item._segments && (!item.getFillColor() || !item.getStrokeColor()))) { var bounds = item.getStrokeBounds() || item.getBounds(); if (!bounds.width || !bounds.height) return; var itemOffset = bounds.getTopLeft().floor(), size = bounds.getSize().ceil().add(new Size(1, 1)); tempCanvas = CanvasProvider.getCanvas(size); parentCtx = ctx; ctx = tempCanvas.getContext('2d'); ctx.save(); ctx.translate(-itemOffset.x, -itemOffset.y); } var savedOffset; if (itemOffset) { savedOffset = param.offset; param.offset = itemOffset; } item.draw(ctx, param); if (itemOffset) param.offset = savedOffset; if (tempCanvas) { ctx.restore(); if (item._blendMode !== 'normal') { var pixelOffset = itemOffset.subtract(param.offset); BlendMode.process(item._blendMode, ctx, parentCtx, item._opacity, pixelOffset); } else { parentCtx.save(); parentCtx.globalAlpha = item._opacity; parentCtx.drawImage(tempCanvas, itemOffset.x, itemOffset.y); parentCtx.restore(); } CanvasProvider.returnCanvas(tempCanvas); } } } }, new function() { var sets = { down: {}, drag: {}, up: {}, move: {} }; function removeAll(set) { for (var id in set) { var item = set[id]; item.remove(); for (var type in sets) { var other = sets[type]; if (other != set && other[item.getId()]) delete other[item.getId()]; } } } function installHandler(name) { var handler = 'onMouse' + Base.capitalize(name); var func = paper.tool[handler]; if (!func || !func._installed) { var hash = {}; hash[handler] = function(event) { if (name === 'up') sets.drag = {}; removeAll(sets[name]); sets[name] = {}; if (this.base) this.base(event); }; paper.tool.inject(hash); paper.tool[handler]._installed = true; } } return Base.each(['down', 'drag', 'up', 'move'], function(name) { this['removeOn' + Base.capitalize(name)] = function() { var hash = {}; hash[name] = true; return this.removeOn(hash); }; }, { removeOn: function(obj) { for (var name in obj) { if (obj[name]) { sets[name][this.getId()] = this; if (name === 'drag') installHandler('up'); installHandler(name); } } return this; } }); }); var Group = this.Group = Item.extend({ initialize: function(items) { this.base(); this._children = []; this._namedChildren = {}; this.addChildren(!items || !Array.isArray(items) || typeof items[0] !== 'object' ? arguments : items); }, _changed: function(flags) { Item.prototype._changed.call(this, flags); if (flags & (ChangeFlag.HIERARCHY | ChangeFlag.CLIPPING)) { delete this._clipItem; } }, _getClipItem: function() { if (this._clipItem !== undefined) return this._clipItem; for (var i = 0, l = this._children.length; i < l; i++) { var child = this._children[i]; if (child._clipMask) return this._clipItem = child; } return this._clipItem = null; }, isClipped: function() { return !!this._getClipItem(); }, setClipped: function(clipped) { var child = this.getFirstChild(); if (child) child.setClipMask(clipped); return this; }, draw: function(ctx, param) { var clipItem = this._getClipItem(); if (clipItem) Item.draw(clipItem, ctx, param); for (var i = 0, l = this._children.length; i < l; i++) { var item = this._children[i]; if (item != clipItem) Item.draw(item, ctx, param); } } }); var Layer = this.Layer = Group.extend({ initialize: function(items) { this._project = paper.project; this._index = this._project.layers.push(this) - 1; this.base.apply(this, arguments); this.activate(); }, _remove: function(deselect, notify) { if (this._parent) return this.base(deselect, notify); if (this._index != null) { if (deselect) this.setSelected(false); Base.splice(this._project.layers, null, this._index, 1); this._project._needsRedraw(); return true; } return false; }, getNextSibling: function() { return this._parent ? this.base() : this._project.layers[this._index + 1] || null; }, getPreviousSibling: function() { return this._parent ? this.base() : this._project.layers[this._index - 1] || null; }, activate: function() { this._project.activeLayer = this; } }, new function () { function insert(above) { return function(item) { if (item instanceof Layer && !item._parent && this._remove(false, true)) { Base.splice(item._project.layers, [this], item._index + (above ? 1 : -1), 0); this._setProject(item._project); return true; } return this.base(item); }; } return { insertAbove: insert(true), insertBelow: insert(false) }; }); var PlacedItem = this.PlacedItem = Item.extend({ _transform: function(matrix, flags) { this._matrix.preConcatenate(matrix); }, _changed: function(flags) { Item.prototype._changed.call(this, flags); if (flags & ChangeFlag.GEOMETRY) { delete this._strokeBounds; delete this._handleBounds; delete this._roughBounds; } }, getMatrix: function() { return this._matrix; }, setMatrix: function(matrix) { this._matrix = matrix.clone(); this._changed(Change.GEOMETRY); }, getBounds: function() { var useCache = arguments[0] === undefined; if (useCache && this._bounds) return this._bounds; var bounds = this.getStrokeBounds(arguments[0]); if (useCache) bounds = this._bounds = this._createBounds(bounds); return bounds; }, _getBounds: function(getter, cacheName, args) { var matrix = args[0], useCache = matrix === undefined; if (useCache && this[cacheName]) return this[cacheName]; matrix = matrix ? matrix.clone().concatenate(this._matrix) : this._matrix; var bounds = this._calculateBounds(getter, matrix); if (useCache) this[cacheName] = bounds; return bounds; } }); var Raster = this.Raster = PlacedItem.extend({ initialize: function(object) { this.base(); if (object.getContext) { this.setCanvas(object); } else { if (typeof object === 'string') object = document.getElementById(object); this.setImage(object); } this._matrix = new Matrix(); }, clone: function() { var image = this._image; if (!image) { image = CanvasProvider.getCanvas(this._size); image.getContext('2d').drawImage(this._canvas, 0, 0); } var copy = new Raster(image); copy._matrix = this._matrix.clone(); return this._clone(copy); }, getSize: function() { return this._size; }, setSize: function() { var size = Size.read(arguments), image = this.getImage(); this.setCanvas(CanvasProvider.getCanvas(size)); this.getContext(true).drawImage(image, 0, 0, size.width, size.height); }, getWidth: function() { return this._size.width; }, getHeight: function() { return this._size.height; }, getPpi: function() { var matrix = this._matrix, orig = new Point(0, 0).transform(matrix), u = new Point(1, 0).transform(matrix).subtract(orig), v = new Point(0, 1).transform(matrix).subtract(orig); return new Size( 72 / u.getLength(), 72 / v.getLength() ); }, getContext: function() { if (!this._context) this._context = this.getCanvas().getContext('2d'); if (arguments[0]) this._changed(Change.PIXELS); return this._context; }, setContext: function(context) { this._context = context; }, getCanvas: function() { if (!this._canvas) { this._canvas = CanvasProvider.getCanvas(this._size); if (this._image) this.getContext(true).drawImage(this._image, 0, 0); } return this._canvas; }, setCanvas: function(canvas) { if (this._canvas) CanvasProvider.returnCanvas(this._canvas); this._canvas = canvas; this._size = new Size(canvas.width, canvas.height); this._image = null; this._context = null; this._changed(Change.GEOMETRY); }, getImage: function() { return this._image || this.getCanvas(); }, setImage: function(image) { if (this._canvas) CanvasProvider.returnCanvas(this._canvas); this._image = image; this._size = new Size(image.naturalWidth, image.naturalHeight); this._canvas = null; this._context = null; this._changed(Change.GEOMETRY); }, getSubImage: function(rect) { rect = Rectangle.read(arguments); var canvas = CanvasProvider.getCanvas(rect.getSize()); canvas.getContext('2d').drawImage(this.getCanvas(), rect.x, rect.y, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); return canvas; }, drawImage: function(image, point) { point = Point.read(arguments, 1); this.getContext(true).drawImage(image, point.x, point.y); }, getAverageColor: function(object) { if (!object) object = this.getBounds(); var bounds, path; if (object instanceof PathItem) { path = object; bounds = object.getBounds(); } else if (object.width) { bounds = new Rectangle(object); } else if (object.x) { bounds = Rectangle.create(object.x - 0.5, object.y - 0.5, 1, 1); } var sampleSize = 32, width = Math.min(bounds.width, sampleSize), height = Math.min(bounds.height, sampleSize); var ctx = Raster._sampleContext; if (!ctx) { ctx = Raster._sampleContext = CanvasProvider.getCanvas( new Size(sampleSize)).getContext('2d'); } else { ctx.clearRect(0, 0, sampleSize, sampleSize); } ctx.save(); ctx.scale(width / bounds.width, height / bounds.height); ctx.translate(-bounds.x, -bounds.y); if (path) path.draw(ctx, { clip: true }); this._matrix.applyToContext(ctx); ctx.drawImage(this._canvas || this._image, -this._size.width / 2, -this._size.height / 2); ctx.restore(); var pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width), Math.ceil(height)).data, channels = [0, 0, 0], total = 0; for (var i = 0, l = pixels.length; i < l; i += 4) { var alpha = pixels[i + 3]; total += alpha; alpha /= 255; channels[0] += pixels[i] * alpha; channels[1] += pixels[i + 1] * alpha; channels[2] += pixels[i + 2] * alpha; } for (var i = 0; i < 3; i++) channels[i] /= total; return total ? Color.read(channels) : null; }, getPixel: function(point) { point = Point.read(arguments); var pixels = this.getContext().getImageData(point.x, point.y, 1, 1).data, channels = new Array(4); for (var i = 0; i < 4; i++) channels[i] = pixels[i] / 255; return RgbColor.read(channels); }, setPixel: function(point, color) { var hasPoint = arguments.length == 2; point = Point.read(arguments, 0, hasPoint ? 1 : 2); color = Color.read(arguments, hasPoint ? 1 : 2); var ctx = this.getContext(true), imageData = ctx.createImageData(1, 1), alpha = color.getAlpha(); imageData.data[0] = color.getRed() * 255; imageData.data[1] = color.getGreen() * 255; imageData.data[2] = color.getBlue() * 255; imageData.data[3] = alpha != null ? alpha * 255 : 255; ctx.putImageData(imageData, point.x, point.y); }, createData: function(size) { size = Size.read(arguments); return this.getContext().createImageData(size.width, size.height); }, getData: function(rect) { rect = Rectangle.read(arguments); if (rect.isEmpty()) rect = new Rectangle(this.getSize()); return this.getContext().getImageData(rect.x, rect.y, rect.width, rect.height); }, setData: function(data, point) { point = Point.read(arguments, 1); this.getContext(true).putImageData(data, point.x, point.y); }, _calculateBounds: function(getter, matrix) { return matrix._transformBounds( new Rectangle(this._size).setCenter(0, 0)); }, getHandleBounds: function() { return this.getStrokeBounds(arguments[0]); }, getRoughBounds: function() { return this.getStrokeBounds(arguments[0]); }, draw: function(ctx, param) { if (param.selection) { var bounds = new Rectangle(this._size).setCenter(0, 0); Item.drawSelectedBounds(bounds, ctx, this._matrix); } else { ctx.save(); this._matrix.applyToContext(ctx); ctx.drawImage(this._canvas || this._image, -this._size.width / 2, -this._size.height / 2); ctx.restore(); } } }); var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend({ initialize: function(symbol, matrixOrOffset) { this.base(); this.setSymbol(symbol instanceof Symbol ? symbol : new Symbol(symbol)); this._matrix = matrixOrOffset !== undefined ? matrixOrOffset instanceof Matrix ? matrixOrOffset : new Matrix().translate(Point.read(arguments, 1)) : new Matrix(); }, getSymbol: function() { return this._symbol; }, setSymbol: function(symbol) { if (this._symbol) delete this._symbol._instances[this._id]; this._symbol = symbol; symbol._instances[this._id] = this; }, clone: function() { return this._clone(new PlacedSymbol(this.symbol, this._matrix.clone())); }, _calculateBounds: function(getter, matrix) { return this.symbol._definition[getter](matrix); }, draw: function(ctx, param) { if (param.selection) { Item.drawSelectedBounds(this.symbol._definition.getStrokeBounds(), ctx, this._matrix); } else { ctx.save(); this._matrix.applyToContext(ctx); Item.draw(this.symbol.getDefinition(), ctx, param); ctx.restore(); } } }); HitResult = Base.extend({ initialize: function(type, item, values) { this.type = type; this.item = item; if (values) { Base.each(values, function(value, key) { this[key] = value; }, this); } }, statics: { getOptions: function(point, options) { return options && options._merged ? options : Base.merge({ point: Point.read(arguments, 0, 1), type: null, tolerance: 2, fill: !options, stroke: !options, segments: !options, handles: false, ends: false, center: false, bounds: false, guides: false, selected: false, _merged: true }, options); } } }); var Segment = this.Segment = Base.extend({ initialize: function(arg0, arg1, arg2, arg3, arg4, arg5) { var count = arguments.length, createPoint = SegmentPoint.create, point, handleIn, handleOut; if (count == 0) { } else if (count == 1) { if (arg0.point) { point = arg0.point; handleIn = arg0.handleIn; handleOut = arg0.handleOut; } else { point = arg0; } } else if (count < 6) { if (count == 2 && arg1.x === undefined) { point = [ arg0, arg1 ]; } else { point = arg0; handleIn = arg1; handleOut = arg2; } } else if (count == 6) { point = [ arg0, arg1 ]; handleIn = [ arg2, arg3 ]; handleOut = [ arg4, arg5 ]; } createPoint(this, '_point', point); createPoint(this, '_handleIn', handleIn); createPoint(this, '_handleOut', handleOut); }, _changed: function(point) { if (!this._path) return; var curve = this._path._curves && this.getCurve(), other; if (curve) { curve._changed(); if (other = (curve[point == this._point || point == this._handleIn && curve._segment1 == this ? 'getPrevious' : 'getNext']())) { other._changed(); } } this._path._changed(Change.GEOMETRY); }, getPoint: function() { return this._point; }, setPoint: function(point) { point = Point.read(arguments); this._point.set(point.x, point.y); }, getHandleIn: function() { return this._handleIn; }, setHandleIn: function(point) { point = Point.read(arguments); this._handleIn.set(point.x, point.y); }, getHandleOut: function() { return this._handleOut; }, setHandleOut: function(point) { point = Point.read(arguments); this._handleOut.set(point.x, point.y); }, _isSelected: function(point) { var state = this._selectionState; return point == this._point ? !!(state & SelectionState.POINT) : point == this._handleIn ? !!(state & SelectionState.HANDLE_IN) : point == this._handleOut ? !!(state & SelectionState.HANDLE_OUT) : false; }, _setSelected: function(point, selected) { var path = this._path, selected = !!selected, state = this._selectionState || 0, selection = [ !!(state & SelectionState.POINT), !!(state & SelectionState.HANDLE_IN), !!(state & SelectionState.HANDLE_OUT) ]; if (point == this._point) { if (selected) { selection[1] = selection[2] = false; } else { var previous = this.getPrevious(), next = this.getNext(); selection[1] = previous && (previous._point.isSelected() || previous._handleOut.isSelected()); selection[2] = next && (next._point.isSelected() || next._handleIn.isSelected()); } selection[0] = selected; } else { var index = point == this._handleIn ? 1 : 2; if (selection[index] != selected) { if (selected) selection[0] = false; selection[index] = selected; path._changed(Change.ATTRIBUTE); } } this._selectionState = (selection[0] ? SelectionState.POINT : 0) | (selection[1] ? SelectionState.HANDLE_IN : 0) | (selection[2] ? SelectionState.HANDLE_OUT : 0); if (path && state != this._selectionState) path._updateSelection(this, state, this._selectionState); }, isSelected: function() { return this._isSelected(this._point); }, setSelected: function(selected) { this._setSelected(this._point, selected); }, getIndex: function() { return this._index !== undefined ? this._index : null; }, getPath: function() { return this._path || null; }, getCurve: function() { if (this._path) { var index = this._index; if (!this._path._closed && index == this._path._segments.length - 1) index--; return this._path.getCurves()[index] || null; } return null; }, getNext: function() { var segments = this._path && this._path._segments; return segments && (segments[this._index + 1] || this._path._closed && segments[0]) || null; }, getPrevious: function() { var segments = this._path && this._path._segments; return segments && (segments[this._index - 1] || this._path._closed && segments[segments.length - 1]) || null; }, reverse: function() { return new Segment(this._point, this._handleOut, this._handleIn); }, remove: function() { return this._path ? !!this._path.removeSegment(this._index) : false; }, toString: function() { var parts = [ 'point: ' + this._point ]; if (!this._handleIn.isZero()) parts.push('handleIn: ' + this._handleIn); if (!this._handleOut.isZero()) parts.push('handleOut: ' + this._handleOut); return '{ ' + parts.join(', ') + ' }'; }, _transformCoordinates: function(matrix, coords, change) { var point = this._point, handleIn = !change || !this._handleIn.isZero() ? this._handleIn : null, handleOut = !change || !this._handleOut.isZero() ? this._handleOut : null, x = point._x, y = point._y, i = 2; coords[0] = x; coords[1] = y; if (handleIn) { coords[i++] = handleIn._x + x; coords[i++] = handleIn._y + y; } if (handleOut) { coords[i++] = handleOut._x + x; coords[i++] = handleOut._y + y; } if (!matrix) return; matrix._transformCoordinates(coords, 0, coords, 0, i / 2); x = coords[0]; y = coords[1]; if (change) { point._x = x; point._y = y; i = 2; if (handleIn) { handleIn._x = coords[i++] - x; handleIn._y = coords[i++] - y; } if (handleOut) { handleOut._x = coords[i++] - x; handleOut._y = coords[i++] - y; } } else { if (!handleIn) { coords[i++] = x; coords[i++] = y; } if (!handleOut) { coords[i++] = x; coords[i++] = y; } } } }); var SegmentPoint = Point.extend({ set: function(x, y) { this._x = x; this._y = y; this._owner._changed(this); return this; }, getX: function() { return this._x; }, setX: function(x) { this._x = x; this._owner._changed(this); }, getY: function() { return this._y; }, setY: function(y) { this._y = y; this._owner._changed(this); }, isZero: function() { return this._x == 0 && this._y == 0; }, setSelected: function(selected) { this._owner._setSelected(this, selected); }, isSelected: function() { return this._owner._isSelected(this); }, statics: { create: function(segment, key, pt) { var point = new SegmentPoint(SegmentPoint.dont), x, y, selected; if (!pt) { x = y = 0; } else if ((x = pt[0]) !== undefined) { y = pt[1]; } else { if ((x = pt.x) === undefined) { pt = Point.read(arguments, 2, 1); x = pt.x; } y = pt.y; selected = pt.selected; } point._x = x; point._y = y; point._owner = segment; segment[key] = point; if (selected) point.setSelected(true); return point; } } }); var SelectionState = { HANDLE_IN: 1, HANDLE_OUT: 2, POINT: 4 }; var Curve = this.Curve = Base.extend({ initialize: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { var count = arguments.length; if (count == 0) { this._segment1 = new Segment(); this._segment2 = new Segment(); } else if (count == 1) { this._segment1 = new Segment(arg0.segment1); this._segment2 = new Segment(arg0.segment2); } else if (count == 2) { this._segment1 = new Segment(arg0); this._segment2 = new Segment(arg1); } else if (count == 4) { this._segment1 = new Segment(arg0, null, arg1); this._segment2 = new Segment(arg3, arg2, null); } else if (count == 8) { var p1 = Point.create(arg0, arg1), p2 = Point.create(arg6, arg7); this._segment1 = new Segment(p1, null, Point.create(arg2, arg3).subtract(p1)); this._segment2 = new Segment(p2, Point.create(arg4, arg5).subtract(p2), null); } }, _changed: function() { delete this._length; }, getPoint1: function() { return this._segment1._point; }, setPoint1: function(point) { point = Point.read(arguments); this._segment1._point.set(point.x, point.y); }, getPoint2: function() { return this._segment2._point; }, setPoint2: function(point) { point = Point.read(arguments); this._segment2._point.set(point.x, point.y); }, getHandle1: function() { return this._segment1._handleOut; }, setHandle1: function(point) { point = Point.read(arguments); this._segment1._handleOut.set(point.x, point.y); }, getHandle2: function() { return this._segment2._handleIn; }, setHandle2: function(point) { point = Point.read(arguments); this._segment2._handleIn.set(point.x, point.y); }, getSegment1: function() { return this._segment1; }, getSegment2: function() { return this._segment2; }, getPath: function() { return this._path; }, getIndex: function() { return this._segment1._index; }, getNext: function() { var curves = this._path && this._path._curves; return curves && (curves[this._segment1._index + 1] || this._path._closed && curves[0]) || null; }, getPrevious: function() { var curves = this._path && this._path._curves; return curves && (curves[this._segment1._index - 1] || this._path._closed && curves[curves.length - 1]) || null; }, isSelected: function() { return this.getHandle1().isSelected() && this.getHandle2().isSelected(); }, setSelected: function(selected) { this.getHandle1().setSelected(selected); this.getHandle2().setSelected(selected); }, getValues: function(matrix) { return Curve.getValues(this._segment1, this._segment2, matrix); }, getPoints: function(matrix) { var coords = this.getValues(matrix), points = []; for (var i = 0; i < 8; i += 2) points.push(Point.create(coords[i], coords[i + 1])); return points; }, getLength: function() { var from = arguments[0], to = arguments[1]; fullLength = arguments.length == 0 || from == 0 && to == 1; if (fullLength && this._length != null) return this._length; var length = Curve.getLength(this.getValues(), from, to); if (fullLength) this._length = length; return length; }, getPart: function(from, to) { return new Curve(Curve.getPart(this.getValues(), from, to)); }, isLinear: function() { return this._segment1._handleOut.isZero() && this._segment2._handleIn.isZero(); }, getParameterAt: function(offset, start) { return Curve.getParameterAt(this.getValues(), offset, start !== undefined ? start : offset < 0 ? 1 : 0); }, getPoint: function(parameter) { return Curve.evaluate(this.getValues(), parameter, 0); }, getTangent: function(parameter) { return Curve.evaluate(this.getValues(), parameter, 1); }, getNormal: function(parameter) { return Curve.evaluate(this.getValues(), parameter, 2); }, getParameter: function(point) { point = Point.read(point); return Curve.getParameter(this.getValues(), point.x, point.y); }, getCrossings: function(point, matrix, roots) { var vals = this.getValues(matrix), num = Curve.solveCubic(vals, 1, point.y, roots), crossings = 0; for (var i = 0; i < num; i++) { var t = roots[i]; if (t >= 0 && t < 1 && Curve.evaluate(vals, t, 0).x > point.x) { if (t < Numerical.TOLERANCE && Curve.evaluate( this.getPrevious().getValues(matrix), 1, 1).y * Curve.evaluate(vals, t, 1).y >= 0) continue; crossings++; } } return crossings; }, reverse: function() { return new Curve(this._segment2.reverse(), this._segment1.reverse()); }, clone: function() { return new Curve(this._segment1, this._segment2); }, toString: function() { var parts = [ 'point1: ' + this._segment1._point ]; if (!this._segment1._handleOut.isZero()) parts.push('handle1: ' + this._segment1._handleOut); if (!this._segment2._handleIn.isZero()) parts.push('handle2: ' + this._segment2._handleIn); parts.push('point2: ' + this._segment2._point); return '{ ' + parts.join(', ') + ' }'; }, statics: { create: function(path, segment1, segment2) { var curve = new Curve(Curve.dont); curve._path = path; curve._segment1 = segment1; curve._segment2 = segment2; return curve; }, getValues: function(segment1, segment2, matrix) { var p1 = segment1._point, h1 = segment1._handleOut, h2 = segment2._handleIn, p2 = segment2._point, coords = [ p1._x, p1._y, p1._x + h1._x, p1._y + h1._y, p2._x + h2._x, p2._y + h2._y, p2._x, p2._y ]; return matrix ? matrix._transformCoordinates(coords, 0, coords, 0, 4) : coords; }, evaluate: function(v, t, type) { var p1x = v[0], p1y = v[1], c1x = v[2], c1y = v[3], c2x = v[4], c2y = v[5], p2x = v[6], p2y = v[7], x, y; if (type == 0 && (t == 0 || t == 1)) { x = t == 0 ? p1x : p2x; y = t == 0 ? p1y : p2y; } else { var tMin = Numerical.TOLERANCE; if (t < tMin && c1x == p1x && c1y == p1y) t = tMin; else if (t > 1 - tMin && c2x == p2x && c2y == p2y) t = 1 - tMin; var cx = 3 * (c1x - p1x), bx = 3 * (c2x - c1x) - cx, ax = p2x - p1x - cx - bx, cy = 3 * (c1y - p1y), by = 3 * (c2y - c1y) - cy, ay = p2y - p1y - cy - by; switch (type) { case 0: x = ((ax * t + bx) * t + cx) * t + p1x; y = ((ay * t + by) * t + cy) * t + p1y; break; case 1: case 2: x = (3 * ax * t + 2 * bx) * t + cx; y = (3 * ay * t + 2 * by) * t + cy; break; } } return type == 2 ? new Point(y, -x) : new Point(x, y); }, subdivide: function(v, t) { var p1x = v[0], p1y = v[1], c1x = v[2], c1y = v[3], c2x = v[4], c2y = v[5], p2x = v[6], p2y = v[7]; if (t === undefined) t = 0.5; var u = 1 - t, p3x = u * p1x + t * c1x, p3y = u * p1y + t * c1y, p4x = u * c1x + t * c2x, p4y = u * c1y + t * c2y, p5x = u * c2x + t * p2x, p5y = u * c2y + t * p2y, p6x = u * p3x + t * p4x, p6y = u * p3y + t * p4y, p7x = u * p4x + t * p5x, p7y = u * p4y + t * p5y, p8x = u * p6x + t * p7x, p8y = u * p6y + t * p7y; return [ [p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], [p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] ]; }, solveCubic: function (v, coord, val, roots) { var p1 = v[coord], c1 = v[coord + 2], c2 = v[coord + 4], p2 = v[coord + 6], c = 3 * (c1 - p1), b = 3 * (c2 - c1) - c, a = p2 - p1 - c - b; return Numerical.solveCubic(a, b, c, p1 - val, roots, Numerical.TOLERANCE); }, getParameter: function(v, x, y) { var txs = [], tys = [], sx = Curve.solveCubic(v, 0, x, txs), sy = Curve.solveCubic(v, 1, y, tys), tx, ty; for (var cx = 0; sx == -1 || cx < sx;) { if (sx == -1 || (tx = txs[cx++]) >= 0 && tx <= 1) { for (var cy = 0; sy == -1 || cy < sy;) { if (sy == -1 || (ty = tys[cy++]) >= 0 && ty <= 1) { if (sx == -1) tx = ty; else if (sy == -1) ty = tx; if (Math.abs(tx - ty) < Numerical.TOLERANCE) return (tx + ty) * 0.5; } } if (sx == -1) break; } } return null; }, getPart: function(v, from, to) { if (from > 0) v = Curve.subdivide(v, from)[1]; if (to < 1) v = Curve.subdivide(v, (to - from) / (1 - from))[0]; return v; }, isFlatEnough: function(v) { var p1x = v[0], p1y = v[1], c1x = v[2], c1y = v[3], c2x = v[4], c2y = v[5], p2x = v[6], p2y = v[7], a = p1y - p2y, b = p2x - p1x, c = p1x * p2y - p2x * p1y, v1 = a * c1x + b * c1y + c, v2 = a * c2x + b * c2y + c; return Math.abs((v1 * v1 + v2 * v2) / (a * (a * a + b * b))) < 0.005; } } }, new function() { function getLengthIntegrand(v) { var p1x = v[0], p1y = v[1], c1x = v[2], c1y = v[3], c2x = v[4], c2y = v[5], p2x = v[6], p2y = v[7], ax = 9 * (c1x - c2x) + 3 * (p2x - p1x), bx = 6 * (p1x + c2x) - 12 * c1x, cx = 3 * (c1x - p1x), ay = 9 * (c1y - c2y) + 3 * (p2y - p1y), by = 6 * (p1y + c2y) - 12 * c1y, cy = 3 * (c1y - p1y); return function(t) { var dx = (ax * t + bx) * t + cx, dy = (ay * t + by) * t + cy; return Math.sqrt(dx * dx + dy * dy); }; } function getIterations(a, b) { return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32))); } return { statics: true, getLength: function(v, a, b) { if (a === undefined) a = 0; if (b === undefined) b = 1; if (v[0] == v[2] && v[1] == v[3] && v[6] == v[4] && v[7] == v[5]) { var dx = v[6] - v[0], dy = v[7] - v[1]; return (b - a) * Math.sqrt(dx * dx + dy * dy); } var ds = getLengthIntegrand(v); return Numerical.integrate(ds, a, b, getIterations(a, b)); }, getParameterAt: function(v, offset, start) { if (offset == 0) return start; var forward = offset > 0, a = forward ? start : 0, b = forward ? 1 : start, offset = Math.abs(offset), ds = getLengthIntegrand(v), rangeLength = Numerical.integrate(ds, a, b, getIterations(a, b)); if (offset >= rangeLength) return forward ? b : a; var guess = offset / rangeLength, length = 0; function f(t) { var count = getIterations(start, t); length += start < t ? Numerical.integrate(ds, start, t, count) : -Numerical.integrate(ds, t, start, count); start = t; return length - offset; } return Numerical.findRoot(f, ds, forward ? a + guess : b - guess, a, b, 16, Numerical.TOLERANCE); } }; }, new function() { var maxDepth = 32, epsilon = Math.pow(2, -maxDepth - 1); var zCubic = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; var xAxis = new Line(new Point(0, 0), new Point(1, 0)); function toBezierForm(v, point) { var n = 3, degree = 5, c = [], d = [], cd = [], w = []; for(var i = 0; i <= n; i++) { c[i] = v[i].subtract(point); if (i < n) d[i] = v[i + 1].subtract(v[i]).multiply(n); } for (var row = 0; row < n; row++) { cd[row] = []; for (var column = 0; column <= n; column++) cd[row][column] = d[row].dot(c[column]); } for (var i = 0; i <= degree; i++) w[i] = new Point(i / degree, 0); for (k = 0; k <= degree; k++) { var lb = Math.max(0, k - n + 1), ub = Math.min(k, n); for (var i = lb; i <= ub; i++) { var j = k - i; w[k].y += cd[j][i] * zCubic[j][i]; } } return w; } function findRoots(w, depth) { switch (countCrossings(w)) { case 0: return []; case 1: if (depth >= maxDepth) return [0.5 * (w[0].x + w[5].x)]; if (isFlatEnough(w)) { var line = new Line(w[0], w[5], true); return [ line.vector.getLength(true) <= Numerical.EPSILON ? line.point.x : xAxis.intersect(line).x ]; } } var p = [[]], left = [], right = []; for (var j = 0; j <= 5; j++) p[0][j] = new Point(w[j]); for (var i = 1; i <= 5; i++) { p[i] = []; for (var j = 0 ; j <= 5 - i; j++) p[i][j] = p[i - 1][j].add(p[i - 1][j + 1]).multiply(0.5); } for (var j = 0; j <= 5; j++) { left[j] = p[j][0]; right[j] = p[5 - j][j]; } return findRoots(left, depth + 1).concat(findRoots(right, depth + 1)); } function countCrossings(v) { var crossings = 0, prevSign = null; for (var i = 0, l = v.length; i < l; i++) { var sign = v[i].y < 0 ? -1 : 1; if (prevSign != null && sign != prevSign) crossings++; prevSign = sign; } return crossings; } function isFlatEnough(v) { var n = v.length - 1, a = v[0].y - v[n].y, b = v[n].x - v[0].x, c = v[0].x * v[n].y - v[n].x * v[0].y, maxAbove = 0, maxBelow = 0; for (var i = 1; i < n; i++) { var val = a * v[i].x + b * v[i].y + c, dist = val * val; if (val < 0 && dist > maxBelow) { maxBelow = dist; } else if (dist > maxAbove) { maxAbove = dist; } } return Math.abs((maxAbove + maxBelow) / (2 * a * (a * a + b * b))) < epsilon; } return { getNearestLocation: function(point, matrix) { var w = toBezierForm(this.getPoints(matrix), point); var roots = findRoots(w, 0).concat([0, 1]); var minDist = Infinity, minT, minPoint; for (var i = 0; i < roots.length; i++) { var pt = this.getPoint(roots[i]), dist = point.getDistance(pt, true); if (dist < minDist) { minDist = dist; minT = roots[i]; minPoint = pt; } } return new CurveLocation(this, minT, minPoint, Math.sqrt(minDist)); }, getNearestPoint: function(point, matrix) { return this.getNearestLocation(point, matrix).getPoint(); } }; }); CurveLocation = Base.extend({ initialize: function(curve, parameter, point, distance) { this._curve = curve; this._parameter = parameter; this._point = point; this._distance = distance; }, getSegment: function() { if (!this._segment) { var curve = this._curve, parameter = this.getParameter(); if (parameter == 0) { this._segment = curve._segment1; } else if (parameter == 1) { this._segment = curve._segment2; } else if (parameter == null) { return null; } else { this._segment = curve.getLength(0, parameter) < curve.getLength(parameter, 1) ? curve._segment1 : curve._segment2; } } return this._segment; }, getCurve: function() { return this._curve; }, getPath: function() { return this._curve && this._curve._path; }, getIndex: function() { return this._curve && this._curve.getIndex(); }, getOffset: function() { var path = this._curve && this._curve._path; return path && path._getOffset(this); }, getCurveOffset: function() { var parameter = this.getParameter(); return parameter != null && this._curve && this._curve.getLength(0, parameter); }, getParameter: function() { if (this._parameter == null && this._curve && this._point) this._parameter = this._curve.getParameterAt(this._point); return this._parameter; }, getPoint: function() { if (!this._point && this._curve && this._parameter != null) this._point = this._curve.getPoint(this._parameter); return this._point; }, getTangent: function() { var parameter = this.getParameter(); return parameter != null && this._curve && this._curve.getTangent(parameter); }, getNormal: function() { var parameter = this.getParameter(); return parameter != null && this._curve && this._curve.getNormal(parameter); }, getDistance: function() { return this._distance; }, toString: function() { var parts = [], point = this.getPoint(); if (point) parts.push('point: ' + point); var index = this.getIndex(); if (index != null) parts.push('index: ' + index); var parameter = this.getParameter(); if (parameter != null) parts.push('parameter: ' + Base.formatNumber(parameter)); if (this._distance != null) parts.push('distance: ' + Base.formatNumber(this._distance)); return '{ ' + parts.join(', ') + ' }'; } }); var PathItem = this.PathItem = Item.extend({ }); var Path = this.Path = PathItem.extend({ initialize: function(segments) { this.base(); this._closed = false; this._selectedSegmentState = 0; this.setSegments(!segments || !Array.isArray(segments) || typeof segments[0] !== 'object' ? arguments : segments); }, clone: function() { var copy = this._clone(new Path(this._segments)); copy._closed = this._closed; if (this._clockwise !== undefined) copy._clockwise = this._clockwise; return copy; }, _changed: function(flags) { Item.prototype._changed.call(this, flags); if (flags & ChangeFlag.GEOMETRY) { delete this._strokeBounds; delete this._handleBounds; delete this._roughBounds; delete this._length; delete this._clockwise; } else if (flags & ChangeFlag.STROKE) { delete this._strokeBounds; } }, getSegments: function() { return this._segments; }, setSegments: function(segments) { if (!this._segments) { this._segments = []; } else { this._selectedSegmentState = 0; this._segments.length = 0; if (this._curves) delete this._curves; } this._add(Segment.readAll(segments)); }, getFirstSegment: function() { return this._segments[0]; }, getLastSegment: function() { return this._segments[this._segments.length - 1]; }, getCurves: function() { if (!this._curves) { var segments = this._segments, length = segments.length; if (!this._closed && length > 0) length--; this._curves = new Array(length); for (var i = 0; i < length; i++) this._curves[i] = Curve.create(this, segments[i], segments[i + 1] || segments[0]); } return this._curves; }, getFirstCurve: function() { return this.getCurves()[0]; }, getLastCurve: function() { var curves = this.getCurves(); return curves[curves.length - 1]; }, getClosed: function() { return this._closed; }, setClosed: function(closed) { if (this._closed != (closed = !!closed)) { this._closed = closed; if (this._curves) { var length = this._segments.length, i; if (!closed && length > 0) length--; this._curves.length = length; if (closed) this._curves[i = length - 1] = Curve.create(this, this._segments[i], this._segments[0]); } this._changed(Change.GEOMETRY); } }, _transform: function(matrix, flags) { if (!matrix.isIdentity()) { var coords = new Array(6); for (var i = 0, l = this._segments.length; i < l; i++) { this._segments[i]._transformCoordinates(matrix, coords, true); } var fillColor = this.getFillColor(), strokeColor = this.getStrokeColor(); if (fillColor && fillColor.transform) fillColor.transform(matrix); if (strokeColor && strokeColor.transform) strokeColor.transform(matrix); } }, _add: function(segs, index) { var segments = this._segments, curves = this._curves, amount = segs.length, append = index == null, index = append ? segments.length : index, fullySelected = this.isFullySelected(); for (var i = 0; i < amount; i++) { var segment = segs[i]; if (segment._path) { segment = segs[i] = new Segment(segment); } segment._path = this; segment._index = index + i; if (fullySelected) segment._selectionState = SelectionState.POINT; if (segment._selectionState) this._updateSelection(segment, 0, segment._selectionState); } if (append) { segments.push.apply(segments, segs); } else { segments.splice.apply(segments, [index, 0].concat(segs)); for (var i = index + amount, l = segments.length; i < l; i++) { segments[i]._index = i; } } if (curves && --index >= 0) { curves.splice(index, 0, Curve.create(this, segments[index], segments[index + 1])); var curve = curves[index + amount]; if (curve) { curve._segment1 = segments[index + amount]; } } this._changed(Change.GEOMETRY); return segs; }, add: function(segment1 ) { return arguments.length > 1 && typeof segment1 !== 'number' ? this._add(Segment.readAll(arguments)) : this._add([ Segment.read(arguments) ])[0]; }, insert: function(index, segment1 ) { return arguments.length > 2 && typeof segment1 !== 'number' ? this._add(Segment.readAll(arguments, 1), index) : this._add([ Segment.read(arguments, 1) ], index)[0]; }, addSegment: function(segment) { return this._add([ Segment.read(arguments) ])[0]; }, insertSegment: function(index, segment) { return this._add([ Segment.read(arguments, 1) ], index)[0]; }, addSegments: function(segments) { return this._add(Segment.readAll(segments)); }, insertSegments: function(index, segments) { return this._add(Segment.readAll(segments), index); }, removeSegment: function(index) { var segments = this.removeSegments(index, index + 1); return segments[0] || null; }, removeSegments: function(from, to) { from = from || 0; to = Base.pick(to, this._segments.length); var segments = this._segments, curves = this._curves, last = to >= segments.length, removed = segments.splice(from, to - from), amount = removed.length; if (!amount) return removed; for (var i = 0; i < amount; i++) { var segment = removed[i]; if (segment._selectionState) this._updateSelection(segment, segment._selectionState, 0); removed._index = removed._path = undefined; } for (var i = from, l = segments.length; i < l; i++) segments[i]._index = i; if (curves) { curves.splice(from, amount); var curve; if (curve = curves[from - 1]) curve._segment2 = segments[from]; if (curve = curves[from]) curve._segment1 = segments[from]; if (last && this._closed && (curve = curves[curves.length - 1])) curve._segment2 = segments[0]; } this._changed(Change.GEOMETRY); return removed; }, isFullySelected: function() { return this._selected && this._selectedSegmentState == this._segments.length * SelectionState.POINT; }, setFullySelected: function(selected) { var length = this._segments.length; this._selectedSegmentState = selected ? length * SelectionState.POINT : 0; for (var i = 0; i < length; i++) this._segments[i]._selectionState = selected ? SelectionState.POINT : 0; this.setSelected(selected); }, _updateSelection: function(segment, oldState, newState) { segment._selectionState = newState; var total = this._selectedSegmentState += newState - oldState; if (total > 0) this.setSelected(true); }, flatten: function(maxDistance) { var flattener = new PathFlattener(this), pos = 0, step = flattener.length / Math.ceil(flattener.length / maxDistance), end = flattener.length + (this._closed ? -step : step) / 2; var segments = []; while (pos <= end) { segments.push(new Segment(flattener.evaluate(pos, 0))); pos += step; } this.setSegments(segments); }, simplify: function(tolerance) { if (this._segments.length > 2) { var fitter = new PathFitter(this, tolerance || 2.5); this.setSegments(fitter.fit()); } }, isClockwise: function() { if (this._clockwise !== undefined) return this._clockwise; var sum = 0, xPre, yPre; function edge(x, y) { if (xPre !== undefined) sum += (xPre - x) * (y + yPre); xPre = x; yPre = y; } for (var i = 0, l = this._segments.length; i < l; i++) { var seg1 = this._segments[i], seg2 = this._segments[i + 1 < l ? i + 1 : 0], point1 = seg1._point, handle1 = seg1._handleOut, handle2 = seg2._handleIn, point2 = seg2._point; edge(point1._x, point1._y); edge(point1._x + handle1._x, point1._y + handle1._y); edge(point2._x + handle2._x, point2._y + handle2._y); edge(point2._x, point2._y); } return sum > 0; }, setClockwise: function(clockwise) { if (this.isClockwise() != (clockwise = !!clockwise)) { this.reverse(); this._clockwise = clockwise; } }, reverse: function() { this._segments.reverse(); for (var i = 0, l = this._segments.length; i < l; i++) { var segment = this._segments[i]; var handleIn = segment._handleIn; segment._handleIn = segment._handleOut; segment._handleOut = handleIn; } if (this._clockwise !== undefined) this._clockwise = !this._clockwise; }, join: function(path) { if (path) { var segments = path._segments, last1 = this.getLastSegment(), last2 = path.getLastSegment(); if (last1._point.equals(last2._point)) path.reverse(); var first2 = path.getFirstSegment(); if (last1._point.equals(first2._point)) { last1.setHandleOut(first2._handleOut); this._add(segments.slice(1)); } else { var first1 = this.getFirstSegment(); if (first1._point.equals(first2._point)) path.reverse(); last2 = path.getLastSegment(); if (first1._point.equals(last2._point)) { first1.setHandleIn(last2._handleIn); this._add(segments.slice(0, segments.length - 1), 0); } else { this._add(segments.slice(0)); } } path.remove(); var first1 = this.getFirstSegment(); last1 = this.getLastSegment(); if (last1._point.equals(first1._point)) { first1.setHandleIn(last1._handleIn); last1.remove(); this.setClosed(true); } this._changed(Change.GEOMETRY); return true; } return false; }, getLength: function() { if (this._length == null) { var curves = this.getCurves(); this._length = 0; for (var i = 0, l = curves.length; i < l; i++) this._length += curves[i].getLength(); } return this._length; }, _getOffset: function(location) { var index = location && location.getIndex(); if (index != null) { var curves = this.getCurves(), offset = 0; for (var i = 0; i < index; i++) offset += curves[i].getLength(); var curve = curves[index]; return offset + curve.getLength(0, location.getParameter()); } return null; }, getLocation: function(point) { var curves = this.getCurves(); for (var i = 0, l = curves.length; i < l; i++) { var curve = curves[i]; var t = curve.getParameter(point); if (t != null) return new CurveLocation(curve, t); } return null; }, getLocationAt: function(offset, isParameter) { var curves = this.getCurves(), length = 0; if (isParameter) { var index = ~~offset; return new CurveLocation(curves[index], offset - index); } for (var i = 0, l = curves.length; i < l; i++) { var start = length, curve = curves[i]; length += curve.getLength(); if (length >= offset) { return new CurveLocation(curve, curve.getParameterAt(offset - start)); } } if (offset <= this.getLength()) return new CurveLocation(curves[curves.length - 1], 1); return null; }, getPointAt: function(offset, isParameter) { var loc = this.getLocationAt(offset, isParameter); return loc && loc.getPoint(); }, getTangentAt: function(offset, isParameter) { var loc = this.getLocationAt(offset, isParameter); return loc && loc.getTangent(); }, getNormalAt: function(offset, isParameter) { var loc = this.getLocationAt(offset, isParameter); return loc && loc.getNormal(); }, getNearestLocation: function(point, matrix) { var curves = this.getCurves(), minDist = Infinity, minLoc = null; for (var i = 0, l = curves.length; i < l; i++) { var loc = curves[i].getNearestLocation(point, matrix); if (loc._distance < minDist) { minDist = loc._distance; minLoc = loc; } } return minLoc; }, getNearestPoint: function(point, matrix) { return this.getNearestLocation(point, matrix).getPoint(); }, contains: function(point, matrix) { point = Point.read(arguments); if (!this._closed || !this.getRoughBounds(matrix)._containsPoint(point)) return false; var curves = this.getCurves(), crossings = 0, roots = []; for (var i = 0, l = curves.length; i < l; i++) crossings += curves[i].getCrossings(point, matrix, roots); return (crossings & 1) == 1; }, _hitTest: function(point, options, matrix) { var tolerance = options.tolerance || 0, radius = (options.stroke ? this.getStrokeWidth() / 2 : 0) + tolerance, loc, res; var coords = [], that = this; function checkSegment(segment, ends) { segment._transformCoordinates(matrix, coords); for (var j = ends || options.segments ? 0 : 2, m = !ends && options.handles ? 6 : 2; j < m; j += 2) { if (point.getDistance(coords[j], coords[j + 1]) < tolerance) return new HitResult(j == 0 ? 'segment' : 'handle-' + (j == 2 ? 'in' : 'out'), that, { segment: segment }); } } if (options.ends && !options.segments && !this._closed) { if (res = checkSegment(this.getFirstSegment(), true) || checkSegment(this.getLastSegment(), true)) return res; } else if (options.segments || options.handles) { for (var i = 0, l = this._segments.length; i < l; i++) { if (res = checkSegment(this._segments[i])) return res; } } if (options.stroke && radius > 0) loc = this.getNearestLocation(point, matrix); if (!(loc && loc._distance <= radius) && options.fill && this.getFillColor() && this.contains(point, matrix)) return new HitResult('fill', this); if (!loc && options.stroke && radius > 0) loc = this.getNearestLocation(point, matrix); if (loc && loc._distance <= radius) return options.stroke ? new HitResult('stroke', this, { location: loc }) : new HitResult('fill', this); } }, new function() { function drawHandles(ctx, segments) { for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i], point = segment._point, state = segment._selectionState, selected = state & SelectionState.POINT; if (selected || (state & SelectionState.HANDLE_IN)) drawHandle(ctx, point, segment._handleIn); if (selected || (state & SelectionState.HANDLE_OUT)) drawHandle(ctx, point, segment._handleOut); ctx.save(); ctx.beginPath(); ctx.rect(point._x - 2, point._y - 2, 4, 4); ctx.fill(); if (!selected) { ctx.beginPath(); ctx.rect(point._x - 1, point._y - 1, 2, 2); ctx.fillStyle = '#ffffff'; ctx.fill(); } ctx.restore(); } } function drawHandle(ctx, point, handle) { if (!handle.isZero()) { var handleX = point._x + handle._x, handleY = point._y + handle._y; ctx.beginPath(); ctx.moveTo(point._x, point._y); ctx.lineTo(handleX, handleY); ctx.stroke(); ctx.beginPath(); ctx.arc(handleX, handleY, 1.75, 0, Math.PI * 2, true); ctx.fill(); } } function drawSegments(ctx, path) { var segments = path._segments, length = segments.length, handleOut, outX, outY; function drawSegment(i) { var segment = segments[i], point = segment._point, x = point._x, y = point._y, handleIn = segment._handleIn; if (!handleOut) { ctx.moveTo(x, y); } else { if (handleIn.isZero() && handleOut.isZero()) { ctx.lineTo(x, y); } else { ctx.bezierCurveTo(outX, outY, handleIn._x + x, handleIn._y + y, x, y); } } handleOut = segment._handleOut; outX = handleOut._x + x; outY = handleOut._y + y; } for (var i = 0; i < length; i++) drawSegment(i); if (path._closed && length > 1) drawSegment(0); } function drawDashes(ctx, path, dashArray, dashOffset) { var flattener = new PathFlattener(path), from = dashOffset, to, i = 0; while (from < flattener.length) { to = from + dashArray[(i++) % dashArray.length]; flattener.drawPart(ctx, from, to); from = to + dashArray[(i++) % dashArray.length]; } } return { draw: function(ctx, param) { if (!param.compound) ctx.beginPath(); var fillColor = this.getFillColor(), strokeColor = this.getStrokeColor(), dashArray = this.getDashArray() || [], hasDash = !!dashArray.length; if (param.compound || param.selection || this._clipMask || fillColor || strokeColor && !hasDash) { drawSegments(ctx, this); } if (param.selection) { ctx.stroke(); drawHandles(ctx, this._segments); } else if (this._clipMask) { ctx.clip(); } else if (!param.compound && (fillColor || strokeColor)) { ctx.save(); this._setStyles(ctx); if (!fillColor || !strokeColor) ctx.globalAlpha = this._opacity; if (fillColor) { ctx.fillStyle = fillColor.getCanvasStyle(ctx); ctx.fill(); } if (strokeColor) { ctx.strokeStyle = strokeColor.getCanvasStyle(ctx); if (hasDash) { ctx.beginPath(); drawDashes(ctx, this, dashArray, this.getDashOffset()); } ctx.stroke(); } ctx.restore(); } } }; }, new function() { function getFirstControlPoints(rhs) { var n = rhs.length, x = [], tmp = [], b = 2; x[0] = rhs[0] / b; for (var i = 1; i < n; i++) { tmp[i] = 1 / b; b = (i < n - 1 ? 4 : 2) - tmp[i]; x[i] = (rhs[i] - x[i - 1]) / b; } for (var i = 1; i < n; i++) { x[n - i - 1] -= tmp[n - i] * x[n - i]; } return x; }; var styles = { getStrokeWidth: 'lineWidth', getStrokeJoin: 'lineJoin', getStrokeCap: 'lineCap', getMiterLimit: 'miterLimit' }; return { _setStyles: function(ctx) { for (var i in styles) { var style = this._style[i](); if (style) ctx[styles[i]] = style; } }, smooth: function() { var segments = this._segments, size = segments.length, n = size, overlap; if (size <= 2) return; if (this._closed) { overlap = Math.min(size, 4); n += Math.min(size, overlap) * 2; } else { overlap = 0; } var knots = []; for (var i = 0; i < size; i++) knots[i + overlap] = segments[i]._point; if (this._closed) { for (var i = 0; i < overlap; i++) { knots[i] = segments[i + size - overlap]._point; knots[i + size + overlap] = segments[i]._point; } } else { n--; } var rhs = []; for (var i = 1; i < n - 1; i++) rhs[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x; rhs[0] = knots[0]._x + 2 * knots[1]._x; rhs[n - 1] = 3 * knots[n - 1]._x; var x = getFirstControlPoints(rhs); for (var i = 1; i < n - 1; i++) rhs[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y; rhs[0] = knots[0]._y + 2 * knots[1]._y; rhs[n - 1] = 3 * knots[n - 1]._y; var y = getFirstControlPoints(rhs); if (this._closed) { for (var i = 0, j = size; i < overlap; i++, j++) { var f1 = (i / overlap); var f2 = 1 - f1; x[j] = x[i] * f1 + x[j] * f2; y[j] = y[i] * f1 + y[j] * f2; var ie = i + overlap, je = j + overlap; x[je] = x[ie] * f2 + x[je] * f1; y[je] = y[ie] * f2 + y[je] * f1; } n--; } var handleIn = null; for (var i = overlap; i <= n - overlap; i++) { var segment = segments[i - overlap]; if (handleIn) segment.setHandleIn(handleIn.subtract(segment._point)); if (i < n) { segment.setHandleOut( new Point(x[i], y[i]).subtract(segment._point)); if (i < n - 1) handleIn = new Point( 2 * knots[i + 1]._x - x[i + 1], 2 * knots[i + 1]._y - y[i + 1]); else handleIn = new Point( (knots[n]._x + x[n - 1]) / 2, (knots[n]._y + y[n - 1]) / 2); } } if (this._closed && handleIn) { var segment = this._segments[0]; segment.setHandleIn(handleIn.subtract(segment._point)); } } }; }, new function() { function getCurrentSegment(that) { var segments = that._segments; if (segments.length == 0) throw new Error('Use a moveTo() command first'); return segments[segments.length - 1]; } return { moveTo: function(point) { if (!this._segments.length) this._add([ new Segment(Point.read(arguments)) ]); }, moveBy: function(point) { throw new Error('moveBy() is unsupported on Path items.'); }, lineTo: function(point) { this._add([ new Segment(Point.read(arguments)) ]); }, cubicCurveTo: function(handle1, handle2, to) { handle1 = Point.read(arguments, 0, 1); handle2 = Point.read(arguments, 1, 1); to = Point.read(arguments, 2, 1); var current = getCurrentSegment(this); current.setHandleOut(handle1.subtract(current._point)); this._add([ new Segment(to, handle2.subtract(to)) ]); }, quadraticCurveTo: function(handle, to) { handle = Point.read(arguments, 0, 1); to = Point.read(arguments, 1, 1); var current = getCurrentSegment(this)._point; this.cubicCurveTo( handle.add(current.subtract(handle).multiply(1/3)), handle.add(to.subtract(handle).multiply(1/3)), to ); }, curveTo: function(through, to, parameter) { through = Point.read(arguments, 0, 1); to = Point.read(arguments, 1, 1); var t = Base.pick(parameter, 0.5), t1 = 1 - t, current = getCurrentSegment(this)._point, handle = through.subtract(current.multiply(t1 * t1)) .subtract(to.multiply(t * t)).divide(2 * t * t1); if (handle.isNaN()) throw new Error( 'Cannot put a curve through points with parameter = ' + t); this.quadraticCurveTo(handle, to); }, arcTo: function(to, clockwise ) { var current = getCurrentSegment(this), from = current._point, through; if (clockwise === undefined) clockwise = true; if (typeof clockwise === 'boolean') { to = Point.read(arguments, 0, 1); var middle = from.add(to).divide(2), through = middle.add(middle.subtract(from).rotate( clockwise ? -90 : 90)); } else { through = Point.read(arguments, 0, 1); to = Point.read(arguments, 1, 1); } var l1 = new Line(from.add(through).divide(2), through.subtract(from).rotate(90)), l2 = new Line(through.add(to).divide(2), to.subtract(through).rotate(90)), center = l1.intersect(l2), line = new Line(from, to, true), throughSide = line.getSide(through); if (!center) { if (!throughSide) return this.lineTo(to); throw new Error("Cannot put an arc through the given points: " + [from, through, to]); } var vector = from.subtract(center), radius = vector.getLength(), extent = vector.getDirectedAngle(to.subtract(center)), centerSide = line.getSide(center); if (centerSide == 0) { extent = throughSide * Math.abs(extent); } else if (throughSide == centerSide) { extent -= 360 * (extent < 0 ? -1 : 1); } var ext = Math.abs(extent), count = ext >= 360 ? 4 : Math.ceil(ext / 90), inc = extent / count, half = inc * Math.PI / 360, z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)), segments = []; for (var i = 0; i <= count; i++) { var pt = i < count ? center.add(vector) : to; var out = i < count ? vector.rotate(90).multiply(z) : null; if (i == 0) { current.setHandleOut(out); } else { segments.push( new Segment(pt, vector.rotate(-90).multiply(z), out)); } vector = vector.rotate(inc); } this._add(segments); }, lineBy: function(vector) { vector = Point.read(arguments); var current = getCurrentSegment(this); this.lineTo(current._point.add(vector)); }, curveBy: function(throughVector, toVector, parameter) { throughVector = Point.read(throughVector); toVector = Point.read(toVector); var current = getCurrentSegment(this)._point; this.curveTo(current.add(throughVector), current.add(toVector), parameter); }, arcBy: function(throughVector, toVector) { throughVector = Point.read(throughVector); toVector = Point.read(toVector); var current = getCurrentSegment(this)._point; this.arcBy(current.add(throughVector), current.add(toVector)); }, closePath: function() { this.setClosed(true); } }; }, new function() { function getBounds(that, matrix, strokePadding) { var segments = that._segments, first = segments[0]; if (!first) return null; var coords = new Array(6), prevCoords = new Array(6); if (matrix && matrix.isIdentity()) matrix = null; first._transformCoordinates(matrix, prevCoords, false); var min = prevCoords.slice(0, 2), max = min.slice(0), tMin = Numerical.TOLERANCE, tMax = 1 - tMin; function processSegment(segment) { segment._transformCoordinates(matrix, coords, false); for (var i = 0; i < 2; i++) { var v0 = prevCoords[i], v1 = prevCoords[i + 4], v2 = coords[i + 2], v3 = coords[i]; function add(value, t) { var padding = 0; if (value == null) { var u = 1 - t; value = u * u * u * v0 + 3 * u * u * t * v1 + 3 * u * t * t * v2 + t * t * t * v3; padding = strokePadding ? strokePadding[i] : 0; } var left = value - padding, right = value + padding; if (left < min[i]) min[i] = left; if (right > max[i]) max[i] = right; } add(v3, null); var a = 3 * (v1 - v2) - v0 + v3, b = 2 * (v0 + v2) - 4 * v1, c = v1 - v0; if (a == 0) { if (b == 0) continue; var t = -c / b; if (tMin < t && t < tMax) add(null, t); continue; } var q = b * b - 4 * a * c; if (q < 0) continue; var sqrt = Math.sqrt(q), f = -0.5 / a, t1 = (b - sqrt) * f, t2 = (b + sqrt) * f; if (tMin < t1 && t1 < tMax) add(null, t1); if (tMin < t2 && t2 < tMax) add(null, t2); } var tmp = prevCoords; prevCoords = coords; coords = tmp; } for (var i = 1, l = segments.length; i < l; i++) processSegment(segments[i]); if (that._closed) processSegment(first); return Rectangle.create(min[0], min[1], max[0] - min[0], max[1] - min[1]); } function getPenPadding(radius, matrix) { if (!matrix) return [radius, radius]; var mx = matrix.createShiftless(), hor = mx.transform(new Point(radius, 0)), ver = mx.transform(new Point(0, radius)), phi = hor.getAngleInRadians(), a = hor.getLength(), b = ver.getLength(); var tx = - Math.atan(b * Math.tan(phi)), ty = + Math.atan(b / Math.tan(phi)), x = a * Math.cos(tx) * Math.cos(phi) - b * Math.sin(tx) * Math.sin(phi), y = b * Math.sin(ty) * Math.cos(phi) + a * Math.cos(ty) * Math.sin(phi); return [Math.abs(x), Math.abs(y)]; } return { getBounds: function() { var useCache = arguments[0] === undefined; if (useCache && this._bounds) return this._bounds; var bounds = this._createBounds(getBounds(this, arguments[0])); if (useCache) this._bounds = bounds; return bounds; }, getStrokeBounds: function() { if (!this._style._strokeColor || !this._style._strokeWidth) return this.getBounds.apply(this, arguments); var useCache = arguments[0] === undefined; if (useCache && this._strokeBounds) return this._strokeBounds; var matrix = arguments[0], width = this.getStrokeWidth(), radius = width / 2, padding = getPenPadding(radius, matrix), join = this.getStrokeJoin(), cap = this.getStrokeCap(), miter = this.getMiterLimit() * width / 2, segments = this._segments, length = segments.length, bounds = getBounds(this, matrix, getPenPadding(radius)); var joinBounds = new Rectangle(new Size(padding).multiply(2)); function add(point) { bounds = bounds.include(matrix ? matrix.transform(point) : point); } function addBevelJoin(curve, t) { var point = curve.getPoint(t), normal = curve.getNormal(t).normalize(radius); add(point.add(normal)); add(point.subtract(normal)); } function addJoin(segment, join) { if (join === 'round' || !segment._handleIn.isZero() && !segment._handleOut.isZero()) { bounds = bounds.unite(joinBounds.setCenter(matrix ? matrix.transform(segment._point) : segment._point)); } else if (join == 'bevel') { var curve = segment.getCurve(); addBevelJoin(curve, 0); addBevelJoin(curve.getPrevious(), 1); } else if (join == 'miter') { var curve2 = segment.getCurve(), curve1 = curve2.getPrevious(), point = curve2.getPoint(0), normal1 = curve1.getNormal(1).normalize(radius), normal2 = curve2.getNormal(0).normalize(radius), line1 = new Line(point.subtract(normal1), new Point(-normal1.y, normal1.x)), line2 = new Line(point.subtract(normal2), new Point(-normal2.y, normal2.x)), corner = line1.intersect(line2); if (!corner || point.getDistance(corner) > miter) { addJoin(segment, 'bevel'); } else { add(corner); } } } function addCap(segment, cap, t) { switch (cap) { case 'round': return addJoin(segment, cap); case 'butt': case 'square': var curve = segment.getCurve(), point = curve.getPoint(t), normal = curve.getNormal(t).normalize(radius); if (cap === 'square') point = point.add(normal.y, -normal.x); add(point.add(normal)); add(point.subtract(normal)); break; } } for (var i = 1, l = length - (this._closed ? 0 : 1); i < l; i++) { addJoin(segments[i], join); } if (this._closed) { addJoin(segments[0], join); } else { addCap(segments[0], cap, 0); addCap(segments[length - 1], cap, 1); } if (useCache) this._strokeBounds = bounds; return bounds; }, getHandleBounds: function() { var matrix = arguments[0], useCache = matrix === undefined; if (useCache && this._handleBounds) return this._handleBounds; var coords = new Array(6), stroke = arguments[1] / 2 || 0, join = arguments[2] / 2 || 0, open = !this._closed, x1 = Infinity, x2 = -x1, y1 = x1, y2 = x2; for (var i = 0, l = this._segments.length; i < l; i++) { var segment = this._segments[i]; segment._transformCoordinates(matrix, coords, false); for (var j = 0; j < 6; j += 2) { var padding = j == 0 ? join : stroke, x = coords[j], y = coords[j + 1], xn = x - padding, xx = x + padding, yn = y - padding, yx = y + padding; if (xn < x1) x1 = xn; if (xx > x2) x2 = xx; if (yn < y1) y1 = yn; if (yx > y2) y2 = yx; } } var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1); if (useCache) this._handleBounds = bounds; return bounds; }, getRoughBounds: function() { var useCache = arguments[0] === undefined; if (useCache && this._roughBounds) return this._roughBounds; var bounds = this.getHandleBounds(arguments[0], this.strokeWidth, this.getStrokeJoin() == 'miter' ? this.strokeWidth * this.getMiterLimit() : this.strokeWidth); if (useCache) this._roughBounds = bounds; return bounds; } }; }); Path.inject({ statics: new function() { var kappa = 2 / 3 * (Math.sqrt(2) - 1); var ovalSegments = [ new Segment([0, 0.5], [0, kappa ], [0, -kappa]), new Segment([0.5, 0], [-kappa, 0], [kappa, 0 ]), new Segment([1, 0.5], [0, -kappa], [0, kappa ]), new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0]) ]; return { Line: function() { var step = Math.floor(arguments.length / 2); return new Path( Segment.read(arguments, 0, step), Segment.read(arguments, step, step) ); }, Rectangle: function(rect) { rect = Rectangle.read(arguments); var left = rect.x, top = rect.y right = left + rect.width, bottom = top + rect.height, path = new Path(); path._add([ new Segment(Point.create(left, bottom)), new Segment(Point.create(left, top)), new Segment(Point.create(right, top)), new Segment(Point.create(right, bottom)) ]); path._closed = true; return path; }, RoundRectangle: function(rect, size) { if (arguments.length == 2) { rect = Rectangle.read(arguments, 0, 1); size = Size.read(arguments, 1, 1); } else if (arguments.length == 6) { rect = Rectangle.read(arguments, 0, 4); size = Size.read(arguments, 4, 2); } size = Size.min(size, rect.getSize(true).divide(2)); var path = new Path(), uSize = size.multiply(kappa * 2), bl = rect.getBottomLeft(true), tl = rect.getTopLeft(true), tr = rect.getTopRight(true), br = rect.getBottomRight(true); path._add([ new Segment(bl.add(size.width, 0), null, [-uSize.width, 0]), new Segment(bl.subtract(0, size.height), [0, uSize.height], null), new Segment(tl.add(0, size.height), null, [0, -uSize.height]), new Segment(tl.add(size.width, 0), [-uSize.width, 0], null), new Segment(tr.subtract(size.width, 0), null, [uSize.width, 0]), new Segment(tr.add(0, size.height), [0, -uSize.height], null), new Segment(br.subtract(0, size.height), null, [0, uSize.height]), new Segment(br.subtract(size.width, 0), [uSize.width, 0], null) ]); path._closed = true; return path; }, Oval: function(rect) { rect = Rectangle.read(arguments); var path = new Path(), point = rect.getPoint(true), size = rect.getSize(true), segments = new Array(4); for (var i = 0; i < 4; i++) { var segment = ovalSegments[i]; segments[i] = new Segment( segment._point.multiply(size).add(point), segment._handleIn.multiply(size), segment._handleOut.multiply(size) ); } path._add(segments); path._closed = true; return path; }, Circle: function(center, radius) { if (arguments.length == 3) { center = Point.read(arguments, 0, 2); radius = arguments[2]; } else { center = Point.read(arguments, 0, 1); } return Path.Oval(new Rectangle(center.subtract(radius), Size.create(radius * 2, radius * 2))); }, Arc: function(from, through, to) { var path = new Path(); path.moveTo(from); path.arcTo(through, to); return path; }, RegularPolygon: function(center, numSides, radius) { center = Point.read(arguments, 0, 1); var path = new Path(), step = 360 / numSides, three = !(numSides % 3), vector = new Point(0, three ? -radius : radius), offset = three ? -1 : 0.5, segments = new Array(numSides); for (var i = 0; i < numSides; i++) { segments[i] = new Segment(center.add( vector.rotate((i + offset) * step))); } path._add(segments); path._closed = true; return path; }, Star: function(center, numPoints, radius1, radius2) { center = Point.read(arguments, 0, 1); numPoints *= 2; var path = new Path(), step = 360 / numPoints, vector = new Point(0, -1), segments = new Array(numPoints); for (var i = 0; i < numPoints; i++) { segments[i] = new Segment(center.add( vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1))); } path._add(segments); path._closed = true; return path; } }; }}); var CompoundPath = this.CompoundPath = PathItem.extend({ initialize: function(paths) { this.base(); this._children = []; this._namedChildren = {}; var items = !paths || !Array.isArray(paths) || typeof paths[0] !== 'object' ? arguments : paths; this.addChildren(items); }, insertChild: function(index, item) { this.base(index, item); if (item._clockwise === undefined) item.setClockwise(item._index == 0); }, simplify: function() { if (this._children.length == 1) { var child = this._children[0]; child.insertAbove(this); this.remove(); return child; } return this; }, smooth: function() { for (var i = 0, l = this._children.length; i < l; i++) this._children[i].smooth(); }, draw: function(ctx, param) { var l = this._children.length; if (l == 0) { return; } var firstChild = this._children[0]; ctx.beginPath(); param.compound = true; for (var i = 0; i < l; i++) Item.draw(this._children[i], ctx, param); firstChild._setStyles(ctx); var fillColor = firstChild.getFillColor(), strokeColor = firstChild.getStrokeColor(); if (fillColor) { ctx.fillStyle = fillColor.getCanvasStyle(ctx); ctx.fill(); } if (strokeColor) { ctx.strokeStyle = strokeColor.getCanvasStyle(ctx); ctx.stroke(); } param.compound = false; } }, new function() { function getCurrentPath(that) { if (!that._children.length) throw new Error('Use a moveTo() command first'); return that._children[that._children.length - 1]; } var fields = { moveTo: function(point) { var path = new Path(); this.addChild(path); path.moveTo.apply(path, arguments); }, moveBy: function(point) { this.moveTo(getCurrentPath(this).getLastSegment()._point.add( Point.read(arguments))); }, closePath: function() { getCurrentPath(this).setClosed(true); } }; Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo', 'arcTo', 'lineBy', 'curveBy', 'arcBy'], function(key) { fields[key] = function() { var path = getCurrentPath(this); path[key].apply(path, arguments); }; }); return fields; }); var PathFlattener = Base.extend({ initialize: function(path) { this.curves = []; this.parts = []; this.length = 0; this.index = 0; var segments = path._segments, segment1 = segments[0], segment2, that = this; function addCurve(segment1, segment2) { var curve = Curve.getValues(segment1, segment2); that.curves.push(curve); that._computeParts(curve, segment1._index, 0, 1); } for (var i = 1, l = segments.length; i < l; i++) { segment2 = segments[i]; addCurve(segment1, segment2); segment1 = segment2; } if (path._closed) addCurve(segment2, segments[0]); }, _computeParts: function(curve, index, minT, maxT) { if ((maxT - minT) > 1 / 32 && !Curve.isFlatEnough(curve)) { var curves = Curve.subdivide(curve); var halfT = (minT + maxT) / 2; this._computeParts(curves[0], index, minT, halfT); this._computeParts(curves[1], index, halfT, maxT); } else { var x = curve[6] - curve[0], y = curve[7] - curve[1], dist = Math.sqrt(x * x + y * y); if (dist > Numerical.TOLERANCE) { this.length += dist; this.parts.push({ offset: this.length, value: maxT, index: index }); } } }, getParameterAt: function(offset) { var i, j = this.index; for (;;) { i = j; if (j == 0 || this.parts[--j].offset < offset) break; } for (var l = this.parts.length; i < l; i++) { var part = this.parts[i]; if (part.offset >= offset) { this.index = i; var prev = this.parts[i - 1]; var prevVal = prev && prev.index == part.index ? prev.value : 0, prevLen = prev ? prev.offset : 0; return { value: prevVal + (part.value - prevVal) * (offset - prevLen) / (part.offset - prevLen), index: part.index }; } } var part = this.parts[this.parts.length - 1]; return { value: 1, index: part.index }; }, evaluate: function(offset, type) { var param = this.getParameterAt(offset); return Curve.evaluate(this.curves[param.index], param.value, type); }, drawPart: function(ctx, from, to) { from = this.getParameterAt(from); to = this.getParameterAt(to); for (var i = from.index; i <= to.index; i++) { var curve = Curve.getPart(this.curves[i], i == from.index ? from.value : 0, i == to.index ? to.value : 1); if (i == from.index) ctx.moveTo(curve[0], curve[1]); ctx.bezierCurveTo.apply(ctx, curve.slice(2)); } } }); var PathFitter = Base.extend({ initialize: function(path, error) { this.points = []; var segments = path._segments, prev; for (var i = 0, l = segments.length; i < l; i++) { var point = segments[i].point.clone(); if (!prev || !prev.equals(point)) { this.points.push(point); prev = point; } } this.error = error; }, fit: function() { this.segments = [new Segment(this.points[0])]; this.fitCubic(0, this.points.length - 1, this.points[1].subtract(this.points[0]).normalize(), this.points[this.points.length - 2].subtract( this.points[this.points.length - 1]).normalize()); return this.segments; }, fitCubic: function(first, last, tan1, tan2) { if (last - first == 1) { var pt1 = this.points[first], pt2 = this.points[last], dist = pt1.getDistance(pt2) / 3; this.addCurve([pt1, pt1.add(tan1.normalize(dist)), pt2.add(tan2.normalize(dist)), pt2]); return; } var uPrime = this.chordLengthParameterize(first, last), maxError = Math.max(this.error, this.error * this.error), error, split; for (var i = 0; i <= 4; i++) { var curve = this.generateBezier(first, last, uPrime, tan1, tan2); var max = this.findMaxError(first, last, curve, uPrime); if (max.error < this.error) { this.addCurve(curve); return; } split = max.index; if (max.error >= maxError) break; this.reparameterize(first, last, uPrime, curve); maxError = max.error; } var V1 = this.points[split - 1].subtract(this.points[split]), V2 = this.points[split].subtract(this.points[split + 1]), tanCenter = V1.add(V2).divide(2).normalize(); this.fitCubic(first, split, tan1, tanCenter); this.fitCubic(split, last, tanCenter.negate(), tan2); }, addCurve: function(curve) { var prev = this.segments[this.segments.length - 1]; prev.setHandleOut(curve[1].subtract(curve[0])); this.segments.push( new Segment(curve[3], curve[2].subtract(curve[3]))); }, generateBezier: function(first, last, uPrime, tan1, tan2) { var epsilon = Numerical.EPSILON, pt1 = this.points[first], pt2 = this.points[last], C = [[0, 0], [0, 0]], X = [0, 0]; for (var i = 0, l = last - first + 1; i < l; i++) { var u = uPrime[i], t = 1 - u, b = 3 * u * t, b0 = t * t * t, b1 = b * t, b2 = b * u, b3 = u * u * u, a1 = tan1.normalize(b1), a2 = tan2.normalize(b2), tmp = this.points[first + i] .subtract(pt1.multiply(b0 + b1)) .subtract(pt2.multiply(b2 + b3)); C[0][0] += a1.dot(a1); C[0][1] += a1.dot(a2); C[1][0] = C[0][1]; C[1][1] += a2.dot(a2); X[0] += a1.dot(tmp); X[1] += a2.dot(tmp); } var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1], alpha1, alpha2; if (Math.abs(detC0C1) > epsilon) { var detC0X = C[0][0] * X[1] - C[1][0] * X[0], detXC1 = X[0] * C[1][1] - X[1] * C[0][1]; alpha1 = detXC1 / detC0C1; alpha2 = detC0X / detC0C1; } else { var c0 = C[0][0] + C[0][1], c1 = C[1][0] + C[1][1]; if (Math.abs(c0) > epsilon) { alpha1 = alpha2 = X[0] / c0; } else if (Math.abs(c0) > epsilon) { alpha1 = alpha2 = X[1] / c1; } else { alpha1 = alpha2 = 0.; } } var segLength = pt2.getDistance(pt1); epsilon *= segLength; if (alpha1 < epsilon || alpha2 < epsilon) { alpha1 = alpha2 = segLength / 3; } return [pt1, pt1.add(tan1.normalize(alpha1)), pt2.add(tan2.normalize(alpha2)), pt2]; }, reparameterize: function(first, last, u, curve) { for (var i = first; i <= last; i++) { u[i - first] = this.findRoot(curve, this.points[i], u[i - first]); } }, findRoot: function(curve, point, u) { var curve1 = [], curve2 = []; for (var i = 0; i <= 2; i++) { curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3); } for (var i = 0; i <= 1; i++) { curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2); } var pt = this.evaluate(3, curve, u), pt1 = this.evaluate(2, curve1, u), pt2 = this.evaluate(1, curve2, u), diff = pt.subtract(point), df = pt1.dot(pt1) + diff.dot(pt2); if (Math.abs(df) < Numerical.TOLERANCE) return u; return u - diff.dot(pt1) / df; }, evaluate: function(degree, curve, t) { var tmp = curve.slice(); for (var i = 1; i <= degree; i++) { for (var j = 0; j <= degree - i; j++) { tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t)); } } return tmp[0]; }, chordLengthParameterize: function(first, last) { var u = [0]; for (var i = first + 1; i <= last; i++) { u[i - first] = u[i - first - 1] + this.points[i].getDistance(this.points[i - 1]); } for (var i = 1, m = last - first; i <= m; i++) { u[i] /= u[m]; } return u; }, findMaxError: function(first, last, curve, u) { var index = Math.floor((last - first + 1) / 2), maxDist = 0; for (var i = first + 1; i < last; i++) { var P = this.evaluate(3, curve, u[i - first]); var v = P.subtract(this.points[i]); var dist = v.x * v.x + v.y * v.y; if (dist >= maxDist) { maxDist = dist; index = i; } } return { error: maxDist, index: index }; } }); var TextItem = this.TextItem = Item.extend({ initialize: function() { this.base(); this._content = ''; this._characterStyle = CharacterStyle.create(this); this.setCharacterStyle(this._project.getCurrentStyle()); this._paragraphStyle = ParagraphStyle.create(this); this.setParagraphStyle(); }, _clone: function(copy) { copy._content = this._content; copy.setCharacterStyle(this._characterStyle); copy.setParagraphStyle(this._paragraphStyle); return this.base(copy); }, getContent: function() { return this._content; }, setContent: function(content) { this._changed(Change.CONTENT); this._content = '' + content; }, getCharacterStyle: function() { return this._characterStyle; }, setCharacterStyle: function(style) { this._characterStyle.initialize(style); }, getParagraphStyle: function() { return this._paragraphStyle; }, setParagraphStyle: function(style) { this._paragraphStyle.initialize(style); } }); var PointText = this.PointText = TextItem.extend({ initialize: function(point) { this.base(); this._point = Point.read(arguments).clone(); this._matrix = new Matrix().translate(this._point); }, clone: function() { var copy = this._clone(new PointText(this._point)); copy._matrix.initialize(this._matrix); return copy; }, getPoint: function() { return LinkedPoint.create(this, 'setPoint', this._point.x, this._point.y); }, setPoint: function(point) { this.translate(Point.read(arguments).subtract(this._point)); }, getPosition: function() { return this.getPoint(); }, setPosition: function(point) { this.setPoint.apply(this, arguments); }, _transform: function(matrix, flags) { this._matrix.preConcatenate(matrix); matrix._transformPoint(this._point, this._point); }, draw: function(ctx) { if (!this._content) return; ctx.save(); ctx.font = this.getFontSize() + 'pt ' + this.getFont(); ctx.textAlign = this.getJustification(); this._matrix.applyToContext(ctx); var fillColor = this.getFillColor(); var strokeColor = this.getStrokeColor(); if (!fillColor || !strokeColor) ctx.globalAlpha = this._opacity; if (fillColor) { ctx.fillStyle = fillColor.getCanvasStyle(ctx); ctx.fillText(this._content, 0, 0); } if (strokeColor) { ctx.strokeStyle = strokeColor.getCanvasStyle(ctx); ctx.strokeText(this._content, 0, 0); } ctx.restore(); } }); var Style = Item.extend({ initialize: function(style) { var clone = style instanceof Style; return Base.each(this._defaults, function(value, key) { value = style && style[key] || value; this[key] = value && clone && value.clone ? value.clone() : value; }, this); }, statics: { create: function(item) { var style = new this(this.dont); style._item = item; return style; }, extend: function(src) { var styleKey = src._style, flags = src._flags || {}; src._owner.inject(Base.each(src._defaults, function(value, key) { var isColor = !!key.match(/Color$/), part = Base.capitalize(key), set = 'set' + part, get = 'get' + part; src[set] = function(value) { var children = this._item && this._item._children; value = isColor ? Color.read(arguments) : value; if (children) { for (var i = 0, l = children.length; i < l; i++) children[i][styleKey][set](value); } else { var old = this['_' + key]; if (old != value && !(old && old.equals && old.equals(value))) { this['_' + key] = value; if (isColor) { if (old) old._removeOwner(this._item); if (value) value._addOwner(this._item); } if (this._item) this._item._changed(flags[key] || Change.STYLE); } } return this; }; src[get] = function() { var children = this._item && this._item._children, style; if (!children) return this['_' + key]; for (var i = 0, l = children.length; i < l; i++) { var childStyle = children[i][styleKey][get](); if (!style) { style = childStyle; } else if (style != childStyle && !(style && style.equals && style.equals(childStyle))) { return undefined; } } return style; }; this[set] = function(value) { this[styleKey][set](value); return this; }; this[get] = function() { return this[styleKey][get](); }; }, {})); return this.base(src); } } }); var PathStyle = this.PathStyle = Style.extend({ _defaults: { fillColor: undefined, strokeColor: undefined, strokeWidth: 1, strokeCap: 'butt', strokeJoin: 'miter', miterLimit: 10, dashOffset: 0, dashArray: [] }, _flags: { strokeWidth: Change.STROKE, strokeCap: Change.STROKE, strokeJoin: Change.STROKE, miterLimit: Change.STROKE }, _owner: Item, _style: '_style' }); var ParagraphStyle = this.ParagraphStyle = Style.extend({ _defaults: { justification: 'left' }, _owner: TextItem, _style: '_paragraphStyle' }); var CharacterStyle = this.CharacterStyle = PathStyle.extend({ _defaults: Base.merge(PathStyle.prototype._defaults, { fillColor: 'black', fontSize: 10, font: 'sans-serif' }), _owner: TextItem, _style: '_characterStyle' }); var Color = this.Color = Base.extend(new function() { var components = { gray: ['gray'], rgb: ['red', 'green', 'blue'], hsb: ['hue', 'saturation', 'brightness'], hsl: ['hue', 'saturation', 'lightness'] }; var colorCache = {}, colorContext; function nameToRgbColor(name) { var color = colorCache[name]; if (color) return color.clone(); if (!colorContext) { var canvas = CanvasProvider.getCanvas(Size.create(1, 1)); colorContext = canvas.getContext('2d'); colorContext.globalCompositeOperation = 'copy'; } colorContext.fillStyle = 'rgba(0,0,0,0)'; colorContext.fillStyle = name; colorContext.fillRect(0, 0, 1, 1); var data = colorContext.getImageData(0, 0, 1, 1).data, rgb = [data[0] / 255, data[1] / 255, data[2] / 255]; return (colorCache[name] = RgbColor.read(rgb)).clone(); } function hexToRgbColor(string) { var hex = string.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); if (hex.length >= 4) { var rgb = new Array(3); for (var i = 0; i < 3; i++) { var channel = hex[i + 1]; rgb[i] = parseInt(channel.length == 1 ? channel + channel : channel, 16) / 255; } return RgbColor.read(rgb); } } var hsbIndices = [ [0, 3, 1], [2, 0, 1], [1, 0, 3], [1, 2, 0], [3, 1, 0], [0, 1, 2] ]; var converters = { 'rgb-hsb': function(color) { var r = color._red, g = color._green, b = color._blue, max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min, h = delta == 0 ? 0 : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) : max == g ? (b - r) / delta + 2 : (r - g) / delta + 4) * 60, s = max == 0 ? 0 : delta / max, v = max; return new HsbColor(h, s, v, color._alpha); }, 'hsb-rgb': function(color) { var h = (color._hue / 60) % 6, s = color._saturation, b = color._brightness, i = Math.floor(h), f = h - i, i = hsbIndices[i], v = [ b, b * (1 - s), b * (1 - s * f), b * (1 - s * (1 - f)) ]; return new RgbColor(v[i[0]], v[i[1]], v[i[2]], color._alpha); }, 'rgb-hsl': function(color) { var r = color._red, g = color._green, b = color._blue, max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min, achromatic = delta == 0, h = achromatic ? 0 : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) : max == g ? (b - r) / delta + 2 : (r - g) / delta + 4) * 60, l = (max + min) / 2, s = achromatic ? 0 : l < 0.5 ? delta / (max + min) : delta / (2 - max - min); return new HslColor(h, s, l, color._alpha); }, 'hsl-rgb': function(color) { var s = color._saturation, h = color._hue / 360, l = color._lightness, t1, t2, c; if (s == 0) return new RgbColor(l, l, l, color._alpha); var t3s = [ h + 1 / 3, h, h - 1 / 3 ], t2 = l < 0.5 ? l * (1 + s) : l + s - l * s, t1 = 2 * l - t2, c = []; for (var i = 0; i < 3; i++) { var t3 = t3s[i]; if (t3 < 0) t3 += 1; if (t3 > 1) t3 -= 1; c[i] = 6 * t3 < 1 ? t1 + (t2 - t1) * 6 * t3 : 2 * t3 < 1 ? t2 : 3 * t3 < 2 ? t1 + (t2 - t1) * ((2 / 3) - t3) * 6 : t1; } return new RgbColor(c[0], c[1], c[2], color._alpha); }, 'rgb-gray': function(color) { return new GrayColor(1 - (color._red * 0.2989 + color._green * 0.587 + color._blue * 0.114), color._alpha); }, 'gray-rgb': function(color) { var comp = 1 - color._gray; return new RgbColor(comp, comp, comp, color._alpha); }, 'gray-hsb': function(color) { return new HsbColor(0, 0, 1 - color._gray, color._alpha); }, 'gray-hsl': function(color) { return new HslColor(0, 0, 1 - color._gray, color._alpha); } }; var fields = { _readNull: true, initialize: function(arg) { var isArray = Array.isArray(arg), type = this._colorType; if (typeof arg === 'object' && !isArray) { if (!type) { return arg.red !== undefined ? new RgbColor(arg.red, arg.green, arg.blue, arg.alpha) : arg.gray !== undefined ? new GrayColor(arg.gray, arg.alpha) : arg.lightness !== undefined ? new HslColor(arg.hue, arg.saturation, arg.lightness, arg.alpha) : arg.hue !== undefined ? new HsbColor(arg.hue, arg.saturation, arg.brightness, arg.alpha) : new RgbColor(); } else { return Color.read(arguments).convert(type); } } else if (typeof arg === 'string') { var rgbColor = arg.match(/^#[0-9a-f]{3,6}$/i) ? hexToRgbColor(arg) : nameToRgbColor(arg); return type ? rgbColor.convert(type) : rgbColor; } else { var components = isArray ? arg : Array.prototype.slice.call(arguments); if (!type) { if (components.length >= 3) return new RgbColor(components); return new GrayColor(components); } else { Base.each(this._components, function(name, i) { var value = components[i]; this['_' + name] = value !== undefined ? value : null; }, this); } } }, clone: function() { var ctor = this.constructor, copy = new ctor(ctor.dont), components = this._components; for (var i = 0, l = components.length; i < l; i++) { var key = '_' + components[i]; copy[key] = this[key]; } return copy; }, convert: function(type) { var converter; return this._colorType == type ? this.clone() : (converter = converters[this._colorType + '-' + type]) ? converter(this) : converters['rgb-' + type]( converters[this._colorType + '-rgb'](this)); }, statics: { extend: function(src) { src.beans = true; if (src._colorType) { var comps = components[src._colorType]; src._components = comps.concat(['alpha']); Base.each(comps, function(name) { var isHue = name === 'hue', part = Base.capitalize(name), name = '_' + name; this['get' + part] = function() { return this[name]; }; this['set' + part] = function(value) { this[name] = isHue ? ((value % 360) + 360) % 360 : Math.min(Math.max(value, 0), 1); this._changed(); return this; }; }, src); } return this.base(src); } } }; Base.each(components, function(comps, type) { Base.each(comps, function(component) { var part = Base.capitalize(component); fields['get' + part] = function() { return this.convert(type)[component]; }; fields['set' + part] = function(value) { var color = this.convert(type); color[component] = value; color = color.convert(this._colorType); for (var i = 0, l = this._components.length; i < l; i++) { var key = this._components[i]; this[key] = color[key]; } }; }); }); return fields; }, { _changed: function() { this._cssString = null; for (var i = 0, l = this._owners && this._owners.length; i < l; i++) this._owners[i]._changed(Change.STYLE); }, _addOwner: function(item) { if (!this._owners) this._owners = []; this._owners.push(item); }, _removeOwner: function(item) { var index = this._owners ? this._owners.indexOf(item) : -1; if (index != -1) { this._owners.splice(index, 1); if (this._owners.length == 0) delete this._owners; } }, getType: function() { return this._colorType; }, getComponents: function() { var length = this._components.length; var comps = new Array(length); for (var i = 0; i < length; i++) comps[i] = this['_' + this._components[i]]; return comps; }, getAlpha: function() { return this._alpha != null ? this._alpha : 1; }, setAlpha: function(alpha) { this._alpha = alpha == null ? null : Math.min(Math.max(alpha, 0), 1); this._changed(); return this; }, hasAlpha: function() { return this._alpha != null; }, equals: function(color) { if (color && color._colorType === this._colorType) { for (var i = 0, l = this._components.length; i < l; i++) { var component = '_' + this._components[i]; if (this[component] !== color[component]) return false; } return true; } return false; }, toString: function() { var parts = [], format = Base.formatNumber; for (var i = 0, l = this._components.length; i < l; i++) { var component = this._components[i], value = this['_' + component]; if (component === 'alpha' && value == null) value = 1; parts.push(component + ': ' + format(value)); } return '{ ' + parts.join(', ') + ' }'; }, toCssString: function() { if (!this._cssString) { var color = this.convert('rgb'), alpha = color.getAlpha(), components = [ Math.round(color._red * 255), Math.round(color._green * 255), Math.round(color._blue * 255), alpha != null ? alpha : 1 ]; this._cssString = 'rgba(' + components.join(', ') + ')'; } return this._cssString; }, getCanvasStyle: function() { return this.toCssString(); } }); var GrayColor = this.GrayColor = Color.extend({ _colorType: 'gray' }); var RgbColor = this.RgbColor = this.RGBColor = Color.extend({ _colorType: 'rgb' }); var HsbColor = this.HsbColor = this.HSBColor = Color.extend({ _colorType: 'hsb' }); var HslColor = this.HslColor = this.HSLColor = Color.extend({ _colorType: 'hsl' }); var GradientColor = this.GradientColor = Color.extend({ initialize: function(gradient, origin, destination, hilite) { this.gradient = gradient || new Gradient(); this.setOrigin(origin); this.setDestination(destination); if (hilite) this.setHilite(hilite); }, clone: function() { return new GradientColor(this.gradient, this._origin, this._destination, this._hilite); }, getOrigin: function() { return this._origin; }, setOrigin: function(origin) { origin = Point.read(arguments).clone(); this._origin = origin; if (this._destination) this._radius = this._destination.getDistance(this._origin); this._changed(); return this; }, getDestination: function() { return this._destination; }, setDestination: function(destination) { destination = Point.read(arguments).clone(); this._destination = destination; this._radius = this._destination.getDistance(this._origin); this._changed(); return this; }, getHilite: function() { return this._hilite; }, setHilite: function(hilite) { hilite = Point.read(arguments).clone(); var vector = hilite.subtract(this._origin); if (vector.getLength() > this._radius) { this._hilite = this._origin.add( vector.normalize(this._radius - 0.1)); } else { this._hilite = hilite; } this._changed(); return this; }, getCanvasStyle: function(ctx) { var gradient; if (this.gradient.type === 'linear') { gradient = ctx.createLinearGradient(this._origin.x, this._origin.y, this._destination.x, this._destination.y); } else { var origin = this._hilite || this._origin; gradient = ctx.createRadialGradient(origin.x, origin.y, 0, this._origin.x, this._origin.y, this._radius); } for (var i = 0, l = this.gradient._stops.length; i < l; i++) { var stop = this.gradient._stops[i]; gradient.addColorStop(stop._rampPoint, stop._color.toCssString()); } return gradient; }, equals: function(color) { return color == this || color && color._colorType === this._colorType && this.gradient.equals(color.gradient) && this._origin.equals(color._origin) && this._destination.equals(color._destination); }, transform: function(matrix) { matrix._transformPoint(this._origin, this._origin, true); matrix._transformPoint(this._destination, this._destination, true); if (this._hilite) matrix._transformPoint(this._hilite, this._hilite, true); this._radius = this._destination.getDistance(this._origin); } }); var Gradient = this.Gradient = Base.extend({ initialize: function(stops, type) { this.setStops(stops || ['white', 'black']); this.type = type || 'linear'; }, clone: function() { var stops = []; for (var i = 0, l = this._stops.length; i < l; i++) stops[i] = this._stops[i].clone(); return new Gradient(stops, this.type); }, getStops: function() { return this._stops; }, setStops: function(stops) { if (stops.length < 2) throw new Error( 'Gradient stop list needs to contain at least two stops.'); this._stops = GradientStop.readAll(stops); for (var i = 0, l = this._stops.length; i < l; i++) { var stop = this._stops[i]; if (stop._defaultRamp) stop.setRampPoint(i / (l - 1)); } }, equals: function(gradient) { if (gradient.type != this.type) return false; if (this._stops.length == gradient._stops.length) { for (var i = 0, l = this._stops.length; i < l; i++) { if (!this._stops[i].equals(gradient._stops[i])) return false; } return true; } return false; } }); var GradientStop = this.GradientStop = Base.extend({ initialize: function(arg0, arg1) { if (arg1 === undefined && Array.isArray(arg0)) { this.setColor(arg0[0]); this.setRampPoint(arg0[1]); } else if (arg0.color) { this.setColor(arg0.color); this.setRampPoint(arg0.rampPoint); } else { this.setColor(arg0); this.setRampPoint(arg1); } }, clone: function() { return new GradientStop(this._color.clone(), this._rampPoint); }, getRampPoint: function() { return this._rampPoint; }, setRampPoint: function(rampPoint) { this._defaultRamp = rampPoint == null; this._rampPoint = rampPoint || 0; }, getColor: function() { return this._color; }, setColor: function(color) { this._color = Color.read(arguments); }, equals: function(stop) { return stop == this || stop instanceof GradientStop && this._color.equals(stop._color) && this._rampPoint == stop._rampPoint; } }); var DomElement = { getBounds: function(el, viewport) { var rect = el.getBoundingClientRect(), doc = el.ownerDocument, body = doc.body, docEl = doc.documentElement, x = rect.left - (docEl.clientLeft || body.clientLeft || 0), y = rect.top - (docEl.clientTop || body.clientTop || 0); if (!viewport) { var win = DomElement.getViewport(doc); x += win.pageXOffset || docEl.scrollLeft || body.scrollLeft; y += win.pageYOffset || docEl.scrollTop || body.scrollTop; } return new Rectangle(x, y, rect.width, rect.height); }, getOffset: function(el, viewport) { return this.getBounds(el, viewport).getPoint(); }, getSize: function(el) { return this.getBounds(el, true).getSize(); }, isInvisible: function(el) { return this.getSize(el).equals([0, 0]); }, isVisible: function(el) { return !this.isInvisible(el) && this.getViewportBounds(el).intersects( this.getBounds(el, true)); }, getViewport: function(doc) { return doc.defaultView || doc.parentWindow; }, getViewportBounds: function(el) { var doc = el.ownerDocument, view = this.getViewport(doc), body = doc.getElementsByTagName( doc.compatMode === 'CSS1Compat' ? 'html' : 'body')[0]; return Rectangle.create(0, 0, view.innerWidth || body.clientWidth, view.innerHeight || body.clientHeight ); }, getComputedStyle: function(el, name) { if (el.currentStyle) return el.currentStyle[Base.camelize(name)]; var style = this.getViewport(el.ownerDocument) .getComputedStyle(el, null); return style ? style.getPropertyValue(Base.hyphenate(name)) : null; } }; var DomEvent = { add: function(el, events) { for (var type in events) { var func = events[type]; if (el.addEventListener) { el.addEventListener(type, func, false); } else if (el.attachEvent) { el.attachEvent('on' + type, func.bound = function() { func.call(el, window.event); }); } } }, remove: function(el, events) { for (var type in events) { var func = events[type]; if (el.removeEventListener) { el.removeEventListener(type, func, false); } else if (el.detachEvent) { el.detachEvent('on' + type, func.bound); } } }, getPoint: function(event) { var pos = event.targetTouches ? event.targetTouches.length ? event.targetTouches[0] : event.changedTouches[0] : event; return Point.create( pos.pageX || pos.clientX + document.documentElement.scrollLeft, pos.pageY || pos.clientY + document.documentElement.scrollTop ); }, getTarget: function(event) { return event.target || event.srcElement; }, getOffset: function(event, target) { return DomEvent.getPoint(event).subtract(DomElement.getOffset( target || DomEvent.getTarget(event))); }, preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } }, stopPropagation: function(event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } }, stop: function(event) { DomEvent.stopPropagation(event); DomEvent.preventDefault(event); } }; DomEvent.requestAnimationFrame = new function() { var part = 'equestAnimationFrame', request = window['r' + part] || window['webkitR' + part] || window['mozR' + part] || window['oR' + part] || window['msR' + part]; if (request) { request(function(time) { if (time == undefined) request = null; }); } var callbacks = [], focused = true, timer; DomEvent.add(window, { focus: function() { focused = true; }, blur: function() { focused = false; } }); return function(callback, element) { if (request) return request(callback, element); callbacks.push([callback, element]); if (timer) return; timer = window.setInterval(function() { for (var i = callbacks.length - 1; i >= 0; i--) { var entry = callbacks[i], func = entry[0], el = entry[1]; if (!el || (PaperScript.getAttribute(el, 'keepalive') == 'true' || focused) && DomElement.isVisible(el)) { callbacks.splice(i, 1); func(Date.now()); } } }, 1000 / 60); }; }; var View = this.View = PaperScopeItem.extend({ _list: 'views', _reference: 'view', initialize: function(canvas) { this.base(); var size; if (typeof canvas === 'string') canvas = document.getElementById(canvas); if (canvas instanceof HTMLCanvasElement) { this._canvas = canvas; if (PaperScript.hasAttribute(canvas, 'resize')) { var offset = DomElement.getOffset(canvas, true), that = this; size = DomElement.getViewportBounds(canvas) .getSize().subtract(offset); canvas.width = size.width; canvas.height = size.height; DomEvent.add(window, { resize: function(event) { if (!DomElement.isInvisible(canvas)) offset = DomElement.getOffset(canvas, true); that.setViewSize(DomElement.getViewportBounds(canvas) .getSize().subtract(offset)); } }); } else { size = DomElement.isInvisible(canvas) ? Size.create(parseInt(canvas.getAttribute('width')), parseInt(canvas.getAttribute('height'))) : DomElement.getSize(canvas); } if (PaperScript.hasAttribute(canvas, 'stats')) { this._stats = new Stats(); var element = this._stats.domElement, style = element.style, offset = DomElement.getOffset(canvas); style.position = 'absolute'; style.left = offset.x + 'px'; style.top = offset.y + 'px'; document.body.appendChild(element); } } else { size = Size.read(arguments, 1); if (size.isZero()) size = new Size(1024, 768); this._canvas = CanvasProvider.getCanvas(size); } this._id = this._canvas.getAttribute('id'); if (this._id == null) this._canvas.setAttribute('id', this._id = 'canvas-' + View._id++); View._views[this._id] = this; this._viewSize = LinkedSize.create(this, 'setViewSize', size.width, size.height); this._context = this._canvas.getContext('2d'); this._matrix = new Matrix(); this._zoom = 1; this._events = this._createEvents(); DomEvent.add(this._canvas, this._events); if (!View._focused) View._focused = this; this._scope._redrawNotified = false; }, remove: function() { if (!this.base()) return false; if (View._focused == this) View._focused = null; delete View._views[this._id]; DomEvent.remove(this._canvas, this._events); this._canvas = this._events = this._onFrame = null; return true; }, _redraw: function() { this._redrawNeeded = true; if (this._onFrameCallback) { this._onFrameCallback(0, true); } else { this.draw(); } }, _transform: function(matrix, flags) { this._matrix.preConcatenate(matrix); this._bounds = null; this._inverse = null; this._redraw(); }, getCanvas: function() { return this._canvas; }, getViewSize: function() { return this._viewSize; }, setViewSize: function(size) { size = Size.read(arguments); var delta = size.subtract(this._viewSize); if (delta.isZero()) return; this._canvas.width = size.width; this._canvas.height = size.height; this._viewSize.set(size.width, size.height, true); this._bounds = null; this._redrawNeeded = true; if (this.onResize) { this.onResize({ size: size, delta: delta }); } this._redraw(); }, getBounds: function() { if (!this._bounds) this._bounds = this._getInverse()._transformBounds( new Rectangle(new Point(), this._viewSize)); return this._bounds; }, getSize: function() { return this.getBounds().getSize(); }, getCenter: function() { return this.getBounds().getCenter(); }, setCenter: function(center) { this.scrollBy(Point.read(arguments).subtract(this.getCenter())); }, getZoom: function() { return this._zoom; }, setZoom: function(zoom) { this._transform(new Matrix().scale(zoom / this._zoom, this.getCenter())); this._zoom = zoom; }, isVisible: function() { return DomElement.isVisible(this._canvas); }, scrollBy: function(point) { this._transform(new Matrix().translate(Point.read(arguments).negate())); }, draw: function(checkRedraw) { if (checkRedraw && !this._redrawNeeded) return false; if (this._stats) this._stats.update(); var ctx = this._context, size = this._viewSize; ctx.clearRect(0, 0, size._width + 1, size._height + 1); ctx.save(); this._matrix.applyToContext(ctx); this._scope.project.draw(ctx); ctx.restore(); if (this._redrawNeeded) { this._redrawNeeded = false; this._scope._redrawNotified = false; } return true; }, projectToView: function(point) { return this._matrix._transformPoint(Point.read(arguments)); }, viewToProject: function(point) { return this._getInverse()._transformPoint(Point.read(arguments)); }, _getInverse: function() { if (!this._inverse) this._inverse = this._matrix.createInverse(); return this._inverse; }, getOnFrame: function() { return this._onFrame; }, setOnFrame: function(onFrame) { this._onFrame = onFrame; if (!onFrame) { delete this._onFrameCallback; return; } var that = this, requested = false, before, time = 0, count = 0; this._onFrameCallback = function(param, dontRequest) { requested = false; if (!that._onFrame) return; paper = that._scope; requested = true; if (!dontRequest) { DomEvent.requestAnimationFrame(that._onFrameCallback, that._canvas); } var now = Date.now() / 1000, delta = before ? now - before : 0; that._onFrame(Base.merge({ delta: delta, time: time += delta, count: count++ })); before = now; that.draw(true); }; if (!requested) this._onFrameCallback(); }, onResize: null }, { statics: { _views: {}, _id: 0 } }, new function() { var tool, timer, curPoint, tempFocus, dragging = false; function viewToProject(view, event) { return view.viewToProject(DomEvent.getOffset(event, view._canvas)); } function updateFocus() { if (!View._focused || !View._focused.isVisible()) { PaperScope.each(function(scope) { for (var i = 0, l = scope.views.length; i < l; i++) { var view = scope.views[i]; if (view.isVisible()) { View._focused = tempFocus = view; throw Base.stop; } } }); } } function mousemove(event) { var view; if (!dragging) { view = View._views[DomEvent.getTarget(event).getAttribute('id')]; if (view) { View._focused = tempFocus = view; } else if (tempFocus && tempFocus == View._focused) { View._focused = null; updateFocus(); } } if (!(view = view || View._focused) || !(tool = view._scope.tool)) return; var point = event && viewToProject(view, event); var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove); if (dragging && !onlyMove) { curPoint = point || curPoint; if (curPoint && tool.onHandleEvent('mousedrag', curPoint, event)) { view.draw(true); DomEvent.stop(event); } } else if ((!dragging || onlyMove) && tool.onHandleEvent('mousemove', point, event)) { view.draw(true); DomEvent.stop(event); } } function mouseup(event) { var view = View._focused; if (!view || !dragging) return; dragging = false; curPoint = null; if (tool) { if (timer != null) timer = clearInterval(timer); if (tool.onHandleEvent('mouseup', viewToProject(view, event), event)) { view.draw(true); DomEvent.stop(event); } } } function selectstart(event) { if (dragging) DomEvent.stop(event); } DomEvent.add(document, { mousemove: mousemove, mouseup: mouseup, touchmove: mousemove, touchend: mouseup, selectstart: selectstart, scroll: updateFocus }); DomEvent.add(window, { load: updateFocus }); return { _createEvents: function() { var view = this; function mousedown(event) { View._focused = view; if (!(tool = view._scope.tool)) return; curPoint = viewToProject(view, event); if (tool.onHandleEvent('mousedown', curPoint, event)) view.draw(true); if (tool.eventInterval != null) timer = setInterval(mousemove, tool.eventInterval); dragging = true; } return { mousedown: mousedown, touchstart: mousedown, selectstart: selectstart }; }, statics: { updateFocus: updateFocus } }; }); var Event = this.Event = Base.extend({ initialize: function(event) { this.event = event; }, preventDefault: function() { DomEvent.preventDefault(this.event); }, stopPropagation: function() { DomEvent.stopPropagation(this.event); }, stop: function() { DomEvent.stop(this.event); }, getModifiers: function() { return Key.modifiers; } }); var KeyEvent = this.KeyEvent = Event.extend(new function() { return { initialize: function(down, key, character, event) { this.base(event); this.type = down ? 'keydown' : 'keyup'; this.key = key; this.character = character; }, toString: function() { return '{ type: ' + this.type + ', key: ' + this.key + ', character: ' + this.character + ', modifiers: ' + this.getModifiers() + ' }'; } }; }); var Key = this.Key = new function() { var keys = { 8: 'backspace', 13: 'enter', 16: 'shift', 17: 'control', 18: 'option', 19: 'pause', 20: 'caps-lock', 27: 'escape', 32: 'space', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down', 46: 'delete', 91: 'command', 93: 'command', 224: 'command' }, modifiers = Base.merge({ shift: false, control: false, option: false, command: false, capsLock: false }), charCodeMap = {}, keyMap = {}, downCode; function handleKey(down, keyCode, charCode, event) { var character = String.fromCharCode(charCode), key = keys[keyCode] || character.toLowerCase(), handler = down ? 'onKeyDown' : 'onKeyUp', view = View._focused, scope = view && view.isVisible() && view._scope, tool = scope && scope.tool; keyMap[key] = down; if (tool && tool[handler]) { var keyEvent = new KeyEvent(down, key, character, event); if (tool[handler](keyEvent) === false) keyEvent.preventDefault(); if (view) view.draw(true); } } DomEvent.add(document, { keydown: function(event) { var code = event.which || event.keyCode; var key = keys[code], name; if (key) { if ((name = Base.camelize(key)) in modifiers) modifiers[name] = true; charCodeMap[code] = 0; handleKey(true, code, null, event); } else { downCode = code; } }, keypress: function(event) { if (downCode != null) { var code = event.which || event.keyCode; charCodeMap[downCode] = code; handleKey(true, downCode, code, event); downCode = null; } }, keyup: function(event) { var code = event.which || event.keyCode, key = keys[code], name; if (key && (name = Base.camelize(key)) in modifiers) modifiers[name] = false; if (charCodeMap[code] != null) { handleKey(false, code, charCodeMap[code], event); delete charCodeMap[code]; } } }); return { modifiers: modifiers, isDown: function(key) { return !!keyMap[key]; } }; }; var ToolEvent = this.ToolEvent = Event.extend({ initialize: function(tool, type, event) { this.tool = tool; this.type = type; this.event = event; }, _choosePoint: function(point, toolPoint) { return point ? point : toolPoint ? toolPoint.clone() : null; }, getPoint: function() { return this._choosePoint(this._point, this.tool._point); }, setPoint: function(point) { this._point = point; }, getLastPoint: function() { return this._choosePoint(this._lastPoint, this.tool._lastPoint); }, setLastPoint: function(lastPoint) { this._lastPoint = lastPoint; }, getDownPoint: function() { return this._choosePoint(this._downPoint, this.tool._downPoint); }, setDownPoint: function(downPoint) { this._downPoint = downPoint; }, getMiddlePoint: function() { if (!this._middlePoint && this.tool._lastPoint) { return this.tool._point.add(this.tool._lastPoint).divide(2); } return this.middlePoint; }, setMiddlePoint: function(middlePoint) { this._middlePoint = middlePoint; }, getDelta: function() { return !this._delta && this.tool._lastPoint ? this.tool._point.subtract(this.tool._lastPoint) : this._delta; }, setDelta: function(delta) { this._delta = delta; }, getCount: function() { return /^mouse(down|up)$/.test(this.type) ? this.tool._downCount : this.tool._count; }, setCount: function(count) { this.tool[/^mouse(down|up)$/.test(this.type) ? 'downCount' : 'count'] = count; }, getItem: function() { if (!this._item) { var result = this.tool._scope.project.hitTest(this.getPoint()); if (result) { var item = result.item, parent = item._parent; while ((parent instanceof Group && !(parent instanceof Layer)) || parent instanceof CompoundPath) { item = parent; parent = parent._parent; } this._item = item; } } return this._item; }, setItem: function(item) { this._item = item; }, toString: function() { return '{ type: ' + this.type + ', point: ' + this.getPoint() + ', count: ' + this.getCount() + ', modifiers: ' + this.getModifiers() + ' }'; } }); var Tool = this.Tool = PaperScopeItem.extend({ _list: 'tools', _reference: 'tool', initialize: function() { this.base(); this._firstMove = true; this._count = 0; this._downCount = 0; }, eventInterval: null, getMinDistance: function() { return this._minDistance; }, setMinDistance: function(minDistance) { this._minDistance = minDistance; if (this._minDistance != null && this._maxDistance != null && this._minDistance > this._maxDistance) { this._maxDistance = this._minDistance; } }, getMaxDistance: function() { return this._maxDistance; }, setMaxDistance: function(maxDistance) { this._maxDistance = maxDistance; if (this._minDistance != null && this._maxDistance != null && this._maxDistance < this._minDistance) { this._minDistance = maxDistance; } }, getFixedDistance: function() { return this._minDistance == this._maxDistance ? this._minDistance : null; }, setFixedDistance: function(distance) { this._minDistance = distance; this._maxDistance = distance; }, updateEvent: function(type, pt, minDistance, maxDistance, start, needsChange, matchMaxDistance) { if (!start) { if (minDistance != null || maxDistance != null) { var minDist = minDistance != null ? minDistance : 0; var vector = pt.subtract(this._point); var distance = vector.getLength(); if (distance < minDist) return false; var maxDist = maxDistance != null ? maxDistance : 0; if (maxDist != 0) { if (distance > maxDist) { pt = this._point.add(vector.normalize(maxDist)); } else if (matchMaxDistance) { return false; } } } if (needsChange && pt.equals(this._point)) return false; } this._lastPoint = start && type == 'mousemove' ? pt : this._point; this._point = pt; switch (type) { case 'mousedown': this._lastPoint = this._downPoint; this._downPoint = this._point; this._downCount++; break; case 'mouseup': this._lastPoint = this._downPoint; break; } this._count = start ? 0 : this._count + 1; return true; }, onHandleEvent: function(type, pt, event) { paper = this._scope; var called = false; switch (type) { case 'mousedown': this.updateEvent(type, pt, null, null, true, false, false); if (this.onMouseDown) { this.onMouseDown(new ToolEvent(this, type, event)); called = true; } break; case 'mousedrag': var needsChange = false, matchMaxDistance = false; while (this.updateEvent(type, pt, this.minDistance, this.maxDistance, false, needsChange, matchMaxDistance)) { if (this.onMouseDrag) { this.onMouseDrag(new ToolEvent(this, type, event)); called = true; } needsChange = true; matchMaxDistance = true; } break; case 'mouseup': if ((this._point.x != pt.x || this._point.y != pt.y) && this.updateEvent('mousedrag', pt, this.minDistance, this.maxDistance, false, false, false)) { if (this.onMouseDrag) { this.onMouseDrag(new ToolEvent(this, type, event)); called = true; } } this.updateEvent(type, pt, null, this.maxDistance, false, false, false); if (this.onMouseUp) { this.onMouseUp(new ToolEvent(this, type, event)); called = true; } this.updateEvent(type, pt, null, null, true, false, false); this._firstMove = true; break; case 'mousemove': while (this.updateEvent(type, pt, this.minDistance, this.maxDistance, this._firstMove, true, false)) { if (this.onMouseMove) { this.onMouseMove(new ToolEvent(this, type, event)); called = true; } this._firstMove = false; } break; } return called; } }); var CanvasProvider = { canvases: [], getCanvas: function(size) { if (this.canvases.length) { var canvas = this.canvases.pop(); if ((canvas.width != size.width) || (canvas.height != size.height)) { canvas.width = size.width; canvas.height = size.height; } else { canvas.getContext('2d').clearRect(0, 0, size.width + 1, size.height + 1); } return canvas; } else { var canvas = document.createElement('canvas'); canvas.width = size.width; canvas.height = size.height; return canvas; } }, returnCanvas: function(canvas) { this.canvases.push(canvas); } }; var Numerical = new function() { var abscissas = [ [ 0.5773502691896257645091488], [0,0.7745966692414833770358531], [ 0.3399810435848562648026658,0.8611363115940525752239465], [0,0.5384693101056830910363144,0.9061798459386639927976269], [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016], [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897], [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609], [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762], [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640], [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380], [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491], [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294], [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973], [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657], [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542] ]; var weights = [ [1], [0.8888888888888888888888889,0.5555555555555555555555556], [0.6521451548625461426269361,0.3478548451374538573730639], [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640], [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961], [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114], [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314], [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922], [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688], [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537], [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160], [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216], [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329], [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284], [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806] ]; var abs = Math.abs, sqrt = Math.sqrt, cos = Math.cos, PI = Math.PI; return { TOLERANCE: 10e-6, EPSILON: 10e-12, integrate: function(f, a, b, n) { var x = abscissas[n - 2], w = weights[n - 2], A = 0.5 * (b - a), B = A + a, i = 0, m = (n + 1) >> 1, sum = n & 1 ? w[i++] * f(B) : 0; while (i < m) { var Ax = A * x[i]; sum += w[i++] * (f(B + Ax) + f(B - Ax)); } return A * sum; }, findRoot: function(f, df, x, a, b, n, tolerance) { for (var i = 0; i < n; i++) { var fx = f(x), dx = fx / df(x); if (abs(dx) < tolerance) return x; var nx = x - dx; if (fx > 0) { b = x; x = nx <= a ? 0.5 * (a + b) : nx; } else { a = x; x = nx >= b ? 0.5 * (a + b) : nx; } } }, solveQuadratic: function(a, b, c, roots, tolerance) { if (abs(a) < tolerance) { if (abs(b) >= tolerance) { roots[0] = -c / b; return 1; } if (abs(c) < tolerance) return -1; return 0; } var q = b * b - 4 * a * c; if (q < 0) return 0; q = sqrt(q); if (b < 0) q = -q; q = (b + q) * -0.5; var n = 0; if (abs(q) >= tolerance) roots[n++] = c / q; if (abs(a) >= tolerance) roots[n++] = q / a; return n; }, solveCubic: function(a, b, c, d, roots, tolerance) { if (abs(a) < tolerance) return Numerical.solveQuadratic(b, c, d, roots, tolerance); b /= a; c /= a; d /= a; var Q = (b * b - 3 * c) / 9, R = (2 * b * b * b - 9 * b * c + 27 * d) / 54, Q3 = Q * Q * Q, R2 = R * R; b /= 3; if (R2 < Q3) { var theta = Math.acos(R / sqrt(Q3)), q = -2 * sqrt(Q); roots[0] = q * cos(theta / 3) - b; roots[1] = q * cos((theta + 2 * PI) / 3) - b; roots[2] = q * cos((theta - 2 * PI) / 3) - b; return 3; } else { var A = -Math.pow(abs(R) + sqrt(R2 - Q3), 1 / 3); if (R < 0) A = -A; var B = (abs(A) < tolerance) ? 0 : Q / A; roots[0] = (A + B) - b; return 1; } return 0; } }; }; var BlendMode = { process: function(blendMode, srcContext, dstContext, alpha, offset) { var srcCanvas = srcContext.canvas, dstData = dstContext.getImageData(offset.x, offset.y, srcCanvas.width, srcCanvas.height), dst = dstData.data, src = srcContext.getImageData(0, 0, srcCanvas.width, srcCanvas.height).data, min = Math.min, max = Math.max, abs = Math.abs, sr, sg, sb, sa, br, bg, bb, ba, dr, dg, db; function getLum(r, g, b) { return 0.2989 * r + 0.587 * g + 0.114 * b; } function setLum(r, g, b, l) { var d = l - getLum(r, g, b); dr = r + d; dg = g + d; db = b + d; var l = getLum(dr, dg, db), mn = min(dr, dg, db), mx = max(dr, dg, db); if (mn < 0) { var lmn = l - mn; dr = l + (dr - l) * l / lmn; dg = l + (dg - l) * l / lmn; db = l + (db - l) * l / lmn; } if (mx > 255) { var ln = 255 - l, mxl = mx - l; dr = l + (dr - l) * ln / mxl; dg = l + (dg - l) * ln / mxl; db = l + (db - l) * ln / mxl; } } function getSat(r, g, b) { return max(r, g, b) - min(r, g, b); } function setSat(r, g, b, s) { var col = [r, g, b], mx = max(r, g, b), mn = min(r, g, b), md; mn = mn == r ? 0 : mn == g ? 1 : 2; mx = mx == r ? 0 : mx == g ? 1 : 2; md = min(mn, mx) == 0 ? max(mn, mx) == 1 ? 2 : 1 : 0; if (col[mx] > col[mn]) { col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]); col[mx] = s; } else { col[md] = col[mx] = 0; } col[mn] = 0; dr = col[0]; dg = col[1]; db = col[2]; } var modes = { multiply: function() { dr = br * sr / 255; dg = bg * sg / 255; db = bb * sb / 255; }, screen: function() { dr = 255 - (255 - br) * (255 - sr) / 255; dg = 255 - (255 - bg) * (255 - sg) / 255; db = 255 - (255 - bb) * (255 - sb) / 255; }, overlay: function() { dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255; dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255; db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255; }, 'soft-light': function() { var t = sr * br / 255; dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255; t = sg * bg / 255; dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255; t = sb * bb / 255; db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255; }, 'hard-light': function() { dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255; dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255; db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255; }, 'color-dodge': function() { dr = sr == 255 ? sr : min(255, br * 255 / (255 - sr)); dg = sg == 255 ? sg : min(255, bg * 255 / (255 - sg)); db = sb == 255 ? sb : min(255, bb * 255 / (255 - sb)); }, 'color-burn': function() { dr = sr == 0 ? 0 : max(255 - ((255 - br) * 255) / sr, 0); dg = sg == 0 ? 0 : max(255 - ((255 - bg) * 255) / sg, 0); db = sb == 0 ? 0 : max(255 - ((255 - bb) * 255) / sb, 0); }, darken: function() { dr = br < sr ? br : sr; dg = bg < sg ? bg : sg; db = bb < sb ? bb : sb; }, lighten: function() { dr = br > sr ? br : sr; dg = bg > sg ? bg : sg; db = bb > sb ? bb : sb; }, difference: function() { dr = br - sr; if (dr < 0) dr = -dr; dg = bg - sg; if (dg < 0) dg = -dg; db = bb - sb; if (db < 0) db = -db; }, exclusion: function() { dr = br + sr * (255 - br - br) / 255; dg = bg + sg * (255 - bg - bg) / 255; db = bb + sb * (255 - bb - bb) / 255; }, hue: function() { setSat(sr, sg, sb, getSat(br, bg, bb)); setLum(dr, dg, db, getLum(br, bg, bb)); }, saturation: function() { setSat(br, bg, bb, getSat(sr, sg, sb)); setLum(dr, dg, db, getLum(br, bg, bb)); }, luminosity: function() { setLum(br, bg, bb, getLum(sr, sg, sb)); }, color: function() { setLum(sr, sg, sb, getLum(br, bg, bb)); }, add: function() { dr = min(br + sr, 255); dg = min(bg + sg, 255); db = min(bb + sb, 255); }, subtract: function() { dr = max(br - sr, 0); dg = max(bg - sg, 0); db = max(bb - sb, 0); }, average: function() { dr = (br + sr) / 2; dg = (bg + sg) / 2; db = (bb + sb) / 2; }, negation: function() { dr = 255 - abs(255 - sr - br); dg = 255 - abs(255 - sg - bg); db = 255 - abs(255 - sb - bb); } }; var process = modes[blendMode]; if (!process) return; for (var i = 0, l = dst.length; i < l; i += 4) { sr = src[i]; br = dst[i]; sg = src[i + 1]; bg = dst[i + 1]; sb = src[i + 2]; bb = dst[i + 2]; sa = src[i + 3]; ba = dst[i + 3]; process(); var a1 = sa * alpha / 255, a2 = 1 - a1; dst[i] = a1 * dr + a2 * br; dst[i + 1] = a1 * dg + a2 * bg; dst[i + 2] = a1 * db + a2 * bb; dst[i + 3] = sa * alpha + a2 * ba; } dstContext.putImageData(dstData, offset.x, offset.y); } }; var PaperScript = this.PaperScript = new function() { var parse_js=new function(){function W(a,b,c){var d=[];for(var e=0;e0,g=j(function(){return h(a[0]?k(["case",z(a[0])+":"]):"default:")},.5)+(f?e+j(function(){return u(a[1]).join(e)}):"");!c&&f&&d0?h(a):a}).join(e)},block:w,"var":function(a){return"var "+l(W(a,x))+";"},"const":function(a){return"const "+l(W(a,x))+";"},"try":function(a,b,c){var d=["try",w(a)];b&&d.push("catch","("+b[0]+")",w(b[1])),c&&d.push("finally",w(c));return k(d)},"throw":function(a){return k(["throw",z(a)])+";"},"new":function(a,b){b=b.length>0?"("+l(W(b,z))+")":"";return k(["new",m(a,"seq","binary","conditional","assign",function(a){var b=N(),c={};try{b.with_walkers({call:function(){throw c},"function":function(){return this}},function(){b.walk(a)})}catch(d){if(d===c)return!0;throw d}})+b])},"switch":function(a,b){return k(["switch","("+z(a)+")",v(b)])},"break":function(a){var b="break";a!=null&&(b+=" "+g(a));return b+";"},"continue":function(a){var b="continue";a!=null&&(b+=" "+g(a));return b+";"},conditional:function(a,b,c){return k([m(a,"assign","seq","conditional"),"?",m(b,"seq"),":",m(c,"seq")])},assign:function(a,b,c){a&&a!==!0?a+="=":a="=";return k([z(b),a,m(c,"seq")])},dot:function(a){var b=z(a),c=1;a[0]=="num"?/\./.test(a[1])||(b+="."):o(a)&&(b="("+b+")");while(cB[b[1]])d="("+d+")";if(L(c[0],["assign","conditional","seq"])||c[0]=="binary"&&B[a]>=B[c[1]]&&(c[1]!=a||!L(a,["&&","||","*"])))e="("+e+")";return k([d,a,e])},"unary-prefix":function(a,b){var c=z(b);b[0]=="num"||b[0]=="unary-prefix"&&!M(i,a+b[1])||!o(b)||(c="("+c+")");return a+(p(a.charAt(0))?" ":"")+c},"unary-postfix":function(a,b){var c=z(b);b[0]=="num"||b[0]=="unary-postfix"&&!M(i,a+b[1])||!o(b)||(c="("+c+")");return c+a},sub:function(a,b){var c=z(a);o(a)&&(c="("+c+")");return c+"["+z(b)+"]"},object:function(a){return a.length==0?"{}":"{"+e+j(function(){return W(a,function(a){if(a.length==3)return h(t(a[0],a[1][2],a[1][3],a[2]));var d=a[0],e=z(a[1]);b.quote_keys?d=Q(d):(typeof d=="number"||!c&&+d+""==d)&&parseFloat(d)>=0?d=q(+d):V(d)||(d=Q(d));return h(k(c&&b.space_colon?[d,":",e]:[d+":",e]))}).join(","+e)})+e+h("}")},regexp:function(a,b){return"/"+a+"/"+b},array:function(a){return a.length==0?"[]":k(["[",l(W(a,function(a){return!c&&a[0]=="atom"&&a[1]=="undefined"?"":m(a,"seq")})),"]"])},stat:function(a){return z(a).replace(/;*\s*$/,";")},seq:function(){return l(W(J(arguments),z))},label:function(a,b){return k([g(a),":",z(b)])},"with":function(a,b){return k(["with","("+z(a)+")",z(b)])},atom:function(a){return g(a)}},y=[];return z(a)}function Q(a){var b=0,c=0;a=a.replace(/[\\\b\f\n\r\t\x22\x27]/g,function(a){switch(a){case"\\":return"\\\\";case"\b":return"\\b";case"\f":return"\\f";case"\n":return"\\n";case"\r":return"\\r";case"\t":return"\\t";case'"':++b;return'"';case"'":++c;return"'"}return a});return b>c?"'"+a.replace(/\x27/g,"\\'")+"'":'"'+a.replace(/\x22/g,'\\"')+'"'}function O(a){return!a||a[0]=="block"&&(!a[1]||a[1].length==0)}function N(){function g(a,b){var c={},e;for(e in a)M(a,e)&&(c[e]=d[e],d[e]=a[e]);var f=b();for(e in c)M(c,e)&&(c[e]?d[e]=c[e]:delete d[e]);return f}function f(a){if(a==null)return null;try{e.push(a);var b=a[0],f=d[b];if(f){var g=f.apply(a,a.slice(1));if(g!=null)return g}f=c[b];return f.apply(a,a.slice(1))}finally{e.pop()}}function b(a){var b=[this[0]];a!=null&&b.push(W(a,f));return b}function a(a){return[this[0],W(a,function(a){var b=[a[0]];a.length>1&&(b[1]=f(a[1]));return b})]}var c={string:function(a){return[this[0],a]},num:function(a){return[this[0],a]},name:function(a){return[this[0],a]},toplevel:function(a){return[this[0],W(a,f)]},block:b,splice:b,"var":a,"const":a,"try":function(a,b,c){return[this[0],W(a,f),b!=null?[b[0],W(b[1],f)]:null,c!=null?W(c,f):null]},"throw":function(a){return[this[0],f(a)]},"new":function(a,b){return[this[0],f(a),W(b,f)]},"switch":function(a,b){return[this[0],f(a),W(b,function(a){return[a[0]?f(a[0]):null,W(a[1],f)]})]},"break":function(a){return[this[0],a]},"continue":function(a){return[this[0],a]},conditional:function(a,b,c){return[this[0],f(a),f(b),f(c)]},assign:function(a,b,c){return[this[0],a,f(b),f(c)]},dot:function(a){return[this[0],f(a)].concat(J(arguments,1))},call:function(a,b){return[this[0],f(a),W(b,f)]},"function":function(a,b,c){return[this[0],a,b.slice(),W(c,f)]},defun:function(a,b,c){return[this[0],a,b.slice(),W(c,f)]},"if":function(a,b,c){return[this[0],f(a),f(b),f(c)]},"for":function(a,b,c,d){return[this[0],f(a),f(b),f(c),f(d)]},"for-in":function(a,b,c,d){return[this[0],f(a),f(b),f(c),f(d)]},"while":function(a,b){return[this[0],f(a),f(b)]},"do":function(a,b){return[this[0],f(a),f(b)]},"return":function(a){return[this[0],f(a)]},binary:function(a,b,c){return[this[0],a,f(b),f(c)]},"unary-prefix":function(a,b){return[this[0],a,f(b)]},"unary-postfix":function(a,b){return[this[0],a,f(b)]},sub:function(a,b){return[this[0],f(a),f(b)]},object:function(a){return[this[0],W(a,function(a){return a.length==2?[a[0],f(a[1])]:[a[0],f(a[1]),a[2]]})]},regexp:function(a,b){return[this[0],a,b]},array:function(a){return[this[0],W(a,f)]},stat:function(a){return[this[0],f(a)]},seq:function(){return[this[0]].concat(W(J(arguments),f))},label:function(a,b){return[this[0],a,f(b)]},"with":function(a,b){return[this[0],f(a),f(b)]},atom:function(a){return[this[0],a]}},d={},e=[];return{walk:f,with_walkers:g,parent:function(){return e[e.length-2]},stack:function(){return e}}}function M(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function L(a,b){for(var c=b.length;--c>=0;)if(b[c]===a)return!0;return!1}function K(a){return a.split("")}function J(a,b){return Array.prototype.slice.call(a,b||0)}function I(a){var b={};for(var c=0;c0;++b)arguments[b]();return a}function G(a){var b=J(arguments,1);return function(){return a.apply(this,b.concat(J(arguments)))}}function F(a,b,c){function bk(a){try{++d.in_loop;return a()}finally{--d.in_loop}}function bi(a){var b=bg(a),c=d.token.value;if(e("operator")&&M(A,c)){if(bh(b)){g();return p("assign",A[c],b,bi(a))}i("Invalid assignment")}return b}function bh(a){if(!b)return!0;switch(a[0]){case"dot":case"sub":case"new":case"call":return!0;case"name":return a[1]!="this"}}function bg(a){var b=bf(a);if(e("operator","?")){g();var c=bj(!1);m(":");return p("conditional",b,c,bj(!1,a))}return b}function bf(a){return be(Y(!0),0,a)}function be(a,b,c){var f=e("operator")?d.token.value:null;f&&f=="in"&&c&&(f=null);var h=f!=null?B[f]:null;if(h!=null&&h>b){g();var i=be(Y(!0),h,c);return be(p("binary",f,a,i),b,c)}return a}function bd(a,b,c){(b=="++"||b=="--")&&!bh(c)&&i("Invalid use of "+b+" operator");return p(a,b,c)}function bc(a,b){if(e("punc",".")){g();return bc(p("dot",a,bb()),b)}if(e("punc","[")){g();return bc(p("sub",a,H(bj,G(m,"]"))),b)}if(b&&e("punc","(")){g();return bc(p("call",a,Z(")")),!0)}return b&&e("operator")&&M(z,d.token.value)?H(G(bd,"unary-postfix",d.token.value,a),g):a}function bb(){switch(d.token.type){case"name":case"operator":case"keyword":case"atom":return H(d.token.value,g);default:k()}}function ba(){switch(d.token.type){case"num":case"string":return H(d.token.value,g)}return bb()}function _(){var a=!0,c=[];while(!e("punc","}")){a?a=!1:m(",");if(!b&&e("punc","}"))break;var f=d.token.type,h=ba();f!="name"||h!="get"&&h!="set"||!!e("punc",":")?(m(":"),c.push([h,bj(!1)])):c.push([bb(),P(!1),h])}g();return p("object",c)}function $(){return p("array",Z("]",!b,!0))}function Z(a,b,c){var d=!0,f=[];while(!e("punc",a)){d?d=!1:m(",");if(b&&e("punc",a))break;e("punc",",")&&c?f.push(["atom","undefined"]):f.push(bj(!1))}g();return f}function X(){var a=Y(!1),b;e("punc","(")?(g(),b=Z(")")):b=[];return bc(p("new",a,b),!0)}function W(){return p("const",U())}function V(a){return p("var",U(a))}function U(a){var b=[];for(;;){e("name")||k();var c=d.token.value;g(),e("operator","=")?(g(),b.push([c,bj(!1,a)])):b.push([c]);if(!e("punc",","))break;g()}return b}function T(){var a=R(),b,c;if(e("keyword","catch")){g(),m("("),e("name")||i("Name expected");var f=d.token.value;g(),m(")"),b=[f,R()]}e("keyword","finally")&&(g(),c=R()),!b&&!c&&i("Missing catch/finally blocks");return p("try",a,b,c)}function R(){m("{");var a=[];while(!e("punc","}"))e("eof")&&k(),a.push(t());g();return a}function Q(){var a=q(),b=t(),c;e("keyword","else")&&(g(),c=t());return p("if",a,b,c)}function O(a){var b=a[0]=="var"?p("name",a[1][0]):a;g();var c=bj();m(")");return p("for-in",a,b,c,bk(t))}function N(a){m(";");var b=e("punc",";")?null:bj();m(";");var c=e("punc",")")?null:bj();m(")");return p("for",a,b,c,bk(t))}function K(){m("(");var a=null;if(!e("punc",";")){a=e("keyword","var")?(g(),V(!0)):bj(!0,!0);if(e("operator","in"))return O(a)}return N(a)}function I(a){var b;n()||(b=e("name")?d.token.value:null),b!=null?(g(),L(b,d.labels)||i("Label "+b+" without matching loop or statement")):d.in_loop==0&&i(a+" not inside a loop or switch"),o();return p(a,b)}function F(){return p("stat",H(bj,o))}function w(a){d.labels.push(a);var c=d.token,e=t();b&&!M(C,e[0])&&k(c),d.labels.pop();return p("label",a,e)}function s(a){return c?function(){var b=d.token,c=a.apply(this,arguments);c[0]=r(c[0],b,h());return c}:a}function r(a,b,c){return a instanceof E?a:new E(a,b,c)}function q(){m("(");var a=bj();m(")");return a}function p(){return J(arguments)}function o(){e("punc",";")?g():n()||k()}function n(){return!b&&(d.token.nlb||e("eof")||e("punc","}"))}function m(a){return l("punc",a)}function l(a,b){if(e(a,b))return g();j(d.token,"Unexpected token "+d.token.type+", expected "+a)}function k(a){a==null&&(a=d.token),j(a,"Unexpected token: "+a.type+" ("+a.value+")")}function j(a,b){i(b,a.line,a.col)}function i(a,b,c,e){var f=d.input.context();u(a,b!=null?b:f.tokline,c!=null?c:f.tokcol,e!=null?e:f.tokpos)}function h(){return d.prev}function g(){d.prev=d.token,d.peeked?(d.token=d.peeked,d.peeked=null):d.token=d.input();return d.token}function f(){return d.peeked||(d.peeked=d.input())}function e(a,b){return v(d.token,a,b)}var d={input:typeof a=="string"?x(a,!0):a,token:null,prev:null,peeked:null,in_function:0,in_loop:0,labels:[]};d.token=g();var t=s(function(){e("operator","/")&&(d.peeked=null,d.token=d.input(!0));switch(d.token.type){case"num":case"string":case"regexp":case"operator":case"atom":return F();case"name":return v(f(),"punc",":")?w(H(d.token.value,g,g)):F();case"punc":switch(d.token.value){case"{":return p("block",R());case"[":case"(":return F();case";":g();return p("block");default:k()};case"keyword":switch(H(d.token.value,g)){case"break":return I("break");case"continue":return I("continue");case"debugger":o();return p("debugger");case"do":return function(a){l("keyword","while");return p("do",H(q,o),a)}(bk(t));case"for":return K();case"function":return P(!0);case"if":return Q();case"return":d.in_function==0&&i("'return' outside of function");return p("return",e("punc",";")?(g(),null):n()?null:H(bj,o));case"switch":return p("switch",q(),S());case"throw":return p("throw",H(bj,o));case"try":return T();case"var":return H(V,o);case"const":return H(W,o);case"while":return p("while",q(),bk(t));case"with":return p("with",q(),t());default:k()}}}),P=s(function(a){var b=e("name")?H(d.token.value,g):null;a&&!b&&k(),m("(");return p(a?"defun":"function",b,function(a,b){while(!e("punc",")"))a?a=!1:m(","),e("name")||k(),b.push(d.token.value),g();g();return b}(!0,[]),function(){++d.in_function;var a=d.in_loop;d.in_loop=0;var b=R();--d.in_function,d.in_loop=a;return b}())}),S=G(bk,function(){m("{");var a=[],b=null;while(!e("punc","}"))e("eof")&&k(),e("keyword","case")?(g(),b=[],a.push([bj(),b]),m(":")):e("keyword","default")?(g(),m(":"),b=[],a.push([null,b])):(b||k(),b.push(t()));g();return a}),Y=s(function(a){if(e("operator","new")){g();return X()}if(e("operator")&&M(y,d.token.value))return bd("unary-prefix",H(d.token.value,g),Y(a));if(e("punc")){switch(d.token.value){case"(":g();return bc(H(bj,G(m,")")),a);case"[":g();return bc($(),a);case"{":g();return bc(_(),a)}k()}if(e("keyword","function")){g();return bc(P(!1),a)}if(M(D,d.token.type)){var b=d.token.type=="regexp"?p("regexp",d.token.value[0],d.token.value[1]):p(d.token.type,d.token.value);return bc(H(b,g),a)}k()}),bj=s(function(a,b){arguments.length==0&&(a=!0);var c=bi(b);if(a&&e("punc",",")){g();return p("seq",c,bj(!0,b))}return c});return p("toplevel",function(a){while(!e("eof"))a.push(t());return a}([]))}function E(a,b,c){this.name=a,this.start=b,this.end=c}function x(b){function P(a){if(a)return I();y(),v();var b=g();if(!b)return x("eof");if(o(b))return C();if(b=='"'||b=="'")return F();if(M(l,b))return x("punc",h());if(b==".")return L();if(b=="/")return K();if(M(e,b))return J();if(b=="\\"||q(b))return N();B("Unexpected character '"+b+"'")}function O(a,b){try{return b()}catch(c){if(c===w)B(a);else throw c}}function N(){var b=A(r);return M(a,b)?M(i,b)?x("operator",b):M(d,b)?x("atom",b):x("keyword",b):x("name",b)}function L(){h();return o(g())?C("."):x("punc",".")}function K(){h();var a=f.regex_allowed;switch(g()){case"/":f.comments_before.push(G()),f.regex_allowed=a;return P();case"*":f.comments_before.push(H()),f.regex_allowed=a;return P()}return f.regex_allowed?I():J("/")}function J(a){function b(a){if(!g())return a;var c=a+g();if(M(i,c)){h();return b(c)}return a}return x("operator",b(a||h()))}function I(){return O("Unterminated regular expression",function(){var a=!1,b="",c,d=!1;while(c=h(!0))if(a)b+="\\"+c,a=!1;else if(c=="[")d=!0,b+=c;else if(c=="]"&&d)d=!1,b+=c;else{if(c=="/"&&!d)break;c=="\\"?a=!0:b+=c}var e=A(function(a){return M(m,a)});return x("regexp",[b,e])})}function H(){h();return O("Unterminated multiline comment",function(){var a=t("*/",!0),b=f.text.substring(f.pos,a),c=x("comment2",b,!0);f.pos=a+2,f.line+=b.split("\n").length-1,f.newline_before=b.indexOf("\n")>=0;return c})}function G(){h();var a=t("\n"),b;a==-1?(b=f.text.substr(f.pos),f.pos=f.text.length):(b=f.text.substring(f.pos,a),f.pos=a);return x("comment1",b,!0)}function F(){return O("Unterminated string constant",function(){var a=h(),b="";for(;;){var c=h(!0);if(c=="\\")c=D();else if(c==a)break;b+=c}return x("string",b)})}function E(a){var b=0;for(;a>0;--a){var c=parseInt(h(!0),16);isNaN(c)&&B("Invalid hex-character pattern in string"),b=b<<4|c}return b}function D(){var a=h(!0);switch(a){case"n":return"\n";case"r":return"\r";case"t":return"\t";case"b":return"\b";case"v":return" ";case"f":return"\f";case"0":return"";case"x":return String.fromCharCode(E(2));case"u":return String.fromCharCode(E(4));case"\n":return"";default:return a}}function C(a){var b=!1,c=!1,d=!1,e=a==".",f=A(function(f,g){if(f=="x"||f=="X")return d?!1:d=!0;if(!d&&(f=="E"||f=="e"))return b?!1:b=c=!0;if(f=="-")return c||g==0&&!a?!0:!1;if(f=="+")return c;c=!1;if(f==".")return!e&&!d?e=!0:!1;return p(f)});a&&(f=a+f);var g=s(f);if(!isNaN(g))return x("num",g);B("Invalid syntax: "+f)}function B(a){u(a,f.tokline,f.tokcol,f.tokpos)}function A(a){var b="",c=g(),d=0;while(c&&a(c,d++))b+=h(),c=g();return b}function y(){while(M(j,g()))h()}function x(a,b,d){f.regex_allowed=a=="operator"&&!M(z,b)||a=="keyword"&&M(c,b)||a=="punc"&&M(k,b);var e={type:a,value:b,line:f.tokline,col:f.tokcol,pos:f.tokpos,nlb:f.newline_before};d||(e.comments_before=f.comments_before,f.comments_before=[]),f.newline_before=!1;return e}function v(){f.tokline=f.line,f.tokcol=f.col,f.tokpos=f.pos}function t(a,b){var c=f.text.indexOf(a,f.pos);if(b&&c==-1)throw w;return c}function n(){return!f.peek()}function h(a){var b=f.text.charAt(f.pos++);if(a&&!b)throw w;b=="\n"?(f.newline_before=!0,++f.line,f.col=0):++f.col;return b}function g(){return f.text.charAt(f.pos)}var f={text:b.replace(/\r\n?|[\n\u2028\u2029]/g,"\n").replace(/^\uFEFF/,""),pos:0,tokpos:0,line:0,tokline:0,col:0,tokcol:0,newline_before:!1,regex_allowed:!1,comments_before:[]};P.context=function(a){a&&(f=a);return f};return P}function v(a,b,c){return a.type==b&&(c==null||a.value==c)}function u(a,b,c,d){throw new t(a,b,c,d)}function t(a,b,c,d){this.message=a,this.line=b,this.col=c,this.pos=d}function s(a){if(f.test(a))return parseInt(a.substr(2),16);if(g.test(a))return parseInt(a.substr(1),8);if(h.test(a))return parseFloat(a)}function r(a){return q(a)||o(a)}function q(a){return a=="$"||a=="_"||n(a)}function p(a){return o(a)||n(a)}function o(a){a=a.charCodeAt(0);return a>=48&&a<=57}function n(a){a=a.charCodeAt(0);return a>=65&&a<=90||a>=97&&a<=122}var a=I(["break","case","catch","const","continue","default","delete","do","else","finally","for","function","if","in","instanceof","new","return","switch","throw","try","typeof","var","void","while","with"]),b=I(["abstract","boolean","byte","char","class","debugger","double","enum","export","extends","final","float","goto","implements","import","int","interface","long","native","package","private","protected","public","short","static","super","synchronized","throws","transient","volatile"]),c=I(["return","new","delete","throw","else","case"]),d=I(["false","null","true","undefined"]),e=I(K("+-*&%=<>!?|~^")),f=/^0x[0-9a-f]+$/i,g=/^0[0-7]+$/,h=/^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i,i=I(["in","instanceof","typeof","new","void","delete","++","--","+","-","!","~","&","|","^","*","/","%",">>","<<",">>>","<",">","<=",">=","==","===","!=","!==","?","=","+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&=","&&","||"]),j=I(K(" \n\r\t")),k=I(K("[{}(,.;:")),l=I(K("[]{}(),;:")),m=I(K("gmsiy"));t.prototype.toString=function(){return this.message+" (line: "+this.line+", col: "+this.col+", pos: "+this.pos+")"};var w={},y=I(["typeof","void","delete","--","++","!","~","-","+"]),z=I(["--","++"]),A=function(a,b,c){while(c>=","<<=",">>>=","|=","^=","&="],{"=":!0},0),B=function(a,b){for(var c=0,d=1;c","<=",">=","in","instanceof"],[">>","<<",">>>"],["+","-"],["*","/","%"]],{}),C=I(["for","do","while","switch"]),D=I(["atom","num","string","regexp","name"]);E.prototype.toString=function(){return this.name};var P=I(["name","array","object","string","dot","sub","call","regexp"]),R=I(["if","while","do","for","for-in","with"]);return{parse:F,gen_code:S,tokenizer:x,ast_walker:N}} // Math Operators var operators = { '+': 'add', '-': 'subtract', '*': 'multiply', '/': 'divide', '%': 'modulo', '==': 'equals', '!=': 'equals' }; function $eval(left, operator, right) { var handler = operators[operator]; if (left && left[handler]) { var res = left[handler](right); return operator == '!=' ? !res : res; } switch (operator) { case '+': return left + right; case '-': return left - right; case '*': return left * right; case '/': return left / right; case '%': return left % right; case '==': return left == right; case '!=': return left != right; default: throw new Error('Implement Operator: ' + operator); } }; // Sign Operators var signOperators = { '-': 'negate' }; function $sign(operator, value) { var handler = signOperators[operator]; if (value && value[handler]) { return value[handler](); } switch (operator) { case '+': return +value; case '-': return -value; default: throw new Error('Implement Sign Operator: ' + operator); } } // AST Helpers function isDynamic(exp) { var type = exp[0]; return type != 'num' && type != 'string'; } function handleOperator(operator, left, right) { // Only replace operators with calls to $operator if the left hand side // is potentially an object. if (operators[operator] && isDynamic(left)) { // Replace with call to $operator(left, operator, right): return ['call', ['name', '$eval'], [left, ['string', operator], right]]; } } /** * Compiles PaperScript code into JavaScript code. * * @name PaperScript.compile * @function * @param {String} code The PaperScript code. * @return {String} The compiled PaperScript as JavaScript code. */ function compile(code) { // Use parse-js to translate the code into a AST structure which is then // walked and parsed for operators to overload. The resulting AST is // translated back to code and evaluated. var ast = parse_js.parse(code), walker = parse_js.ast_walker(), walk = walker.walk; ast = walker.with_walkers({ 'binary': function(operator, left, right) { // Handle simple mathematical operators here: return handleOperator(operator, left = walk(left), right = walk(right)) // Always return something since we're walking left and || [this[0], operator, left, right]; }, 'assign': function(operator, left, right) { var res = handleOperator(operator, left = walk(left), right = walk(right)); if (res) return [this[0], true, left, res]; return [this[0], operator, left, right]; }, 'unary-prefix': function(operator, exp) { if (signOperators[operator] && isDynamic(exp)) { return ['call', ['name', '$sign'], [['string', operator], walk(exp)]]; } } }, function() { return walk(ast); }); return parse_js.gen_code(ast, { beautify: true }); } function evaluate(code, scope) { paper = scope; var view = scope.view, tool = /on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code) && new Tool(), res; with (scope) { (function() { var onEditOptions, onSelect, onDeselect, onReselect, onMouseDown, onMouseUp, onMouseDrag, onMouseMove, onKeyDown, onKeyUp, onFrame, onResize, handlers = [ 'onEditOptions', 'onSelect', 'onDeselect', 'onReselect', 'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove', 'onKeyDown', 'onKeyUp']; res = eval(compile(code)); if (tool) { Base.each(handlers, function(key) { tool[key] = eval(key); }); } if (view) { view.onResize = onResize; view.setOnFrame(onFrame); view.draw(); } }).call(scope); } return res; } function request(url, scope) { var xhr = new (window.ActiveXObject || XMLHttpRequest)( 'Microsoft.XMLHTTP'); xhr.open('GET', url, true); if (xhr.overrideMimeType) { xhr.overrideMimeType('text/plain'); } xhr.onreadystatechange = function() { if (xhr.readyState === 4) { return evaluate(xhr.responseText, scope); } }; return xhr.send(null); } function load() { var scripts = document.getElementsByTagName('script'); for (var i = 0, l = scripts.length; i < l; i++) { var script = scripts[i]; if (/^text\/(?:x-|)paperscript$/.test(script.type) && !script.getAttribute('data-paper-loaded')) { var scope = new PaperScope(script); scope.setup(PaperScript.getAttribute(script, 'canvas')); if (script.src) { request(script.src, scope); } else { evaluate(script.innerHTML, scope); } script.setAttribute('data-paper-loaded', true); } } } DomEvent.add(window, { load: load }); function handleAttribute(name) { name += 'Attribute'; return function(el, attr) { return el[name](attr) || el[name]('data-paper-' + attr); }; } return { compile: compile, evaluate: evaluate, load: load, getAttribute: handleAttribute('get'), hasAttribute: handleAttribute('has') }; }; this.load = PaperScript.load; Base.each(this, function(val, key) { if (val && val.prototype instanceof Base) { val._name = key; } }); this.enumerable = true; return new (PaperScope.inject(this)); };