diff --git a/dist/paper.js b/dist/paper.js
index 5696e26a..65312b8f 100644
--- a/dist/paper.js
+++ b/dist/paper.js
@@ -1,7 +1,8254 @@
-// Paper.js loader for development, as produced by the build/load.sh script
-document.write('');
-document.write('');
+/*!
+ * 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: Mon Oct 8 23:14:12 2012 -0700
+ *
+ ***
+ *
+ * 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.
+ */
-// For more information on building the library please refer to the
-// 'Building the Library' section of README.md:
-// https://github.com/paperjs/paper.js#building-the-library
+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 || 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 = { 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], arguments[i].enumerable);
+ 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.project = null;
+ this.projects = [];
+ 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,
+
+ getView: function() {
+ return this.project.view;
+ },
+
+ getTool: function() {
+ if (!this._tool)
+ this._tool = new Tool();
+ return this._tool;
+ },
+
+ 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(canvas);
+ },
+
+ clear: function() {
+ for (var i = this.projects.length - 1; i >= 0; i--)
+ this.projects[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];
+ },
+
+ statics: {
+ _scopes: {},
+ _id: 0,
+
+ get: function(id) {
+ if (typeof id === 'object')
+ id = id.getAttribute('id');
+ return this._scopes[id] || null;
+ }
+ }
+});
+
+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 Callback = {
+ attach: function(type, func) {
+ if (typeof type !== 'string') {
+ return Base.each(type, function(value, key) {
+ this.attach(key, value);
+ }, this);
+ }
+ var entry = this._eventTypes[type];
+ if (!entry)
+ return this;
+ var handlers = this._handlers = this._handlers || {};
+ handlers = handlers[type] = handlers[type] || [];
+ if (handlers.indexOf(func) == -1) {
+ handlers.push(func);
+ if (entry.install && handlers.length == 1)
+ entry.install.call(this, type);
+ }
+ return this;
+ },
+
+ detach: function(type, func) {
+ if (typeof type !== 'string') {
+ return Base.each(type, function(value, key) {
+ this.detach(key, value);
+ }, this);
+ }
+ var entry = this._eventTypes[type],
+ handlers = this._handlers && this._handlers[type],
+ index;
+ if (entry && handlers) {
+ if (!func || (index = handlers.indexOf(func)) != -1
+ && handlers.length == 1) {
+ if (entry.uninstall)
+ entry.uninstall.call(this, type);
+ delete this._handlers[type];
+ } else if (index != -1) {
+ handlers.splice(index, 1);
+ }
+ }
+ return this;
+ },
+
+ fire: function(type, event) {
+ var handlers = this._handlers && this._handlers[type];
+ if (!handlers)
+ return false;
+ Base.each(handlers, function(func) {
+ if (func.call(this, event) === false && event && event.stop)
+ event.stop();
+ }, this);
+ return true;
+ },
+
+ responds: function(type) {
+ return !!(this._handlers && this._handlers[type]);
+ },
+
+ statics: {
+ inject: function() {
+ for (var i = 0, l = arguments.length; i < l; i++) {
+ var src = arguments[i],
+ events = src._events;
+ if (events) {
+ var types = {};
+ Base.each(events, function(entry, key) {
+ var isString = typeof entry === 'string',
+ name = isString ? entry : key,
+ part = Base.capitalize(name),
+ type = name.substring(2).toLowerCase();
+ types[type] = isString ? {} : entry;
+ name = '_' + name;
+ src['get' + part] = function() {
+ return this[name];
+ };
+ src['set' + part] = function(func) {
+ if (func) {
+ this.attach(type, func);
+ } else if (this[name]) {
+ this.detach(type, this[name]);
+ }
+ this[name] = func;
+ };
+ });
+ src._eventTypes = types;
+ }
+ this.base(src);
+ }
+ return this;
+ }
+ }
+};
+
+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.length && 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.setIdentity();
+ } 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;
+ },
+
+ setIdentity: function() {
+ this._a = this._d = 1;
+ this._c = this._b = this._tx = this._ty = 0;
+ 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._c = mx._a * c + mx._c * d;
+ this._d = mx._b * c + mx._d * d;
+ this._tx += mx._tx * a + mx._ty * b;
+ 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._b = mx._a * b + mx._b * d;
+ this._c = mx._c * a + mx._d * c;
+ 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, dest, dontNotify) {
+ 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;
+ }
+ if (!dest)
+ dest = new Rectangle(Rectangle.dont);
+ return dest.set(min[0], min[1], max[0] - min[0], max[1] - min[1],
+ dontNotify);
+ },
+
+ 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 Point.create(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 Point.create(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;
+ },
+
+ equals: function(mx) {
+ return this._a == mx._a && this._b == mx._b && this._c == mx._c
+ && this._d == mx._d && this._tx == mx._tx && this._ty == mx._ty;
+ },
+
+ 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(view) {
+ this.base(true);
+ this._currentStyle = new PathStyle();
+ this._selectedItems = {};
+ this._selectedItemCount = 0;
+ this.layers = [];
+ this.symbols = [];
+ this.activeLayer = new Layer();
+ if (view)
+ this.view = view instanceof View ? view : View.create(view);
+ },
+
+ _needsRedraw: function() {
+ if (this.view)
+ this.view._redrawNeeded = true;
+ },
+
+ remove: function() {
+ if (!this.base())
+ return false;
+ if (this.view)
+ this.view.remove();
+ return true;
+ },
+
+ 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._id] = item;
+ } else {
+ this._selectedItemCount--;
+ delete this._selectedItems[item._id];
+ }
+ },
+
+ 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, matrix) {
+ ctx.save();
+ if (!matrix.isIdentity())
+ matrix.applyToContext(ctx);
+ 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';
+ var matrices = {};
+ function getGlobalMatrix(item, mx, cached) {
+ var cache = cached && matrices[item._id];
+ if (cache) {
+ mx.concatenate(cache);
+ return mx;
+ }
+ if (item._parent) {
+ getGlobalMatrix(item._parent, mx, true);
+ if (!item._matrix.isIdentity())
+ mx.concatenate(item._matrix);
+ } else {
+ mx.initialize(item._matrix);
+ }
+ if (cached)
+ matrices[item._id] = mx.clone();
+ return mx;
+ }
+ for (var id in this._selectedItems) {
+ var item = this._selectedItems[id];
+ item.drawSelected(ctx, getGlobalMatrix(item, matrix.clone()));
+ }
+ 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(Callback, {
+ _events: new function() {
+
+ var mouseFlags = {
+ mousedown: {
+ mousedown: 1,
+ mousedrag: 1,
+ click: 1,
+ doubleclick: 1
+ },
+ mouseup: {
+ mouseup: 1,
+ mousedrag: 1,
+ click: 1,
+ doubleclick: 1
+ },
+ mousemove: {
+ mousedrag: 1,
+ mousemove: 1,
+ mouseenter: 1,
+ mouseleave: 1
+ }
+ };
+
+ var mouseEvent = {
+ install: function(type) {
+ var counters = this._project.view._eventCounters;
+ if (counters) {
+ for (var key in mouseFlags) {
+ counters[key] = (counters[key] || 0)
+ + (mouseFlags[key][type] || 0);
+ }
+ }
+ },
+ uninstall: function(type) {
+ var counters = this._project.view._eventCounters;
+ if (counters) {
+ for (var key in mouseFlags)
+ counters[key] -= mouseFlags[key][type] || 0;
+ }
+ }
+ };
+
+ var onFrameItems = [];
+ function onFrame(event) {
+ for (var i = 0, l = onFrameItems.length; i < l; i++)
+ onFrameItems[i].fire('frame', event);
+ }
+
+ return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
+ 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
+ function(name) {
+ this[name] = mouseEvent;
+ }, {
+ onFrame: {
+ install: function() {
+ if (!onFrameItems.length)
+ this._project.view.attach('frame', onFrame);
+ onFrameItems.push(this);
+ },
+ uninstall: function() {
+ onFrameItems.splice(onFrameItems.indexOf(this), 1);
+ if (!onFrameItems.length)
+ this._project.view.detach('frame', onFrame);
+ }
+ }
+ });
+ },
+
+ initialize: function(pointOrMatrix) {
+ this._id = ++Item._id;
+ if (!this._project)
+ paper.project.activeLayer.addChild(this);
+ if (!this._style)
+ this._style = PathStyle.create(this);
+ this.setStyle(this._project.getCurrentStyle());
+ this._matrix = pointOrMatrix !== undefined
+ ? pointOrMatrix instanceof Matrix
+ ? pointOrMatrix.clone()
+ : new Matrix().translate(Point.read(arguments, 0))
+ : new Matrix();
+ },
+
+ _changed: function(flags) {
+ if (flags & ChangeFlag.GEOMETRY) {
+ delete this._bounds;
+ delete this._position;
+ }
+ if (this._parent
+ && (flags & (ChangeFlag.GEOMETRY | ChangeFlag.STROKE))) {
+ this._parent._clearBoundsCache();
+ }
+ if (flags & ChangeFlag.HIERARCHY) {
+ this._clearBoundsCache();
+ }
+ 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 && this._parent) {
+ var children = this._parent._children,
+ namedChildren = this._parent._namedChildren;
+ (namedChildren[name] = namedChildren[name] || []).push(this);
+ children[name] = this;
+ }
+ this._changed(ChangeFlag.ATTRIBUTE);
+ },
+
+ statics: {
+ _id: 0
+ }
+}, 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 && !arguments[1]) {
+ 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, true);
+ },
+
+ 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,
+
+ getPosition: function() {
+ var pos = this._position
+ || (this._position = this.getBounds().getCenter(true));
+ return arguments[0] ? pos
+ : LinkedPoint.create(this, 'setPosition', pos.x, pos.y);
+ },
+
+ setPosition: function(point) {
+ this.translate(Point.read(arguments).subtract(this.getPosition(true)));
+ },
+
+ getMatrix: function() {
+ return this._matrix;
+ },
+
+ setMatrix: function(matrix) {
+ this._matrix.initialize(matrix);
+ this._changed(Change.GEOMETRY);
+ }
+}, Base.each(['bounds', 'strokeBounds', 'handleBounds', 'roughBounds'],
+function(name) {
+ this['get' + Base.capitalize(name)] = function() {
+ var type = this._boundsType,
+ bounds = this._getCachedBounds(
+ typeof type == 'string' ? type : type && type[name] || name,
+ arguments[0]);
+ return name == 'bounds' ? LinkedRectangle.create(this, 'setBounds',
+ bounds.x, bounds.y, bounds.width, bounds.height) : bounds;
+ };
+}, {
+ _getCachedBounds: function(type, matrix, cacheItem) {
+ var cache = (!matrix || matrix.equals(this._matrix)) && type;
+ if (cacheItem && this._parent) {
+ var id = cacheItem._id,
+ ref = this._parent._boundsCache
+ = this._parent._boundsCache || {
+ ids: {},
+ list: []
+ };
+ if (!ref.ids[id]) {
+ ref.list.push(cacheItem);
+ ref.ids[id] = cacheItem;
+ }
+ }
+ if (cache && this._bounds && this._bounds[cache])
+ return this._bounds[cache];
+ var identity = this._matrix.isIdentity();
+ matrix = !matrix || matrix.isIdentity()
+ ? identity ? null : this._matrix
+ : identity ? matrix : matrix.clone().concatenate(this._matrix);
+ var bounds = this._getBounds(type, matrix, cache ? this : cacheItem);
+ if (cache) {
+ if (!this._bounds)
+ this._bounds = {};
+ this._bounds[cache] = bounds.clone();
+ }
+ return bounds;
+ },
+
+ _clearBoundsCache: function() {
+ if (this._boundsCache) {
+ for (var i = 0, list = this._boundsCache.list, l = list.length;
+ i < l; i++) {
+ var item = list[i];
+ delete item._bounds;
+ if (item != this && item._boundsCache)
+ item._clearBoundsCache();
+ }
+ delete this._boundsCache;
+ }
+ },
+
+ _getBounds: function(type, matrix, cacheItem) {
+ 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._getCachedBounds(type, matrix, cacheItem);
+ 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);
+ }
+ }
+ return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
+ },
+
+ 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);
+ }
+
+}), {
+ 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._matrix.initialize(this._matrix);
+ 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) {
+ options = HitResult.getOptions(point, options);
+ point = options.point = this._matrix._inverseTransform(options.point);
+ if (!this._children && !this.getRoughBounds()
+ .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]();
+ 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) : null;
+ },
+
+ _hitTest: function(point, options) {
+ if (this._children) {
+ for (var i = this._children.length - 1; i >= 0; i--) {
+ var res = this._children[i].hitTest(point, options);
+ 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) {
+ this.insertChildren(this._children.length, items);
+ },
+
+ insertChildren: function(index, items) {
+ items = items && Array.prototype.slice.apply(items);
+ for (var i = 0, l = items && items.length; i < l; i++) {
+ if (this.insertChild(index, items[i]))
+ index++;
+ }
+ },
+
+ insertAbove: function(item) {
+ var index = item._index;
+ if (item._parent == this._parent && index < this._index)
+ index++;
+ return item._parent.insertChild(index, this);
+ },
+
+ insertBelow: function(item) {
+ var index = item._index;
+ if (item._parent == this._parent && index > this._index)
+ index--;
+ return item._parent.insertChild(index, 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();
+ if (this._index != null)
+ 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 = Base.splice(this._children, null, 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;
+ },
+
+ scale: function(hor, ver , center, apply) {
+ if (arguments.length < 2 || typeof ver === 'object') {
+ apply = center;
+ center = ver;
+ ver = hor;
+ }
+ return this.transform(new Matrix().scale(hor, ver,
+ center || this.getPosition(true)), apply);
+ },
+
+ translate: function(delta, apply) {
+ var mx = new Matrix();
+ return this.transform(mx.translate.apply(mx, arguments), apply);
+ },
+
+ rotate: function(angle, center, apply) {
+ return this.transform(new Matrix().rotate(angle,
+ center || this.getPosition(true)), apply);
+ },
+
+ shear: function(hor, ver, center, apply) {
+ if (arguments.length < 2 || typeof ver === 'object') {
+ apply = center;
+ center = ver;
+ ver = hor;
+ }
+ return this.transform(new Matrix().shear(hor, ver,
+ center || this.getPosition(true)), apply);
+ },
+
+ transform: function(matrix, apply) {
+ var bounds = this._bounds,
+ position = this._position;
+ this._matrix.preConcatenate(matrix);
+ if (this._transform)
+ this._transform(matrix);
+ if (apply)
+ this.apply();
+ this._changed(Change.GEOMETRY);
+ if (bounds && matrix.getRotation() % 90 === 0) {
+ for (var key in bounds) {
+ var rect = bounds[key];
+ matrix._transformBounds(rect, rect);
+ }
+ var type = this._boundsType,
+ rect = bounds[type && type.bounds || 'bounds'];
+ if (rect)
+ this._position = rect.getCenter(true);
+ this._bounds = bounds;
+ } else if (position) {
+ this._position = matrix._transformPoint(position, position);
+ }
+ return this;
+ },
+
+ apply: function() {
+ if (this._apply(this._matrix)) {
+ this._matrix.setIdentity();
+ }
+ },
+
+ _apply: function(matrix) {
+ if (this._children) {
+ for (var i = 0, l = this._children.length; i < l; i++) {
+ var child = this._children[i];
+ child.transform(matrix);
+ child.apply();
+ }
+ return true;
+ }
+ },
+
+ 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,
+ newBounds = new Rectangle(new Point(),
+ Size.create(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);
+ },
+
+ _setStyles: function(ctx) {
+ var style = this._style,
+ width = style._strokeWidth,
+ join = style._strokeJoin,
+ cap = style._strokeCap,
+ limit = style._miterLimit,
+ fillColor = style._fillColor,
+ strokeColor = style._strokeColor;
+ if (width != null) ctx.lineWidth = width;
+ if (join) ctx.lineJoin = join;
+ if (cap) ctx.lineCap = cap;
+ if (limit) ctx.miterLimit = limit;
+ if (fillColor) ctx.fillStyle = fillColor.getCanvasStyle(ctx);
+ if (strokeColor) ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
+ if (!fillColor || !strokeColor)
+ ctx.globalAlpha = this._opacity;
+ },
+
+ 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,
+ itemOffset, prevOffset;
+ if (item._blendMode !== 'normal' || item._opacity < 1
+ && !(item._segments
+ && (!item.getFillColor() || !item.getStrokeColor()))) {
+ var bounds = item.getStrokeBounds();
+ if (!bounds.width || !bounds.height)
+ return;
+ prevOffset = param.offset;
+ parentCtx = ctx;
+ itemOffset = param.offset = bounds.getTopLeft().floor();
+ tempCanvas = CanvasProvider.getCanvas(
+ bounds.getSize().ceil().add(Size.create(1, 1)));
+ ctx = tempCanvas.getContext('2d');
+ }
+ if (!param.clipping)
+ ctx.save();
+ if (tempCanvas)
+ ctx.translate(-itemOffset.x, -itemOffset.y);
+ item._matrix.applyToContext(ctx);
+ item.draw(ctx, param);
+ if (!param.clipping)
+ ctx.restore();
+ if (tempCanvas) {
+ param.offset = prevOffset;
+ if (item._blendMode !== 'normal') {
+ BlendMode.process(item._blendMode, ctx, parentCtx,
+ item._opacity, itemOffset.subtract(prevOffset));
+ } else {
+ parentCtx.save();
+ parentCtx.globalAlpha = item._opacity;
+ parentCtx.drawImage(tempCanvas, itemOffset.x, itemOffset.y);
+ parentCtx.restore();
+ }
+ CanvasProvider.returnCanvas(tempCanvas);
+ }
+ }
+ }
+}, 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]) {
+ var key = 'mouse' + name,
+ sets = Tool._removeSets = Tool._removeSets || {};
+ sets[key] = sets[key] || {};
+ sets[key][this._id] = this;
+ }
+ }
+ 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) {
+ param.clipping = true;
+ Item.draw(clipItem, ctx, param);
+ delete param.clipping;
+ }
+ 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({
+ _boundsType: { bounds: 'strokeBounds' }
+});
+
+var Raster = this.Raster = PlacedItem.extend({
+ _boundsType: 'bounds',
+
+ initialize: function(object, pointOrMatrix) {
+ this.base(pointOrMatrix);
+ if (object.getContext) {
+ this.setCanvas(object);
+ } else {
+ if (typeof object === 'string')
+ object = document.getElementById(object);
+ this.setImage(object);
+ }
+ },
+
+ 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);
+ 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 Size.create(
+ 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 = Size.create(canvas.width, canvas.height);
+ this._image = null;
+ this._context = null;
+ this._changed(Change.GEOMETRY | Change.PIXELS);
+ },
+
+ getImage: function() {
+ return this._image || this.getCanvas();
+ },
+
+ setImage: function(image) {
+ if (this._canvas)
+ CanvasProvider.returnCanvas(this._canvas);
+ this._image = image;
+ this._size = Size.create(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) {
+ var bounds, path;
+ if (!object) {
+ bounds = this.getBounds();
+ } else 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);
+ },
+
+ _getBounds: function(type, matrix) {
+ var rect = new Rectangle(this._size).setCenter(0, 0);
+ return matrix ? matrix._transformBounds(rect) : rect;
+ },
+
+ _hitTest: function(point, options) {
+ if (point.isInside(this._getBounds())) {
+ var that = this;
+ return new HitResult('pixel', that, {
+ offset: point.add(that._size.divide(2)).round(),
+ color: {
+ get: function() {
+ return that.getPixel(this.offset);
+ }
+ }
+ });
+ }
+ },
+
+ draw: function(ctx, param) {
+ ctx.drawImage(this._canvas || this._image,
+ -this._size.width / 2, -this._size.height / 2);
+ },
+
+ drawSelected: function(ctx, matrix) {
+ Item.drawSelectedBounds(new Rectangle(this._size).setCenter(0, 0), ctx,
+ matrix);
+ }
+});
+
+var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend({
+ initialize: function(symbol, pointOrMatrix) {
+ this.base(pointOrMatrix);
+ this.setSymbol(symbol instanceof Symbol ? symbol : new Symbol(symbol));
+ },
+
+ 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()));
+ },
+
+ _getBounds: function(type, matrix) {
+ return this.symbol._definition._getCachedBounds(type, matrix);
+ },
+
+ draw: function(ctx, param) {
+ Item.draw(this.symbol._definition, ctx, param);
+ },
+
+ drawSelected: function(ctx, matrix) {
+ Item.drawSelectedBounds(this.symbol._definition.getBounds(), ctx,
+ matrix);
+ }
+
+});
+
+HitResult = Base.extend({
+ initialize: function(type, item, values) {
+ this.type = type;
+ this.item = item;
+ if (values) {
+ values.enumerable = true;
+ this.inject(values);
+ }
+ },
+
+ 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;
+ }
+ }
+ 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);
+ path._changed(Change.ATTRIBUTE);
+ }
+ },
+
+ 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;
+ },
+
+ clone: function() {
+ return new Segment(this._point, this._handleIn, this._handleOut);
+ },
+
+ equals: function(segment) {
+ return segment == this || segment
+ && this._point.equals(segment._point)
+ && this._handleIn.equals(segment._handleIn)
+ && this._handleOut.equals(segment._handleOut);
+ },
+
+ 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() {
+ return Curve.getValues(this._segment1, this._segment2);
+ },
+
+ getPoints: function() {
+ var coords = this.getValues(),
+ 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, roots) {
+ var vals = this.getValues(),
+ 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(), 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) {
+ var p1 = segment1._point,
+ h1 = segment1._handleOut,
+ h2 = segment2._handleIn,
+ p2 = segment2._point;
+ return [
+ p1._x, p1._y,
+ p1._x + h1._x, p1._y + h1._y,
+ p2._x + h2._x, p2._y + h2._y,
+ p2._x, p2._y
+ ];
+ },
+
+ 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) {
+ if (Math.abs(v[0] - x) < Numerical.TOLERANCE
+ && Math.abs(v[1] - y) < Numerical.TOLERANCE)
+ return 0;
+ if (Math.abs(v[6] - x) < Numerical.TOLERANCE
+ && Math.abs(v[7] - y) < Numerical.TOLERANCE)
+ return 1;
+ 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],
+ ux = 3 * c1x - 2 * p1x - p2x,
+ uy = 3 * c1y - 2 * p1y - p2y,
+ vx = 3 * c2x - 2 * p2x - p1x,
+ vy = 3 * c2y - 2 * p2y - p1y;
+ return Math.max(ux * ux, vx * vx) + Math.max(uy * uy, vy * vy) < 1;
+ }
+ }
+}, 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) {
+ var w = toBezierForm(this.getPoints(), 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) {
+ return this.getNearestLocation(point).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._length;
+ delete this._clockwise;
+ if (this._curves != null) {
+ for (var i = 0, l = this._curves.length; i < l; i++) {
+ this._curves[i]._changed(Change.GEOMETRY);
+ }
+ }
+ } else if (flags & ChangeFlag.STROKE) {
+ delete this._bounds;
+ }
+ },
+
+ 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) {
+ return this.base(matrix, true);
+ },
+
+ getMatrix: function() {
+ return null;
+ },
+
+ setMatrix: function(matrix) {
+ },
+
+ _apply: function(matrix) {
+ var coords = new Array(6);
+ for (var i = 0, l = this._segments.length; i < l; i++) {
+ this._segments[i]._transformCoordinates(matrix, coords, true);
+ }
+ var style = this._style,
+ fillColor = style._fillColor,
+ strokeColor = style._strokeColor;
+ if (fillColor && fillColor.transform)
+ fillColor.transform(matrix);
+ if (strokeColor && strokeColor.transform)
+ strokeColor.transform(matrix);
+ return true;
+ },
+
+ _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;
+ this.setSelected(total > 0);
+ },
+
+ 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;
+ segment._index = i;
+ }
+ 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) {
+ var curves = this.getCurves(),
+ minDist = Infinity,
+ minLoc = null;
+ for (var i = 0, l = curves.length; i < l; i++) {
+ var loc = curves[i].getNearestLocation(point);
+ if (loc._distance < minDist) {
+ minDist = loc._distance;
+ minLoc = loc;
+ }
+ }
+ return minLoc;
+ },
+
+ getNearestPoint: function(point) {
+ return this.getNearestLocation(point).getPoint();
+ },
+
+ contains: function(point) {
+ point = Point.read(arguments);
+ if (!this._closed || !this.getRoughBounds()._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, roots);
+ return (crossings & 1) == 1;
+ },
+
+ _hitTest: function(point, options) {
+ var style = this._style,
+ tolerance = options.tolerance || 0,
+ radius = (options.stroke && style._strokeColor
+ ? style._strokeWidth / 2 : 0) + tolerance,
+ loc,
+ res;
+ var coords = [],
+ that = this;
+ function checkPoint(seg, pt, name) {
+ if (point.getDistance(pt) < tolerance)
+ return new HitResult(name, that, { segment: seg, point: pt });
+ }
+ function checkSegment(seg, ends) {
+ var point = seg._point;
+ return (ends || options.segments)
+ && checkPoint(seg, point, 'segment')
+ || (!ends && options.handles) && (
+ checkPoint(seg, point.add(seg._handleIn), 'handle-in') ||
+ checkPoint(seg, point.add(seg._handleOut), 'handle-out'));
+ }
+ 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);
+ if (!(loc && loc._distance <= radius) && options.fill
+ && style._fillColor && this.contains(point))
+ return new HitResult('fill', this);
+ if (!loc && options.stroke && radius > 0)
+ loc = this.getNearestLocation(point);
+ if (loc && loc._distance <= radius)
+ return options.stroke
+ ? new HitResult('stroke', this, { location: loc })
+ : new HitResult('fill', this);
+ }
+
+}, new function() {
+
+ function drawHandles(ctx, segments, matrix) {
+ var coords = new Array(6);
+ for (var i = 0, l = segments.length; i < l; i++) {
+ var segment = segments[i];
+ segment._transformCoordinates(matrix, coords, false);
+ var state = segment._selectionState,
+ selected = state & SelectionState.POINT,
+ pX = coords[0],
+ pY = coords[1];
+
+ function drawHandle(index) {
+ var hX = coords[index],
+ hY = coords[index + 1];
+ if (pX != hX || pY != hY) {
+ ctx.beginPath();
+ ctx.moveTo(pX, pY);
+ ctx.lineTo(hX, hY);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(hX, hY, 1.75, 0, Math.PI * 2, true);
+ ctx.fill();
+ }
+ }
+
+ if (selected || (state & SelectionState.HANDLE_IN))
+ drawHandle(2);
+ if (selected || (state & SelectionState.HANDLE_OUT))
+ drawHandle(4);
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(pX - 2, pY - 2, 4, 4);
+ ctx.fill();
+ if (!selected) {
+ ctx.beginPath();
+ ctx.rect(pX - 1, pY - 1, 2, 2);
+ ctx.fillStyle = '#ffffff';
+ ctx.fill();
+ }
+ ctx.restore();
+ }
+ }
+
+ function drawSegments(ctx, path, matrix) {
+ var segments = path._segments,
+ length = segments.length,
+ coords = new Array(6),
+ first = true,
+ curX, curY,
+ prevX, prevY,
+ inX, inY,
+ outX, outY;
+
+ function drawSegment(i) {
+ var segment = segments[i];
+ if (matrix) {
+ segment._transformCoordinates(matrix, coords, false);
+ curX = coords[0];
+ curY = coords[1];
+ } else {
+ var point = segment._point;
+ curX = point._x;
+ curY = point._y;
+ }
+ if (first) {
+ ctx.moveTo(curX, curY);
+ first = false;
+ } else {
+ if (matrix) {
+ inX = coords[2];
+ inY = coords[3];
+ } else {
+ var handle = segment._handleIn;
+ inX = curX + handle._x;
+ inY = curY + handle._y;
+ }
+ if (inX == curX && inY == curY && outX == prevX && outY == prevY) {
+ ctx.lineTo(curX, curY);
+ } else {
+ ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY);
+ }
+ }
+ prevX = curX;
+ prevY = curY;
+ if (matrix) {
+ outX = coords[4];
+ outY = coords[5];
+ } else {
+ var handle = segment._handleOut;
+ outX = prevX + handle._x;
+ outY = prevY + handle._y;
+ }
+ }
+
+ for (var i = 0; i < length; i++)
+ drawSegment(i);
+ if (path._closed && length > 1)
+ drawSegment(0);
+ }
+
+ return {
+ draw: function(ctx, param) {
+ if (!param.compound)
+ ctx.beginPath();
+
+ var style = this._style,
+ fillColor = style._fillColor,
+ strokeColor = style._strokeColor,
+ dashArray = style._dashArray,
+ hasDash = strokeColor && dashArray && dashArray.length;
+
+ if (param.compound || this._clipMask || fillColor
+ || strokeColor && !hasDash) {
+ drawSegments(ctx, this);
+ }
+
+ if (this._closed)
+ ctx.closePath();
+
+ if (this._clipMask) {
+ ctx.clip();
+ } else if (!param.compound && (fillColor || strokeColor)) {
+ ctx.save();
+ this._setStyles(ctx);
+ if (fillColor)
+ ctx.fill();
+ if (strokeColor) {
+ if (hasDash) {
+ ctx.beginPath();
+ var flattener = new PathFlattener(this),
+ from = style._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];
+ }
+ }
+ ctx.stroke();
+ }
+ ctx.restore();
+ }
+ },
+
+ drawSelected: function(ctx, matrix) {
+ ctx.beginPath();
+ drawSegments(ctx, this, matrix);
+ ctx.stroke();
+ drawHandles(ctx, this._segments, matrix);
+ if (this._selectedSegmentState == 0) {
+ Item.drawSelectedBounds(this.getBounds(), ctx, matrix);
+ }
+ }
+ };
+}, 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;
+ };
+
+ return {
+ 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(
+ Point.create(x[i], y[i]).subtract(segment._point));
+ if (i < n - 1)
+ handleIn = Point.create(
+ 2 * knots[i + 1]._x - x[i + 1],
+ 2 * knots[i + 1]._y - y[i + 1]);
+ else
+ handleIn = Point.create(
+ (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(matrix, strokePadding) {
+ var segments = this._segments,
+ first = segments[0];
+ if (!first)
+ return null;
+ var coords = new Array(6),
+ prevCoords = new Array(6);
+ 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 (this._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(Point.create(radius, 0)),
+ ver = mx.transform(Point.create(0, radius)),
+ phi = hor.getAngleInRadians(),
+ a = hor.getLength(),
+ b = ver.getLength();
+ var sin = Math.sin(phi),
+ cos = Math.cos(phi),
+ tan = Math.tan(phi),
+ tx = -Math.atan(b * tan / a),
+ ty = Math.atan(b / (tan * a));
+ return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin),
+ Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
+ }
+
+ function getStrokeBounds(matrix) {
+ var style = this._style;
+ if (!style._strokeColor || !style._strokeWidth)
+ return getBounds.call(this, matrix);
+ var width = style._strokeWidth,
+ radius = width / 2,
+ padding = getPenPadding(radius, matrix),
+ join = style._strokeJoin,
+ cap = style._strokeCap,
+ miter = style._miterLimit * width / 2,
+ segments = this._segments,
+ length = segments.length,
+ bounds = getBounds.call(this, matrix, padding);
+ var joinBounds = new Rectangle(new Size(padding).multiply(2));
+
+ function add(point) {
+ bounds = bounds.include(matrix
+ ? matrix._transformPoint(point, 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._transformPoint(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),
+ Point.create(-normal1.y, normal1.x)),
+ line2 = new Line(point.subtract(normal2),
+ Point.create(-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.rotate(t == 0 ? -90 : 90));
+ 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);
+ }
+ return bounds;
+ }
+
+ function getHandleBounds(matrix, stroke, join) {
+ var coords = new Array(6),
+ x1 = Infinity,
+ x2 = -x1,
+ y1 = x1,
+ y2 = x2;
+ stroke = stroke / 2 || 0;
+ join = join / 2 || 0;
+ 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;
+ }
+ }
+ return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
+ }
+
+ function getRoughBounds(matrix) {
+ var style = this._style,
+ width = style._strokeWidth;
+ return getHandleBounds.call(this, matrix, width,
+ style._strokeJoin == 'miter'
+ ? width * style._miterLimit
+ : width);
+ }
+
+ var get = {
+ bounds: getBounds,
+ strokeBounds: getStrokeBounds,
+ handleBounds: getHandleBounds,
+ roughBounds: getRoughBounds
+ };
+
+ return {
+ _getBounds: function(type, matrix) {
+ return get[type].call(this, matrix);
+ }
+ };
+});
+
+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 children = this._children;
+ if (children.length == 0)
+ return;
+ var firstChild = children[0],
+ style = firstChild._style;
+ ctx.beginPath();
+ param.compound = true;
+ for (var i = 0, l = children.length; i < l; i++)
+ Item.draw(children[i], ctx, param);
+ firstChild._setStyles(ctx);
+ if (style._fillColor)
+ ctx.fill();
+ if (style._strokeColor)
+ 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(c1) > 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({
+ _boundsType: 'bounds',
+
+ initialize: function(pointOrMatrix) {
+ this._style = CharacterStyle.create(this);
+ this._paragraphStyle = ParagraphStyle.create(this);
+ this.base(pointOrMatrix);
+ this.setParagraphStyle();
+ this._content = '';
+ this._lines = [];
+ },
+
+ _clone: function(copy) {
+ copy.setContent(this._content);
+ copy.setParagraphStyle(this._paragraphStyle);
+ return this.base(copy);
+ },
+
+ getContent: function() {
+ return this._content;
+ },
+
+ setContent: function(content) {
+ this._content = '' + content;
+ this._lines = this._content.split(/\r\n|\n|\r/mg);
+ this._changed(Change.CONTENT);
+ },
+
+ getCharacterStyle: function() {
+ return this.getStyle();
+ },
+
+ setCharacterStyle: function(style) {
+ this.setStyle(style);
+ }
+
+});
+
+var PointText = this.PointText = TextItem.extend({
+ initialize: function(pointOrMatrix) {
+ this.base(pointOrMatrix);
+ this._point = this._matrix.getTranslation();
+ },
+
+ clone: function() {
+ return this._clone(new PointText(this._matrix));
+ },
+
+ getPoint: function() {
+ return LinkedPoint.create(this, 'setPoint',
+ this._point.x, this._point.y);
+ },
+
+ setPoint: function(point) {
+ this.translate(Point.read(arguments).subtract(this._point));
+ },
+
+ _transform: function(matrix) {
+ matrix._transformPoint(this._point, this._point);
+ },
+
+ draw: function(ctx) {
+ if (!this._content)
+ return;
+ this._setStyles(ctx);
+ var style = this._style,
+ leading = this.getLeading(),
+ lines = this._lines;
+ ctx.font = style.getFontStyle();
+ ctx.textAlign = this.getJustification();
+ for (var i = 0, l = lines.length; i < l; i++) {
+ var line = lines[i];
+ if (style._fillColor)
+ ctx.fillText(line, 0, 0);
+ if (style._strokeColor)
+ ctx.strokeText(line, 0, 0);
+ ctx.translate(0, leading);
+ }
+ }
+}, new function() {
+ var context = null;
+
+ return {
+ _getBounds: function(type, matrix) {
+ if (!context)
+ context = CanvasProvider.getCanvas(
+ Size.create(1, 1)).getContext('2d');
+ var justification = this.getJustification(),
+ x = 0;
+ context.font = this._style.getFontStyle();
+ var width = 0;
+ for (var i = 0, l = this._lines.length; i < l; i++)
+ width = Math.max(width, context.measureText(
+ this._lines[i]).width);
+ if (justification !== 'left')
+ x -= width / (justification === 'center' ? 2: 1);
+ var leading = this.getLeading(),
+ count = this._lines.length,
+ bounds = Rectangle.create(x,
+ count ? leading / 4 + (count - 1) * leading : 0,
+ width, -count * leading);
+ return matrix ? matrix._transformBounds(bounds, bounds) : bounds;
+ }
+ };
+});
+
+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,
+ stylePart = Base.capitalize(src._style),
+ flags = src._flags || {},
+ owner = {};
+
+ owner['get' + stylePart] = function() {
+ return this[styleKey];
+ };
+
+ owner['set' + stylePart] = function(style) {
+ this[styleKey].initialize(style);
+ };
+
+ 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;
+ };
+ owner[set] = function(value) {
+ this[styleKey][set](value);
+ return this;
+ };
+ owner[get] = function() {
+ return this[styleKey][get]();
+ };
+ });
+ src._owner.inject(owner);
+ return this.base.apply(this, arguments);
+ }
+ }
+});
+
+var PathStyle = this.PathStyle = Style.extend({
+ _owner: Item,
+ _style: 'style',
+ _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
+ }
+
+});
+
+var ParagraphStyle = this.ParagraphStyle = Style.extend({
+ _owner: TextItem,
+ _style: 'paragraphStyle',
+ _defaults: {
+ justification: 'left'
+ },
+ _flags: {
+ justification: Change.GEOMETRY
+ }
+
+});
+
+var CharacterStyle = this.CharacterStyle = PathStyle.extend({
+ _owner: TextItem,
+ _style: 'style',
+ _defaults: Base.merge(PathStyle.prototype._defaults, {
+ fillColor: 'black',
+ fontSize: 12,
+ leading: null,
+ font: 'sans-serif'
+ }),
+ _flags: {
+ fontSize: Change.GEOMETRY,
+ leading: Change.GEOMETRY,
+ font: Change.GEOMETRY
+ }
+
+}, {
+ getLeading: function() {
+ var leading = this.base();
+ return leading != null ? leading : this.getFontSize() * 1.2;
+ },
+
+ getFontStyle: function() {
+ return this._fontSize + 'px ' + this._font;
+ }
+});
+
+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;
+ 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) {
+ 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.gradient._addOwner(this);
+ 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';
+ },
+
+ _changed: function() {
+ for (var i = 0, l = this._owners && this._owners.length; i < l; i++)
+ this._owners[i]._changed();
+ },
+
+ _addOwner: function(color) {
+ if (!this._owners)
+ this._owners = [];
+ this._owners.push(color);
+ },
+
+ _removeOwner: function(color) {
+ var index = this._owners ? this._owners.indexOf(color) : -1;
+ if (index != -1) {
+ this._owners.splice(index, 1);
+ if (this._owners.length == 0)
+ delete this._owners;
+ }
+ },
+
+ 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 (this.stops) {
+ for (var i = 0, l = this._stops.length; i < l; i++) {
+ this._stops[i]._removeOwner(this);
+ }
+ }
+ 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];
+ stop._addOwner(this);
+ if (stop._defaultRamp)
+ stop.setRampPoint(i / (l - 1));
+ }
+ this._changed();
+ },
+
+ 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);
+ },
+
+ _changed: function() {
+ for (var i = 0, l = this._owners && this._owners.length; i < l; i++)
+ this._owners[i]._changed(Change.STYLE);
+ },
+
+ _addOwner: function(gradient) {
+ if (!this._owners)
+ this._owners = [];
+ this._owners.push(gradient);
+ },
+
+ _removeOwner: function(gradient) {
+ var index = this._owners ? this._owners.indexOf(gradient) : -1;
+ if (index != -1) {
+ this._owners.splice(index, 1);
+ if (this._owners.length == 0)
+ delete this._owners;
+ }
+ },
+
+ getRampPoint: function() {
+ return this._rampPoint;
+ },
+
+ setRampPoint: function(rampPoint) {
+ this._defaultRamp = rampPoint == null;
+ this._rampPoint = rampPoint || 0;
+ this._changed();
+ },
+
+ getColor: function() {
+ return this._color;
+ },
+
+ setColor: function(color) {
+ if (this._color)
+ this._color._removeOwner(this);
+ this._color = Color.read(arguments);
+ this._color._addOwner(this);
+ this._changed();
+ },
+
+ 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 = Base.extend(Callback, {
+ _events: {
+ onFrame: {
+ install: function() {
+ var that = this,
+ requested = false,
+ before,
+ time = 0,
+ count = 0;
+ this._onFrameCallback = function(param, dontRequest) {
+ requested = false;
+ if (!that._onFrameCallback)
+ return;
+ paper = that._scope;
+ if (!dontRequest) {
+ requested = true;
+ DomEvent.requestAnimationFrame(that._onFrameCallback,
+ that._element);
+ }
+ var now = Date.now() / 1000,
+ delta = before ? now - before : 0;
+ that.fire('frame', Base.merge({
+ delta: delta,
+ time: time += delta,
+ count: count++
+ }));
+ before = now;
+ if (that._stats)
+ that._stats.update();
+ that.draw(true);
+ };
+ if (!requested)
+ this._onFrameCallback();
+ },
+
+ uninstall: function() {
+ delete this._onFrameCallback;
+ }
+ },
+
+ onResize: {}
+ },
+
+ initialize: function(element) {
+ this._scope = paper;
+ this._project = paper.project;
+ this._element = element;
+ var size;
+ this._id = element.getAttribute('id');
+ if (this._id == null)
+ element.setAttribute('id', this._id = 'view-' + View._id++);
+ DomEvent.add(element, this._handlers);
+ if (PaperScript.hasAttribute(element, 'resize')) {
+ var offset = DomElement.getOffset(element, true),
+ that = this;
+ size = DomElement.getViewportBounds(element)
+ .getSize().subtract(offset);
+ element.width = size.width;
+ element.height = size.height;
+ this._windowHandlers = {
+ resize: function(event) {
+ if (!DomElement.isInvisible(element))
+ offset = DomElement.getOffset(element, true);
+ that.setViewSize(DomElement.getViewportBounds(element)
+ .getSize().subtract(offset));
+ }
+ };
+ DomEvent.add(window, this._windowHandlers);
+ } else {
+ size = DomElement.isInvisible(element)
+ ? Size.create(parseInt(element.getAttribute('width')),
+ parseInt(element.getAttribute('height')))
+ : DomElement.getSize(element);
+ }
+ if (PaperScript.hasAttribute(element, 'stats')) {
+ this._stats = new Stats();
+ var stats = this._stats.domElement,
+ style = stats.style,
+ offset = DomElement.getOffset(element);
+ style.position = 'absolute';
+ style.left = offset.x + 'px';
+ style.top = offset.y + 'px';
+ document.body.appendChild(stats);
+ }
+ View._views.push(this);
+ View._viewsById[this._id] = this;
+ this._viewSize = LinkedSize.create(this, 'setViewSize',
+ size.width, size.height);
+ this._matrix = new Matrix();
+ this._zoom = 1;
+ if (!View._focused)
+ View._focused = this;
+ },
+
+ remove: function() {
+ if (!this._project)
+ return false;
+ if (View._focused == this)
+ View._focused = null;
+ View._views.splice(View._views.indexOf(this), 1);
+ delete View._viewsById[this._id];
+ if (this._project.view == this)
+ this._project.view = null;
+ DomEvent.remove(this._element, this._handlers);
+ DomEvent.remove(window, this._windowHandlers);
+ this._element = this._project = null;
+ this.detach('frame');
+ return true;
+ },
+
+ _redraw: function() {
+ this._redrawNeeded = true;
+ if (this._onFrameCallback) {
+ this._onFrameCallback(0, true);
+ } else {
+ this.draw();
+ }
+ },
+
+ _transform: function(matrix) {
+ this._matrix.preConcatenate(matrix);
+ this._bounds = null;
+ this._inverse = null;
+ this._redraw();
+ },
+
+ getElement: function() {
+ return this._element;
+ },
+
+ getViewSize: function() {
+ return this._viewSize;
+ },
+
+ setViewSize: function(size) {
+ size = Size.read(arguments);
+ var delta = size.subtract(this._viewSize);
+ if (delta.isZero())
+ return;
+ this._element.width = size.width;
+ this._element.height = size.height;
+ this._viewSize.set(size.width, size.height, true);
+ this._bounds = null;
+ this._redrawNeeded = true;
+ this.fire('resize', {
+ 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._element);
+ },
+
+ scrollBy: function(point) {
+ this._transform(new Matrix().translate(Point.read(arguments).negate()));
+ },
+
+ 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;
+ }
+
+}, {
+ statics: {
+ _views: [],
+ _viewsById: {},
+ _id: 0,
+
+ create: function(element) {
+ if (typeof element === 'string')
+ element = document.getElementById(element);
+ return new CanvasView(element);
+ }
+ }
+}, new function() {
+ var tool,
+ curPoint,
+ tempFocus,
+ dragging = false;
+
+ function getView(event) {
+ return View._viewsById[DomEvent.getTarget(event).getAttribute('id')];
+ }
+
+ function viewToProject(view, event) {
+ return view.viewToProject(DomEvent.getOffset(event, view._element));
+ }
+
+ function updateFocus() {
+ if (!View._focused || !View._focused.isVisible()) {
+ for (var i = 0, l = View._views.length; i < l; i++) {
+ var view = View._views[i];
+ if (view && view.isVisible()) {
+ View._focused = tempFocus = view;
+ break;
+ }
+ }
+ }
+ }
+
+ function mousedown(event) {
+ var view = View._focused = getView(event);
+ curPoint = viewToProject(view, event);
+ dragging = true;
+ if (view._onMouseDown)
+ view._onMouseDown(event, curPoint);
+ if (tool = view._scope.tool)
+ tool._onHandleEvent('mousedown', curPoint, event);
+ view.draw(true);
+ }
+
+ function mousemove(event) {
+ var view;
+ if (!dragging) {
+ view = getView(event);
+ if (view) {
+ View._focused = tempFocus = view;
+ } else if (tempFocus && tempFocus == View._focused) {
+ View._focused = null;
+ updateFocus();
+ }
+ }
+ if (!(view = view || View._focused))
+ return;
+ var point = event && viewToProject(view, event);
+ if (view._onMouseMove)
+ view._onMouseMove(event, point);
+ if (tool = view._scope.tool) {
+ var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove);
+ if (dragging && !onlyMove) {
+ if ((curPoint = point || curPoint)
+ && tool._onHandleEvent('mousedrag', curPoint, event))
+ DomEvent.stop(event);
+ } else if ((!dragging || onlyMove)
+ && tool._onHandleEvent('mousemove', point, event)) {
+ DomEvent.stop(event);
+ }
+ }
+ view.draw(true);
+ }
+
+ function mouseup(event) {
+ var view = View._focused;
+ if (!view || !dragging)
+ return;
+ var point = viewToProject(view, event);
+ curPoint = null;
+ dragging = false;
+ if (view._onMouseUp)
+ view._onMouseUp(event, point);
+ if (tool && tool._onHandleEvent('mouseup', point, event))
+ DomEvent.stop(event);
+ view.draw(true);
+ }
+
+ 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 {
+ _handlers: {
+ mousedown: mousedown,
+ touchstart: mousedown,
+ selectstart: selectstart
+ },
+
+ statics: {
+ updateFocus: updateFocus
+ }
+ };
+});
+
+var CanvasView = View.extend({
+ initialize: function(canvas) {
+ if (!(canvas instanceof HTMLCanvasElement)) {
+ var size = Size.read(arguments, 1);
+ if (size.isZero())
+ size = Size.create(1024, 768);
+ canvas = CanvasProvider.getCanvas(size);
+ }
+ this._context = canvas.getContext('2d');
+ this._eventCounters = {};
+ this.base(canvas);
+ },
+
+ draw: function(checkRedraw) {
+ if (checkRedraw && !this._redrawNeeded)
+ return false;
+ var ctx = this._context,
+ size = this._viewSize;
+ ctx.clearRect(0, 0, size._width + 1, size._height + 1);
+ this._project.draw(ctx, this._matrix);
+ this._redrawNeeded = false;
+ return true;
+ }
+}, new function() {
+
+ var hitOptions = {
+ fill: true,
+ stroke: true,
+ tolerance: 0
+ };
+
+ var downPoint,
+ lastPoint,
+ overPoint,
+ downItem,
+ overItem,
+ hasDrag,
+ doubleClick,
+ clickTime;
+
+ function callEvent(type, event, point, target, lastPoint, bubble) {
+ var item = target,
+ mouseEvent,
+ called = false;
+ while (item) {
+ if (item.responds(type)) {
+ if (!mouseEvent)
+ mouseEvent = new MouseEvent(type, event, point, target,
+ lastPoint ? point.subtract(lastPoint) : null);
+ called = item.fire(type, mouseEvent) || called;
+ if (called && (!bubble || mouseEvent._stopped))
+ break;
+ }
+ item = item.getParent();
+ }
+ return called;
+ }
+
+ function handleEvent(view, type, event, point, lastPoint) {
+ if (view._eventCounters[type]) {
+ var hit = view._project.hitTest(point, hitOptions),
+ item = hit && hit.item;
+ if (item) {
+ if (type == 'mousemove' && item != overItem)
+ lastPoint = point;
+ if (type != 'mousemove' || !hasDrag)
+ callEvent(type, event, point, item, lastPoint);
+ return item;
+ }
+ }
+ }
+
+ return {
+ _onMouseDown: function(event, point) {
+ var item = handleEvent(this, 'mousedown', event, point);
+ doubleClick = downItem == item && Date.now() - clickTime < 300;
+ downItem = item;
+ downPoint = lastPoint = overPoint = point;
+ hasDrag = downItem && downItem.responds('mousedrag');
+ },
+
+ _onMouseUp: function(event, point) {
+ var item = handleEvent(this, 'mouseup', event, point);
+ if (hasDrag) {
+ if (lastPoint && !lastPoint.equals(point))
+ callEvent('mousedrag', event, point, downItem, lastPoint);
+ if (item != downItem) {
+ overPoint = point;
+ callEvent('mousemove', event, point, item, overPoint);
+ }
+ }
+ if (item == downItem) {
+ clickTime = Date.now();
+ callEvent(doubleClick ? 'doubleclick' : 'click', event,
+ downPoint, overItem);
+ doubleClick = false;
+ }
+ downItem = null;
+ hasDrag = false;
+ },
+
+ _onMouseMove: function(event, point) {
+ if (downItem)
+ callEvent('mousedrag', event, point, downItem, lastPoint);
+ var item = handleEvent(this, 'mousemove', event, point, overPoint);
+ lastPoint = overPoint = point;
+ if (item != overItem) {
+ callEvent('mouseleave', event, point, overItem);
+ overItem = item;
+ callEvent('mouseenter', event, point, item);
+ }
+ }
+ };
+});
+
+var Event = this.Event = Base.extend({
+ initialize: function(event) {
+ this.event = event;
+ },
+
+ preventDefault: function() {
+ this._prevented = true;
+ DomEvent.preventDefault(this.event);
+ return this;
+ },
+
+ stopPropagation: function() {
+ this._stopped = true;
+ DomEvent.stopPropagation(this.event);
+ return this;
+ },
+
+ stop: function() {
+ return this.stopPropagation().preventDefault();
+ },
+
+ getModifiers: function() {
+ return Key.modifiers;
+ }
+});
+
+var KeyEvent = this.KeyEvent = Event.extend({
+ 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',
+ 9: 'tab',
+ 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(),
+ type = down ? 'keydown' : 'keyup',
+ view = View._focused,
+ scope = view && view.isVisible() && view._scope,
+ tool = scope && scope.tool;
+ keyMap[key] = down;
+ if (tool && tool.responds(type)) {
+ tool.fire(type, new KeyEvent(down, key, character, event));
+ 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 MouseEvent = this.MouseEvent = Event.extend({
+ initialize: function(type, event, point, target, delta) {
+ this.base(event);
+ this.type = type;
+ this.point = point;
+ this.target = target;
+ this.delta = delta;
+ },
+
+ toString: function() {
+ return '{ type: ' + this.type
+ + ', point: ' + this.point
+ + ', target: ' + this.target
+ + (this.delta ? ', delta: ' + this.delta : '')
+ + ', modifiers: ' + this.getModifiers()
+ + ' }';
+ }
+});
+
+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(Callback, {
+ _list: 'tools',
+ _reference: '_tool',
+ _events: [ 'onEditOptions', 'onSelect', 'onDeselect', 'onReselect',
+ 'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
+ 'onKeyDown', 'onKeyUp' ],
+
+ initialize: function() {
+ this.base();
+ this._firstMove = true;
+ this._count = 0;
+ this._downCount = 0;
+ },
+
+ 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 sets = Tool._removeSets;
+ if (sets) {
+ if (type === 'mouseup')
+ sets.mousedrag = null;
+ var set = sets[type];
+ if (set) {
+ for (var id in set) {
+ var item = set[id];
+ for (var key in sets) {
+ var other = sets[key];
+ if (other && other != set && other[item._id])
+ delete other[item._id];
+ }
+ item.remove();
+ }
+ sets[type] = null;
+ }
+ }
+ var called = false;
+ switch (type) {
+ case 'mousedown':
+ this._updateEvent(type, pt, null, null, true, false, false);
+ if (this.responds(type))
+ called = this.fire(type, new ToolEvent(this, type, event));
+ break;
+ case 'mousedrag':
+ var needsChange = false,
+ matchMaxDistance = false;
+ while (this._updateEvent(type, pt, this.minDistance,
+ this.maxDistance, false, needsChange, matchMaxDistance)) {
+ if (this.responds(type))
+ called = this.fire(type, new ToolEvent(this, type, event));
+ 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.responds('mousedrag'))
+ called = this.fire('mousedrag',
+ new ToolEvent(this, type, event));
+ }
+ this._updateEvent(type, pt, null, this.maxDistance, false,
+ false, false);
+ if (this.responds(type))
+ called = this.fire(type, new ToolEvent(this, type, event));
+ 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.responds(type))
+ called = this.fire(type, new ToolEvent(this, type, event));
+ 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;
+ }
+ }
+ };
+};
+
+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.project && scope.project.view,
+ res;
+ with (scope) {
+ (function() {
+ var onEditOptions, onSelect, onDeselect, onReselect,
+ onMouseDown, onMouseUp, onMouseDrag, onMouseMove,
+ onKeyDown, onKeyUp, onFrame, onResize;
+ res = eval(compile(code));
+ if (/on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)) {
+ Base.each(Tool.prototype._events, function(key) {
+ var value = eval(key);
+ if (value) {
+ scope.getTool()[key] = value;
+ }
+ });
+ }
+ if (view) {
+ view.setOnResize(onResize);
+ view.fire('resize', {
+ size: view.size,
+ delta: new Point()
+ });
+ 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));
+};