diff --git a/leander_herzog/organicIllusion/organicillusion.html b/leander_herzog/organicIllusion/organicillusion.html
new file mode 100644
index 0000000..ad86f5d
--- /dev/null
+++ b/leander_herzog/organicIllusion/organicillusion.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Organic Illusion
+
+
+
+
+
+
+
+
diff --git a/leander_herzog/organicIllusion/organicillusion.js b/leander_herzog/organicIllusion/organicillusion.js
new file mode 100644
index 0000000..3df3029
--- /dev/null
+++ b/leander_herzog/organicIllusion/organicillusion.js
@@ -0,0 +1,91 @@
+/**
+ * This sketch is part of the ReCode Project - http://recodeproject.com
+ * From Computer Graphics and Art vol2 no3 pg 13
+ *
+ * "Organic Illusion"
+ * by William Kolomyjec
+ *
+ * experimental recode by Leander Herzog 2012
+ *
+ * Copyright (c) 2012 Leander Herzog
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+var things = [];
+
+var Thing = Base.extend({
+ initialize: function() {
+ this.res=Math.round(unit*0.1*2), this.flex=180;
+ this.offset=r(-this.flex, this.flex);
+ this.nextoffset=r(-this.flex, this.flex);
+ this.angle=0, this.radius=unit*0.25;
+ this.start=new Point(),this.end=new Point();
+ this.shift= new Point(),this.newshift= new Point();
+ this.dir=new Point(),this.tmp= new Point();
+ this.paths=[];
+ this.m=Math.round(unit*0.1);
+ this.n=new Point(i*unit,j*unit);
+ this.createPaths();
+ },
+ createPaths: function() {
+ this.shift=new Point(Math.random()*unit*0.4-unit*0.2, Math.random()*unit*0.4-unit*0.2);
+ this.dir=new Point(this.m,0);
+ for (var k=0;k
+ * http://marijn.haverbeke.nl/parse-js/
+ *
+ * Ported by to JavaScript by Mihai Bazon
+ * Copyright (c) 2010, Mihai Bazon
+ * http://mihai.bazon.net/blog/
+ *
+ * Modifications and adaptions to browser (c) 2011, Juerg Lehni
+ * http://lehni.org/
+ *
+ * Distributed under the BSD license.
+ */
+
+var paper = new function() {
+
+var Base = new function() {
+ var hidden = /^(statics|generics|preserve|enumerable|prototype|toString|valueOf)$/,
+ proto = Object.prototype,
+ toString = proto.toString,
+ proto = Array.prototype,
+ isArray = Array.isArray = Array.isArray || function(obj) {
+ return toString.call(obj) === '[object Array]';
+ },
+ isObject = function(obj) {
+ return toString.call(obj) === '[object Object]';
+ },
+ 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);
+ },
+ create = Object.create || function(proto) {
+ return { __proto__: proto };
+ },
+ _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 }
+ : obj.hasOwnProperty(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 ((dontCheck || val !== undefined && src.hasOwnProperty(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 (generics && func && (!preserve || !generics[name])) {
+ generics[name] = function(bind) {
+ return bind && dest[name].apply(bind,
+ slice.call(arguments, 1));
+ };
+ }
+ }
+ if (src) {
+ beans = [];
+ for (var name in src)
+ if (src.hasOwnProperty(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 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 = Object.getPrototypeOf(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 ctor = function() {
+ if (this.initialize)
+ return this.initialize.apply(this, arguments);
+ };
+ ctor.prototype = create(this.prototype);
+ ctor.toString = function() {
+ return (this.prototype.initialize || function() {}).toString();
+ };
+ define(ctor.prototype, 'constructor',
+ { value: ctor, writable: true, configurable: true });
+ inject(ctor, this, true);
+ return arguments.length ? this.inject.apply(ctor, arguments) : ctor;
+ }
+ }, true).inject({
+ 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 = create(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,
+ create: function(ctor) {
+ return create(ctor.prototype);
+ },
+ define: define,
+ describe: describe,
+ iterator: iterator,
+ isObject: isObject,
+
+ 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.formatFloat(value)
+ : type === 'string' ? "'" + value + "'" : value));
+ }
+ }, []).join(', ') + ' }';
+ },
+
+ statics: {
+
+ equals: function(obj1, obj2) {
+ if (obj1 == obj2)
+ return true;
+ if (obj1 != null && obj1.equals)
+ return obj1.equals(obj2);
+ if (obj2 != null && obj2.equals)
+ return obj2.equals(obj1);
+ if (Array.isArray(obj1) && Array.isArray(obj2)) {
+ if (obj1.length !== obj2.length)
+ return false;
+ for (var i = 0, l = obj1.length; i < l; i++) {
+ if (!Base.equals(obj1, obj2))
+ return false;
+ }
+ return true;
+ }
+ if (typeof obj1 === 'object' && typeof obj2 === 'object') {
+ function checkKeys(o1, o2) {
+ for (var i in o1)
+ if (o1.hasOwnProperty(i) && typeof o2[i] === 'undefined')
+ return false;
+ return true;
+ }
+ if (!checkKeys(obj1, obj2) || !checkKeys(obj2, obj1))
+ return false;
+ for (var i in obj1) {
+ if (obj1.hasOwnProperty(i) && !Base.equals(obj1[i], obj2[i]))
+ return false;
+ }
+ return true;
+ }
+ return false;
+ },
+
+ read: function(list, start, length, clone) {
+ var proto = this.prototype,
+ readIndex = proto._readIndex,
+ index = start || readIndex && list._index || 0;
+ if (!length)
+ length = list.length - index;
+ var obj = list[index];
+ if (obj instanceof this
+ || proto._readNull && obj == null && length <= 1) {
+ if (readIndex)
+ list._index = index + 1;
+ return obj && clone ? obj.clone() : obj;
+ }
+ obj = Base.create(this);
+ if (readIndex)
+ obj._read = true;
+ obj = obj.initialize.apply(obj, index > 0 || length < list.length
+ ? Array.prototype.slice.call(list, index, index + length)
+ : list) || obj;
+ if (readIndex) {
+ list._index = index + obj._read;
+ list._read = obj._read;
+ delete obj._read;
+ }
+ return obj;
+ },
+
+ peekValue: function(list, start) {
+ return list[list._index = start || list._index || 0];
+ },
+
+ readValue: function(list, start) {
+ var value = this.peekValue(list, start);
+ list._index++;
+ list._read = 1;
+ return value;
+ },
+
+ readAll: function(list, start, clone) {
+ 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, 0, clone)
+ : this.read(list, i, 1, clone));
+ }
+ 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(/-(.)/g, function(all, chr) {
+ return chr.toUpperCase();
+ });
+ },
+
+ hyphenate: function(str) {
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
+ },
+
+ formatFloat: function(num) {
+ return (Math.round(num * 100000) / 100000).toString();
+ },
+
+ toFloat: function(str) {
+ return parseFloat(str, 10);
+ }
+ }
+});
+
+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;
+ var args = [].slice.call(arguments, 1);
+ Base.each(handlers, function(func) {
+ if (func.apply(this, args) === 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 PaperScope = this.PaperScope = Base.extend({
+
+ initialize: function(script) {
+ paper = this;
+ this.project = null;
+ this.projects = [];
+ this.tools = [];
+ this.palettes = [];
+ 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 && 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();
+ for (var i = this.palettes.length - 1; i >= 0; i--)
+ this.palettes[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(Callback, {
+
+ 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;
+ var prev = this._scope[this._reference];
+ if (prev && prev != this)
+ prev.fire('deactivate');
+ this._scope[this._reference] = this;
+ this.fire('activate', prev);
+ return true;
+ },
+
+ isActive: function() {
+ return this._scope[this._reference] === this;
+ },
+
+ remove: function() {
+ if (this._index == null)
+ return false;
+ Base.splice(this._scope[this._list], null, this._index, 1);
+ if (this._scope[this._reference] == this)
+ this._scope[this._reference] = null;
+ this._scope = null;
+ return true;
+ }
+});
+
+var Point = this.Point = Base.extend({
+ _readIndex: true,
+
+ initialize: function(arg0, arg1) {
+ var type = typeof arg0;
+ if (type === 'number') {
+ var hasY = typeof arg1 === 'number';
+ this.x = arg0;
+ this.y = hasY ? arg1 : arg0;
+ if (this._read)
+ this._read = hasY ? 2 : 1;
+ } else if (type === 'undefined' || arg0 === null) {
+ this.x = this.y = 0;
+ if (this._read)
+ this._read = arg0 === null ? 1 : 0;
+ } else {
+ if (Array.isArray(arg0)) {
+ this.x = arg0[0];
+ this.y = arg0.length > 1 ? arg0[1] : arg0[0];
+ } else if (arg0.x != null) {
+ this.x = arg0.x;
+ this.y = arg0.y;
+ } else if (arg0.width != null) {
+ this.x = arg0.width;
+ this.y = arg0.height;
+ } else if (arg0.angle != null) {
+ this.x = arg0.length;
+ this.y = 0;
+ this.setAngle(arg0.angle);
+ } else {
+ this.x = this.y = 0;
+ if (this._read)
+ this._read = 0;
+ }
+ if (this._read)
+ this._read = 1;
+ }
+ },
+
+ 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.formatFloat;
+ 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 (Numerical.isZero(div)) {
+ 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) {
+ if (angle === 0)
+ return this.clone();
+ 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 Numerical.isZero(this.x) && Numerical.isZero(this.y);
+ },
+
+ 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 = Base.create(Point);
+ point.x = x;
+ point.y = y;
+ return point;
+ },
+
+ min: function(point1, point2) {
+ var _point1 = Point.read(arguments);
+ _point2 = Point.read(arguments);
+ return Point.create(
+ Math.min(_point1.x, _point2.x),
+ Math.min(_point1.y, _point2.y)
+ );
+ },
+
+ max: function(point1, point2) {
+ var _point1 = Point.read(arguments);
+ _point2 = Point.read(arguments);
+ 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 = Base.create(LinkedPoint);
+ point._x = x;
+ point._y = y;
+ point._owner = owner;
+ point._setter = setter;
+ return point;
+ }
+ }
+});
+
+var Size = this.Size = Base.extend({
+ _readIndex: true,
+
+ initialize: function(arg0, arg1) {
+ var type = typeof arg0;
+ if (type === 'number') {
+ var hasHeight = typeof arg1 === 'number';
+ this.width = arg0;
+ this.height = hasHeight ? arg1 : arg0;
+ if (this._read)
+ this._read = hasHeight ? 2 : 1;
+ } else if (type === 'undefined' || arg0 === null) {
+ this.width = this.height = 0;
+ if (this._read)
+ this._read = arg0 === null ? 1 : 0;
+ } else {
+ if (Array.isArray(arg0)) {
+ this.width = arg0[0];
+ this.height = arg0.length > 1 ? arg0[1] : arg0[0];
+ } else if (arg0.width != null) {
+ this.width = arg0.width;
+ this.height = arg0.height;
+ } else if (arg0.x != null) {
+ this.width = arg0.x;
+ this.height = arg0.y;
+ } else {
+ this.width = this.height = 0;
+ if (this._read)
+ this._read = 0;
+ }
+ if (this._read)
+ this._read = 1;
+ }
+ },
+
+ toString: function() {
+ var format = Base.formatFloat;
+ 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 Numerical.isZero(this.width) && Numerical.isZero(this.height);
+ },
+
+ isNaN: function() {
+ return isNaN(this.width) || isNaN(this.height);
+ },
+
+ statics: {
+ create: function(width, height) {
+ return Base.create(Size).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 = Base.create(LinkedSize);
+ size._width = width;
+ size._height = height;
+ size._owner = owner;
+ size._setter = setter;
+ return size;
+ }
+ }
+});
+
+var Rectangle = this.Rectangle = Base.extend({
+ _readIndex: true,
+
+ initialize: function(arg0, arg1, arg2, arg3) {
+ var type = typeof arg0;
+ if (type === 'number') {
+ this.x = arg0;
+ this.y = arg1;
+ this.width = arg2;
+ this.height = arg3;
+ if (this._read)
+ this._read = 4;
+ } else if (type === 'undefined' || arg0 === null) {
+ this.x = this.y = this.width = this.height = 0;
+ if (this._read)
+ this._read = arg0 === null ? 1 : 0;
+ } else if (arguments.length > 1 && arg0.width == null) {
+ var point = Point.read(arguments),
+ next = Base.peekValue(arguments);
+ this.x = point.x;
+ this.y = point.y;
+ if (next && next.x !== undefined) {
+ var point2 = Point.read(arguments);
+ this.width = point2.x - point.x;
+ this.height = point2.y - point.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 size = Size.read(arguments);
+ this.width = size.width;
+ this.height = size.height;
+ }
+ if (this._read)
+ this._read = arguments._index;
+ } else {
+ if (Array.isArray(arg0)) {
+ this.x = arg0[0];
+ this.y = arg0[1];
+ this.width = arg0[2];
+ this.height = arg0[3];
+ } else {
+ this.x = arg0.x || 0;
+ this.y = arg0.y || 0;
+ this.width = arg0.width || 0;
+ this.height = arg0.height || 0;
+ }
+ if (this._read)
+ this._read = 1;
+ }
+ },
+
+ 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.formatFloat;
+ 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 Base.create(Rectangle).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 = Base.create(LinkedRectangle).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(scale, center) {
+ var _scale = Point.read(arguments),
+ _center = Point.read(arguments);
+ if (_center)
+ this.translate(_center);
+ this._a *= _scale.x;
+ this._c *= _scale.x;
+ this._b *= _scale.y;
+ this._d *= _scale.y;
+ 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(point, center) {
+ var _point = Point.read(arguments),
+ _center = Point.read(arguments);
+ if (_center)
+ this.translate(_center);
+ var a = this._a,
+ c = this._c;
+ this._a += _point.y * this._b;
+ this._c += _point.y * this._d;
+ this._b += _point.x * a;
+ this._d += _point.x * c;
+ if (_center)
+ this.translate(_center.negate());
+ return this;
+ },
+
+ toString: function() {
+ var format = Base.formatFloat;
+ 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 = Base.create(Point);
+ 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 = Base.create(Rectangle);
+ 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) && !Numerical.isZero(det)
+ && 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 = Base.create(Point);
+ 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.EPSILON
+ ? 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 Base.create(Matrix).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);
+ point2 = Point.read(arguments);
+ 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 (Numerical.isZero(cross))
+ 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.layers = [];
+ this.symbols = [];
+ this.activeLayer = new Layer();
+ if (view)
+ this.view = view instanceof View ? view : View.create(view);
+ this._currentStyle = new PathStyle();
+ this._selectedItems = {};
+ this._selectedItemCount = 0;
+ },
+
+ _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) {
+ point = Point.read(arguments);
+ options = HitResult.getOptions(Base.readValue(arguments));
+ 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(5);
+ },
+
+ place: function(position) {
+ return new PlacedSymbol(this, position);
+ },
+
+ clone: function() {
+ return new Symbol(this._definition.clone());
+ }
+});
+
+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;
+ }
+ }
+ };
+
+ return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
+ 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
+ function(name) {
+ this[name] = mouseEvent;
+ }, {
+ onFrame: {
+ install: function() {
+ this._project.view._animateItem(this, true);
+ },
+ uninstall: function() {
+ this._project.view._animateItem(this, false);
+ }
+ },
+
+ onLoad: {}
+ });
+ },
+
+ 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))
+ : new Matrix();
+ },
+
+ _changed: function(flags) {
+ if (flags & 4) {
+ delete this._bounds;
+ delete this._position;
+ }
+ if (this._parent
+ && (flags & (4 | 8))) {
+ this._parent._clearBoundsCache();
+ }
+ if (flags & 2) {
+ this._clearBoundsCache();
+ }
+ if (flags & 1) {
+ 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;
+ },
+
+ getType: function() {
+ return this._type;
+ },
+
+ 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(32);
+ },
+
+ 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'
+ ? 32 : 33);
+ }
+ };
+}, {}), {
+
+ _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(33);
+ }
+ },
+
+ _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(33);
+ if (this._parent)
+ this._parent._changed(256);
+ }
+ },
+
+ _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(5);
+ }
+}, 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 && !child.isEmpty()) {
+ 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);
+ },
+
+ isEmpty: function() {
+ return true;
+ },
+
+ 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(), true);
+ }
+ 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) {
+ point = Point.read(arguments);
+ options = HitResult.getOptions(Base.readValue(arguments));
+ if (!this._children && !this.getRoughBounds()
+ .expand(options.tolerance)._containsPoint(point))
+ return null;
+ point = this._matrix._inverseTransform(point);
+ 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, _cloning) {
+ return this.insertChild(undefined, item, _cloning);
+ },
+
+ 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(3);
+ return item;
+ }
+ return null;
+ },
+
+ 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(3);
+ 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(3);
+ 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(3);
+ }
+ },
+
+ 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(5);
+ 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._type !== 'path'
+ || 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({
+ _type: 'group',
+
+ initialize: function(items) {
+ this.base();
+ this._children = [];
+ this._namedChildren = {};
+ this.addChildren(Array.isArray(items) ? items : arguments);
+ },
+
+ _changed: function(flags) {
+ Item.prototype._changed.call(this, flags);
+ if (flags & (2 | 256)) {
+ 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;
+ },
+
+ isEmpty: function() {
+ return this._children.length == 0;
+ },
+
+ 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({
+ _type: 'layer',
+ 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 : 0), 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' },
+
+ _hitTest: function(point, options, matrix) {
+ var hitResult = this._symbol._definition._hitTest(point, options, matrix);
+ if (hitResult)
+ hitResult.item = this;
+ return hitResult;
+ }
+});
+
+var Raster = this.Raster = PlacedItem.extend({
+ _type: 'raster',
+ _boundsType: 'bounds',
+
+ initialize: function(object, pointOrMatrix) {
+ this.base(pointOrMatrix);
+ if (object.getContext) {
+ this.setCanvas(object);
+ } else {
+ if (typeof object === 'string') {
+ var str = object,
+ that = this;
+ object = document.getElementById(str) || new Image();
+ DomEvent.add(object, {
+ load: function() {
+ that.setImage(object);
+ that.fire('load');
+ if (that._project.view)
+ that._project.view.draw(true);
+ }
+ });
+ if (!object.src)
+ object.src = str;
+ }
+ 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);
+ if (!this._size.equals(size)) {
+ var 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;
+ },
+
+ isEmpty: function() {
+ return this._size.width == 0 && this._size.height == 0;
+ },
+
+ 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(129);
+ 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(5 | 129);
+ },
+
+ 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(5);
+ },
+
+ 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 _point = Point.read(arguments),
+ _color = Color.read(arguments);
+ 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({
+ _type: 'placedsymbol',
+ 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;
+ },
+
+ isEmpty: function() {
+ return this._symbol._definition.isEmpty();
+ },
+
+ 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(options) {
+ return options && options._merged ? options : Base.merge({
+ 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(5);
+ },
+
+ 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);
+ },
+
+ isLinear: function() {
+ return this._handleIn.isZero() && this._handleOut.isZero();
+ },
+
+ _isSelected: function(point) {
+ var state = this._selectionState;
+ return point == this._point ? !!(state & 4)
+ : point == this._handleIn ? !!(state & 1)
+ : point == this._handleOut ? !!(state & 2)
+ : false;
+ },
+
+ _setSelected: function(point, selected) {
+ var path = this._path,
+ selected = !!selected,
+ state = this._selectionState || 0,
+ selection = [
+ !!(state & 4),
+ !!(state & 1),
+ !!(state & 2)
+ ];
+ 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] ? 4 : 0)
+ | (selection[1] ? 1 : 0)
+ | (selection[2] ? 2 : 0);
+ if (path && state != this._selectionState) {
+ path._updateSelection(this, state, this._selectionState);
+ path._changed(33);
+ }
+ },
+
+ 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 Numerical.isZero(this._x) && Numerical.isZero(this._y);
+ },
+
+ setSelected: function(selected) {
+ this._owner._setSelected(this, selected);
+ },
+
+ isSelected: function() {
+ return this._owner._isSelected(this);
+ },
+
+ statics: {
+ create: function(segment, key, pt) {
+ var point = Base.create(SegmentPoint),
+ 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);
+ 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 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 = Base.create(Curve);
+ 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 (var 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 [ Numerical.isZero(line.vector.getLength(true))
+ ? 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.formatFloat(parameter));
+ if (this._distance != null)
+ parts.push('distance: ' + Base.formatFloat(this._distance));
+ return '{ ' + parts.join(', ') + ' }';
+ }
+});
+
+var PathItem = this.PathItem = Item.extend({
+
+});
+
+var Path = this.Path = PathItem.extend({
+ _type: 'path',
+ 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 & 4) {
+ 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(5);
+ }
+ }
+ } else if (flags & 8) {
+ 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];
+ },
+
+ isClosed: 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(5);
+ }
+ },
+
+ transform: function(matrix) {
+ return this.base(matrix, true);
+ },
+
+ getMatrix: function() {
+ return null;
+ },
+
+ setMatrix: function(matrix) {
+ },
+
+ isEmpty: function() {
+ return this._segments.length === 0;
+ },
+
+ isPolygon: function() {
+ for (var i = 0, l = this._segments.length; i < l; i++) {
+ if (!this._segments[i].isLinear())
+ return false;
+ }
+ return true;
+ },
+
+ _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 = 4;
+ 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(5);
+ 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 == curves.length ? from - 1 : 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(5);
+ return removed;
+ },
+
+ isFullySelected: function() {
+ return this._selected && this._selectedSegmentState
+ == this._segments.length * 4;
+ },
+
+ setFullySelected: function(selected) {
+ var length = this._segments.length;
+ this._selectedSegmentState = selected
+ ? length * 4 : 0;
+ for (var i = 0; i < length; i++)
+ this._segments[i]._selectionState = selected
+ ? 4 : 0;
+ this.setSelected(selected);
+ },
+
+ _updateSelection: function(segment, oldState, newState) {
+ segment._selectionState = newState;
+ var total = this._selectedSegmentState += newState - oldState;
+ if (total > 0)
+ this.setSelected(true);
+ },
+
+ flatten: function(maxDistance) {
+ var flattener = new PathFlattener(this),
+ pos = 0,
+ step = flattener.length / Math.ceil(flattener.length / maxDistance),
+ end = flattener.length + (this._closed ? -step : step) / 2;
+ var segments = [];
+ while (pos <= end) {
+ segments.push(new Segment(flattener.evaluate(pos, 0)));
+ pos += step;
+ }
+ this.setSegments(segments);
+ },
+
+ simplify: function(tolerance) {
+ if (this._segments.length > 2) {
+ var fitter = new PathFitter(this, tolerance || 2.5);
+ this.setSegments(fitter.fit());
+ }
+ },
+
+ isClockwise: function() {
+ if (this._clockwise !== undefined)
+ return this._clockwise;
+ var sum = 0,
+ xPre, yPre;
+ function edge(x, y) {
+ if (xPre !== undefined)
+ sum += (xPre - x) * (y + yPre);
+ xPre = x;
+ yPre = y;
+ }
+ for (var i = 0, l = this._segments.length; i < l; i++) {
+ var seg1 = this._segments[i],
+ seg2 = this._segments[i + 1 < l ? i + 1 : 0],
+ point1 = seg1._point,
+ handle1 = seg1._handleOut,
+ handle2 = seg2._handleIn,
+ point2 = seg2._point;
+ edge(point1._x, point1._y);
+ edge(point1._x + handle1._x, point1._y + handle1._y);
+ edge(point2._x + handle2._x, point2._y + handle2._y);
+ edge(point2._x, point2._y);
+ }
+ return sum > 0;
+ },
+
+ setClockwise: function(clockwise) {
+ if (this.isClockwise() != (clockwise = !!clockwise)) {
+ this.reverse();
+ }
+ this._clockwise = clockwise;
+ },
+
+ reverse: function() {
+ this._segments.reverse();
+ for (var i = 0, l = this._segments.length; i < l; i++) {
+ var segment = this._segments[i];
+ var handleIn = segment._handleIn;
+ segment._handleIn = segment._handleOut;
+ segment._handleOut = handleIn;
+ 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(5);
+ return true;
+ }
+ return false;
+ },
+
+ reduce: function() {
+ return this;
+ },
+
+ 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._style._fillColor
+ || !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 & 4,
+ 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 & 1))
+ drawHandle(2);
+ if (selected || (state & 2))
+ 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)) {
+ 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();
+ }
+ }
+ },
+
+ 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,
+ f2 = 1 - f1,
+ ie = i + overlap,
+ je = j + overlap;
+ x[j] = x[i] * f1 + x[j] * f2;
+ y[j] = y[i] * f1 + y[j] * f2;
+ 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 === 1)
+ this.removeSegment(0);
+ 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) {
+ var _handle1 = Point.read(arguments),
+ _handle2 = Point.read(arguments),
+ _to = Point.read(arguments);
+ var current = getCurrentSegment(this);
+ current.setHandleOut(_handle1.subtract(current._point));
+ this._add([ new Segment(_to, _handle2.subtract(to)) ]);
+ },
+
+ quadraticCurveTo: function(handle, to) {
+ var _handle = Point.read(arguments),
+ to = Point.read(arguments);
+ 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) {
+ var _through = Point.read(arguments),
+ _to = Point.read(arguments),
+ t = Base.pick(Base.readValue(arguments), 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,
+ point = Point.read(arguments),
+ next = Base.pick(Base.peekValue(arguments), true);
+ if (typeof next === 'boolean') {
+ to = point;
+ clockwise = next;
+ var middle = from.add(to).divide(2),
+ through = middle.add(middle.subtract(from).rotate(
+ clockwise ? -90 : 90));
+ } else {
+ through = point;
+ to = Point.read(arguments);
+ }
+ 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._strokeColor ? style._strokeWidth : 0;
+ 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() {
+
+ function createRectangle(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;
+ }
+
+ var kappa = 2 * (Math.sqrt(2) - 1) / 3;
+
+ var ellipseSegments = [
+ 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])
+ ];
+
+ function createEllipse(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 = ellipseSegments[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;
+ }
+
+ return {
+ Line: function() {
+ return new Path(
+ Point.read(arguments),
+ Point.read(arguments)
+ );
+ },
+
+ Rectangle: createRectangle,
+
+ RoundRectangle: function(rect, size) {
+ var _rect = Rectangle.read(arguments),
+ _size = Size.read(arguments);
+ if (_size.isZero())
+ return createRectangle(rect);
+ _size = Size.min(_size, _rect.getSize(true).divide(2));
+ var bl = _rect.getBottomLeft(true),
+ tl = _rect.getTopLeft(true),
+ tr = _rect.getTopRight(true),
+ br = _rect.getBottomRight(true),
+ uSize = _size.multiply(kappa * 2),
+ path = new Path();
+ 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;
+ },
+
+ Ellipse: createEllipse,
+
+ Oval: createEllipse,
+
+ Circle: function(center, radius) {
+ var _center = Point.read(arguments),
+ _radius = Base.readValue(arguments);
+ return createEllipse(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) {
+ var _center = Point.read(arguments),
+ _numSides = Base.readValue(arguments),
+ _radius = Base.readValue(arguments),
+ 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) {
+ var _center = Point.read(arguments),
+ _numPoints = Base.readValue(arguments) * 2,
+ _radius1 = Base.readValue(arguments),
+ _radius2 = Base.readValue(arguments),
+ 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({
+ _type: 'compoundpath',
+ initialize: function(paths) {
+ this.base();
+ this._children = [];
+ this._namedChildren = {};
+ this.addChildren(Array.isArray(paths) ? paths : arguments);
+ },
+
+ insertChild: function(index, item, _cloning) {
+ if (item._type !== 'path')
+ return null;
+ var res = this.base(index, item);
+ if (!_cloning && res && item._clockwise === undefined)
+ item.setClockwise(item._index == 0);
+ return res;
+ },
+
+ reduce: 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();
+ },
+
+ isEmpty: function() {
+ return this._children.length == 0;
+ },
+
+ contains: function(point) {
+ point = Point.read(arguments);
+ var count = 0;
+ for (var i = 0, l = this._children.length; i < l; i++) {
+ if (this._children[i].contains(point))
+ count++;
+ }
+ return (count & 1) == 1;
+ },
+
+ _hitTest: function(point, options) {
+ return this.base(point, Base.merge(options, { fill: false }))
+ || options.fill && this._style._fillColor && this.contains(point)
+ ? new HitResult('fill', this)
+ : null;
+ },
+
+ draw: function(ctx, param) {
+ var children = this._children,
+ style = this._style;
+ if (children.length == 0)
+ return;
+ ctx.beginPath();
+ param.compound = true;
+ for (var i = 0, l = children.length; i < l; i++)
+ Item.draw(children[i], ctx, param);
+ param.compound = false;
+ if (this._clipMask) {
+ ctx.clip();
+ } else {
+ this._setStyles(ctx);
+ if (style._fillColor)
+ ctx.fill();
+ if (style._strokeColor)
+ ctx.stroke();
+ }
+ }
+}, 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(65);
+ },
+
+ isEmpty: function() {
+ return !!this._content;
+ },
+
+ getCharacterStyle: function() {
+ return this.getStyle();
+ },
+
+ setCharacterStyle: function(style) {
+ this.setStyle(style);
+ }
+
+});
+
+var PointText = this.PointText = TextItem.extend({
+ _type: 'pointtext',
+ 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);
+ }
+ },
+
+ drawSelected: function(ctx, matrix) {
+ Item.drawSelectedBounds(this._getBounds(), ctx, matrix);
+ }
+}, 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 SvgStyles = Base.each({
+ fillColor: ['fill', 'color'],
+ strokeColor: ['stroke', 'color'],
+ strokeWidth: ['stroke-width', 'number'],
+ strokeCap: ['stroke-linecap', 'string'],
+ strokeJoin: ['stroke-linejoin', 'string'],
+ miterLimit: ['stroke-miterlimit', 'number'],
+ dashArray: ['stroke-dasharray', 'array'],
+ dashOffset: ['stroke-dashoffset', 'number']
+}, function(entry, key) {
+ var part = Base.capitalize(key);
+ this.attributes[entry[0]] = this.properties[key] = {
+ type: entry[1],
+ property: key,
+ attribute: entry[0],
+ get: 'get' + part,
+ set: 'set' + part
+ };
+}, {
+ properties: {},
+ attributes: {}
+});
+
+new function() {
+
+ var formatFloat = Base.formatFloat;
+
+ function formatPoint(point) {
+ return formatFloat(point.x) + ',' + formatFloat(point.y);
+ }
+
+ function setAttributes(svg, attrs) {
+ for (var key in attrs) {
+ var val = attrs[key];
+ if (typeof val === 'number')
+ val = formatFloat(val);
+ svg.setAttribute(key, val);
+ }
+ return svg;
+ }
+
+ function createElement(tag, attrs) {
+ return setAttributes(
+ document.createElementNS('http://www.w3.org/2000/svg', tag), attrs);
+ }
+
+ function getDistance(segments, index1, index2) {
+ return segments[index1]._point.getDistance(segments[index2]._point);
+ }
+
+ function getTransform(item) {
+ var matrix = item._matrix.createShiftless(),
+ trans = matrix._inverseTransform(item._matrix.getTranslation()),
+ attrs = {
+ x: trans.x,
+ y: trans.y
+ };
+ if (matrix.isIdentity())
+ return attrs;
+ var transform = [],
+ angle = matrix.getRotation(),
+ scale = matrix.getScaling();
+ if (angle != null) {
+ transform.push(angle
+ ? 'rotate(' + formatFloat(angle) + ')'
+ : 'scale(' + formatPoint(scale) +')');
+ } else {
+ transform.push('matrix(' + matrix.getValues().join(',') + ')');
+ }
+ attrs.transform = transform.join(' ');
+ return attrs;
+ }
+
+ function getPath(path, segments) {
+ var parts = [],
+ style = path._style;
+
+ function addCurve(seg1, seg2, skipLine) {
+ var point1 = seg1._point,
+ point2 = seg2._point,
+ handle1 = seg1._handleOut,
+ handle2 = seg2._handleIn;
+ if (handle1.isZero() && handle2.isZero()) {
+ if (!skipLine) {
+ parts.push('L' + formatPoint(point2));
+ }
+ } else {
+ var end = point2.subtract(point1);
+ parts.push('c' + formatPoint(handle1),
+ formatPoint(end.add(handle2)),
+ formatPoint(end));
+ }
+ }
+
+ parts.push('M' + formatPoint(segments[0]._point));
+ for (i = 0, l = segments.length - 1; i < l; i++)
+ addCurve(segments[i], segments[i + 1], false);
+ if (path._closed && style._strokeColor || style._fillColor)
+ addCurve(segments[segments.length - 1], segments[0], true);
+ if (path._closed)
+ parts.push('z');
+ return parts.join(' ');
+ }
+
+ function determineAngle(path, segments, type, center) {
+ var topCenter = type === 'rect'
+ ? segments[1]._point.add(segments[2]._point).divide(2)
+ : type === 'roundrect'
+ ? segments[3]._point.add(segments[4]._point).divide(2)
+ : type === 'circle' || type === 'ellipse'
+ ? segments[1]._point
+ : null;
+ var angle = topCenter && topCenter.subtract(center).getAngle() + 90;
+ return Numerical.isZero(angle || 0) ? 0 : angle;
+ }
+
+ function determineType(path, segments) {
+ function isColinear(i, j) {
+ var seg1 = segments[i],
+ seg2 = seg1.getNext(),
+ seg3 = segments[j],
+ seg4 = seg3.getNext();
+ return seg1._handleOut.isZero() && seg2._handleIn.isZero()
+ && seg3._handleOut.isZero() && seg4._handleIn.isZero()
+ && seg2._point.subtract(seg1._point).isColinear(
+ seg4._point.subtract(seg3._point));
+ }
+
+ var kappa = 4 * (Math.sqrt(2) - 1) / 3;
+
+ function isArc(i) {
+ var segment = segments[i],
+ next = segment.getNext(),
+ handle1 = segment._handleOut,
+ handle2 = next._handleIn;
+ if (handle1.isOrthogonal(handle2)) {
+ var from = segment._point,
+ to = next._point,
+ corner = new Line(from, handle1).intersect(
+ new Line(to, handle2));
+ return corner && Numerical.isZero(handle1.getLength() /
+ corner.subtract(from).getLength() - kappa)
+ && Numerical.isZero(handle2.getLength() /
+ corner.subtract(to).getLength() - kappa);
+ }
+ }
+
+ if (path.isPolygon()) {
+ return segments.length === 4 && path._closed
+ && isColinear(0, 2) && isColinear(1, 3)
+ ? 'rect'
+ : segments.length >= 3
+ ? path._closed ? 'polygon' : 'polyline'
+ : 'line';
+ } else if (path._closed) {
+ if (segments.length === 8
+ && isArc(0) && isArc(2) && isArc(4) && isArc(6)
+ && isColinear(1, 5) && isColinear(3, 7)) {
+ return 'roundrect';
+ } else if (segments.length === 4
+ && isArc(0) && isArc(1) && isArc(2) && isArc(3)) {
+ return Numerical.isZero(getDistance(segments, 0, 2)
+ - getDistance(segments, 1, 3))
+ ? 'circle'
+ : 'ellipse';
+ }
+ }
+ return 'path';
+ }
+
+ function exportGroup(group) {
+ var attrs = getTransform(group),
+ children = group._children;
+ attrs.fill = 'none';
+ var svg = createElement('g', attrs);
+ for (var i = 0, l = children.length; i < l; i++)
+ svg.appendChild(children[i].exportSvg());
+ return svg;
+ }
+
+ function exportText(item) {
+ var attrs = getTransform(item),
+ style = item._style;
+ if (style._font != null)
+ attrs['font-family'] = style._font;
+ if (style._fontSize != null)
+ attrs['font-size'] = style._fontSize;
+ var svg = createElement('text', attrs);
+ svg.textContent = item._content;
+ return svg;
+ }
+
+ function exportPath(path) {
+ var segments = path._segments,
+ center = path.getPosition(true),
+ type = determineType(path, segments),
+ angle = determineAngle(path, segments, type, center),
+ attrs;
+ switch (type) {
+ case 'path':
+ attrs = {
+ d: getPath(path, segments)
+ };
+ break;
+ case 'polyline':
+ case 'polygon':
+ var parts = [];
+ for(i = 0, l = segments.length; i < l; i++)
+ parts.push(formatPoint(segments[i]._point));
+ attrs = {
+ points: parts.join(' ')
+ };
+ break;
+ case 'rect':
+ var width = getDistance(segments, 0, 3),
+ height = getDistance(segments, 0, 1),
+ point = segments[1]._point.rotate(-angle, center);
+ attrs = {
+ x: point.x,
+ y: point.y,
+ width: width,
+ height: height
+ };
+ break;
+ case 'roundrect':
+ type = 'rect';
+ var width = getDistance(segments, 1, 6),
+ height = getDistance(segments, 0, 3),
+ rx = (width - getDistance(segments, 0, 7)) / 2,
+ ry = (height - getDistance(segments, 1, 2)) / 2,
+ left = segments[3]._point,
+ right = segments[4]._point,
+ point = left.subtract(right.subtract(left).normalize(rx))
+ .rotate(-angle, center);
+ attrs = {
+ x: point.x,
+ y: point.y,
+ width: width,
+ height: height,
+ rx: rx,
+ ry: ry
+ };
+ break;
+ case'line':
+ var first = segments[0]._point,
+ last = segments[segments.length - 1]._point;
+ attrs = {
+ x1: first._x,
+ y1: first._y,
+ x2: last._x,
+ y2: last._y
+ };
+ break;
+ case 'circle':
+ var radius = getDistance(segments, 0, 2) / 2;
+ attrs = {
+ cx: center.x,
+ cy: center.y,
+ r: radius
+ };
+ break;
+ case 'ellipse':
+ var rx = getDistance(segments, 2, 0) / 2,
+ ry = getDistance(segments, 3, 1) / 2;
+ attrs = {
+ cx: center.x,
+ cy: center.y,
+ rx: rx,
+ ry: ry
+ };
+ break;
+ }
+ if (angle) {
+ attrs.transform = 'rotate(' + formatFloat(angle) + ','
+ + formatPoint(center) + ')';
+ }
+ var svg = createElement(type, attrs);
+ return svg;
+ }
+
+ var exporters = {
+ group: exportGroup,
+ layer: exportGroup,
+ path: exportPath,
+ pointtext: exportText
+ };
+
+ function applyStyle(item, svg) {
+ var attrs = {},
+ style = item._style,
+ parent = item.getParent(),
+ parentStyle = parent && parent._style;
+
+ if (item._name != null)
+ attrs.id = item._name;
+
+ Base.each(SvgStyles.properties, function(entry) {
+ var value = style[entry.get]();
+ if (!parentStyle || !Base.equals(parentStyle[entry.get](), value)) {
+ if (entry.type === 'color' && value != null && value.getAlpha() < 1)
+ attrs[entry.attribute + '-opacity'] = value.getAlpha();
+ attrs[entry.attribute] = value == null
+ ? 'none'
+ : entry.type === 'color'
+ ? value.toCss(false)
+ : entry.type === 'array'
+ ? value.join(',')
+ : entry.type === 'number'
+ ? formatFloat(value)
+ : value;
+ }
+ });
+
+ if (item._opacity != null && item._opacity < 1)
+ attrs.opacity = item._opacity;
+
+ if (item._visibility != null && !item._visibility)
+ attrs.visibility = 'hidden';
+
+ return setAttributes(svg, attrs);
+ }
+
+ Item.inject({
+ exportSvg: function() {
+ var exporter = exporters[this._type];
+ var svg = exporter && exporter(this, this._type);
+ return svg && applyStyle(this, svg);
+ }
+ });
+
+ Project.inject({
+ exportSvg: function() {
+ var svg = createElement('svg'),
+ layers = this.layers;
+ for (var i = 0, l = layers.length; i < l; i++)
+ svg.appendChild(layers[i].exportSvg());
+ return svg;
+ }
+ });
+};
+
+new function() {
+
+ function getValue(svg, key, allowNull, index) {
+ var base = (!allowNull || svg.getAttribute(key) != null)
+ && svg[key] && svg[key].baseVal;
+ return base
+ ? index !== undefined
+ ? index < base.numberOfItems
+ ? Base.pick((base = base.getItem(index)).value, base)
+ : null
+ : Base.pick(base.value, base)
+ : null;
+ }
+
+ function getPoint(svg, x, y, allowNull, index) {
+ x = getValue(svg, x, allowNull, index);
+ y = getValue(svg, y, allowNull, index);
+ return allowNull && x == null && y == null ? null
+ : Point.create(x || 0, y || 0);
+ }
+
+ function getSize(svg, w, h, allowNull, index) {
+ w = getValue(svg, w, allowNull, index);
+ h = getValue(svg, h, allowNull, index);
+ return allowNull && w == null && h == null ? null
+ : Size.create(w || 0, h || 0);
+ }
+
+ function convertValue(value, type) {
+ return value === 'none'
+ ? null
+ : type === 'number'
+ ? Base.toFloat(value)
+ : type === 'array'
+ ? value.split(/[\s,]+/g).map(parseFloat)
+ : type === 'color' && getDefinition(value)
+ || value;
+ }
+
+ function createClipGroup(item, clip) {
+ clip.setClipMask(true);
+ return new Group(clip, item);
+ }
+
+ function importGroup(svg, type) {
+ var nodes = svg.childNodes,
+ compound = type === 'clippath',
+ group = compound ? new CompoundPath() : new Group();
+
+ for (var i = 0, l = nodes.length; i < l; i++) {
+ var child = nodes[i],
+ item;
+ if (child.nodeType == 1 && (item = importSvg(child))) {
+ if (compound && item instanceof CompoundPath) {
+ group.addChildren(item.removeChildren());
+ item.remove();
+ } else if (!(item instanceof Symbol)) {
+ group.addChild(item);
+ }
+ }
+ }
+
+ if (type == 'defs') {
+ group.remove();
+ group = null;
+ }
+ return group;
+ }
+
+ function importPoly(svg, type) {
+ var path = new Path(),
+ points = svg.points;
+ path.moveTo(points.getItem(0));
+ for (var i = 1, l = points.numberOfItems; i < l; i++)
+ path.lineTo(points.getItem(i));
+ if (type === 'polygon')
+ path.closePath();
+ return path;
+ }
+
+ function importPath(svg) {
+ var path = new Path(),
+ list = svg.pathSegList,
+ compoundPath, lastPoint;
+ for (var i = 0, l = list.numberOfItems; i < l; i++) {
+ var segment = list.getItem(i),
+ segType = segment.pathSegType,
+ isRelative = segType % 2 == 1;
+ if (segType === 0)
+ continue;
+ if (!path.isEmpty())
+ lastPoint = path.getLastSegment().getPoint();
+ var relative = isRelative && !path.isEmpty()
+ ? lastPoint
+ : Point.create(0, 0);
+ var coord = (segType == 12
+ || segType == 13) && 'y'
+ || (segType == 14
+ || segType == 15) && 'x';
+ if (coord)
+ segment[coord] = isRelative ? 0 : lastPoint[coord];
+ var point = Point.create(segment.x, segment.y).add(relative);
+ switch (segType) {
+ case 1:
+ path.closePath();
+ break;
+ case 2:
+ case 3:
+ if (!path.isEmpty() && !compoundPath) {
+ compoundPath = new CompoundPath([path]);
+ }
+ if (compoundPath) {
+ path = new Path();
+ compoundPath.addChild(path);
+ }
+ path.moveTo(point);
+ break;
+ case 4:
+ case 5:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ path.lineTo(point);
+ break;
+ case 6:
+ case 7:
+ path.cubicCurveTo(
+ relative.add(segment.x1, segment.y1),
+ relative.add(segment.x2, segment.y2),
+ point
+ );
+ break;
+ case 8:
+ case 9:
+ path.quadraticCurveTo(
+ relative.add(segment.x1, segment.y1),
+ point
+ );
+ break;
+ case 16:
+ case 17:
+ var prev = list.getItem(i - 1),
+ control = lastPoint.add(lastPoint.subtract(
+ Point.create(prev.x2, prev.y2)
+ .subtract(prev.x, prev.y)
+ .add(lastPoint)));
+ path.cubicCurveTo(
+ control,
+ relative.add(segment.x2, segment.y2),
+ point);
+ break;
+ case 18:
+ case 19:
+ var control,
+ j = i;
+ for (; j >= 0; j--) {
+ var prev = list.getItem(j);
+ if (prev.pathSegType === 8 ||
+ prev.pathSegType === 9) {
+ control = Point.create(prev.x1, prev.y1)
+ .subtract(prev.x, prev.y)
+ .add(path._segments[j].getPoint());
+ break;
+ }
+ }
+ for (; j < i; ++j) {
+ var anchor = path._segments[j].getPoint();
+ control = anchor.add(anchor.subtract(control));
+ }
+ path.quadraticCurveTo(control, point);
+ break;
+ }
+ }
+ return compoundPath || path;
+ }
+
+ function importGradient(svg, type) {
+ var nodes = svg.childNodes,
+ stops = [];
+ for (var i = 0, l = nodes.length; i < l; i++) {
+ var node = nodes[i];
+ if (node.nodeType == 1)
+ stops.push(applyAttributes(new GradientStop(), node));
+ }
+ var gradient = new Gradient(stops),
+ isRadial = type == 'radialgradient',
+ origin, destination, highlight;
+ if (isRadial) {
+ gradient.type = 'radial';
+ origin = getPoint(svg, 'cx', 'cy');
+ destination = origin.add(getValue(svg, 'r'), 0);
+ highlight = getPoint(svg, 'fx', 'fy', true);
+ } else {
+ origin = getPoint(svg, 'x1', 'y1');
+ destination = getPoint(svg, 'x2', 'y2');
+ }
+ applyAttributes(
+ new GradientColor(gradient, origin, destination, highlight), svg);
+ return null;
+ }
+
+ var definitions = {};
+ function getDefinition(value) {
+ var match = value.match(/\(#([^)']+)/);
+ return match && definitions[match[1]];
+ }
+
+ var importers = {
+ // http://www.w3.org/TR/SVG/struct.html#Groups
+ g: importGroup,
+ // http://www.w3.org/TR/SVG/struct.html#NewDocument
+ svg: importGroup,
+ clippath: importGroup,
+ // http://www.w3.org/TR/SVG/shapes.html#PolygonElement
+ polygon: importPoly,
+ // http://www.w3.org/TR/SVG/shapes.html#PolylineElement
+ polyline: importPoly,
+ // http://www.w3.org/TR/SVG/paths.html
+ path: importPath,
+ // http://www.w3.org/TR/SVG/pservers.html#LinearGradients
+ lineargradient: importGradient,
+ // http://www.w3.org/TR/SVG/pservers.html#RadialGradients
+ radialgradient: importGradient,
+
+ // http://www.w3.org/TR/SVG/struct.html#ImageElement
+ image: function (svg) {
+ var raster = new Raster(getValue(svg, 'href'));
+ raster.attach('load', function() {
+ var size = getSize(svg, 'width', 'height');
+ this.setSize(size);
+ // Since x and y start from the top left of an image, add
+ // half of its size:
+ this.translate(getPoint(svg, 'x', 'y').add(size.divide(2)));
+ });
+ return raster;
+ },
+
+ // http://www.w3.org/TR/SVG/struct.html#SymbolElement
+ symbol: function(svg, type) {
+ return new Symbol(applyAttributes(importGroup(svg, type), svg));
+ },
+
+ // http://www.w3.org/TR/SVG/struct.html#DefsElement
+ defs: importGroup,
+
+ // http://www.w3.org/TR/SVG/struct.html#UseElement
+ use: function(svg, type) {
+ // Note the namespaced xlink:href attribute is just called href
+ // as a property on svg.
+ // TODO: Should getValue become namespace aware?
+ var id = (getValue(svg, 'href') || '').substring(1),
+ definition = definitions[id];
+ // Use place if we're dealing with a symbol:
+ return definition
+ ? definition instanceof Symbol
+ ? definition.place()
+ : definition.clone()
+ : null;
+ },
+
+ circle: function(svg) {
+ return new Path.Circle(getPoint(svg, 'cx', 'cy'),
+ getValue(svg, 'r'));
+ },
+
+ ellipse: function(svg) {
+ var center = getPoint(svg, 'cx', 'cy'),
+ radius = getSize(svg, 'rx', 'ry');
+ return new Path.Ellipse(new Rectangle(center.subtract(radius),
+ center.add(radius)));
+ },
+
+ rect: function(svg) {
+ var point = getPoint(svg, 'x', 'y'),
+ size = getSize(svg, 'width', 'height'),
+ radius = getSize(svg, 'rx', 'ry');
+ return new Path.RoundRectangle(new Rectangle(point, size), radius);
+ },
+
+ line: function(svg) {
+ return new Path.Line(getPoint(svg, 'x1', 'y1'),
+ getPoint(svg, 'x2', 'y2'));
+ },
+
+ text: function(svg) {
+ var text = new PointText(getPoint(svg, 'x', 'y', false, 0)
+ .add(getPoint(svg, 'dx', 'dy', false, 0)));
+ text.setContent(svg.textContent || '');
+ return text;
+ }
+ };
+
+ function applyAttributes(item, svg) {
+ for (var i = 0, l = svg.style.length; i < l; i++) {
+ var name = svg.style[i];
+ item = applyAttribute(item, svg, name, svg.style[Base.camelize(name)]);
+ }
+ for (var i = 0, l = svg.attributes.length; i < l; i++) {
+ var attr = svg.attributes[i];
+ item = applyAttribute(item, svg, attr.name, attr.value);
+ }
+ return item;
+ }
+
+ function applyAttribute(item, svg, name, value) {
+ if (value == null)
+ return item;
+ var entry = SvgStyles.attributes[name];
+ if (entry) {
+ item._style[entry.set](convertValue(value, entry.type));
+ } else {
+ switch (name) {
+ case 'id':
+ definitions[value] = item;
+ if (item.setName)
+ item.setName(value);
+ break;
+ case 'clip-path':
+ var clipPath = getDefinition(value).clone().reduce();
+ item = createClipGroup(item, clipPath);
+ break;
+ case 'gradientTransform':
+ case 'transform':
+ applyTransform(item, svg, name);
+ break;
+ case 'stop-opacity':
+ case 'opacity':
+ var opacity = Base.toFloat(value);
+ if (name === 'stop-opacity') {
+ item.color.setAlpha(opacity);
+ } else {
+ item.setOpacity(opacity);
+ }
+ break;
+ case 'fill-opacity':
+ case 'stroke-opacity':
+ var color = item[name == 'fill-opacity'
+ ? 'getFillColor' : 'getStrokeColor']();
+ if (color)
+ color.setAlpha(Base.toFloat(value));
+ break;
+ case 'visibility':
+ item.setVisible(value === 'visible');
+ break;
+ case 'font':
+ case 'font-family':
+ case 'font-size':
+ case 'text-anchor':
+ applyTextAttribute(item, svg, name, value);
+ break;
+ case 'stop-color':
+ item.setColor(value);
+ break;
+ case 'offset':
+ var percentage = value.match(/(.*)%$/);
+ item.setRampPoint(percentage ? percentage[1] / 100 : value);
+ break;
+ case 'viewBox':
+ if (item instanceof Symbol)
+ break;
+ var values = convertValue(value, 'array'),
+ rectangle = Rectangle.create.apply(this, values),
+ size = getSize(svg, 'width', 'height', true),
+ scale = size ? rectangle.getSize().divide(size) : 1,
+ offset = rectangle.getPoint(),
+ matrix = new Matrix().translate(offset).scale(scale);
+ item.transform(matrix.createInverse());
+ if (size)
+ rectangle.setSize(size);
+ rectangle.setPoint(0);
+ item = createClipGroup(item, new Path.Rectangle(rectangle));
+ break;
+ }
+ }
+ return item;
+ }
+
+ function applyTextAttribute(item, svg, name, value) {
+ if (item instanceof TextItem) {
+ switch (name) {
+ case 'font':
+ var text = document.createElement('span');
+ text.style.font = value;
+ for (var i = 0; i < text.style.length; i++) {
+ var name = text.style[i];
+ item = applyAttribute(item, svg, name, text.style[name]);
+ }
+ break;
+ case 'font-family':
+ item.setFont(value.split(',')[0].replace(/^\s+|\s+$/g, ''));
+ break;
+ case 'font-size':
+ item.setFontSize(Base.toFloat(value));
+ break;
+ case 'text-anchor':
+ item.setJustification({
+ start: 'left',
+ middle: 'center',
+ end: 'right'
+ }[value]);
+ break;
+ }
+ } else if (item instanceof Group) {
+ var children = item._children;
+ for (var i = 0, l = children.length; i < l; i++) {
+ applyTextAttribute(children[i], svg, name, value);
+ }
+ }
+ }
+
+ function applyTransform(item, svg, name) {
+ var svgTransform = svg[name],
+ transforms = svgTransform.baseVal,
+ matrix = new Matrix();
+ for (var i = 0, l = transforms.numberOfItems; i < l; i++) {
+ var transform = transforms.getItem(i);
+ if (transform.type === 0)
+ continue;
+ var mx = transform.matrix,
+ a = mx.a,
+ b = mx.b,
+ c = mx.c,
+ d = mx.d;
+ switch (transform.type) {
+ case 1:
+ var tmp = b;
+ b = c;
+ c = tmp;
+ break;
+ case 5:
+ b = c;
+ c = 0;
+ break;
+ case 6:
+ c = b;
+ b = 0;
+ break;
+ case 4:
+ b = -b;
+ c = -c;
+ break;
+ }
+ matrix.concatenate(new Matrix(a, c, b, d, mx.e, mx.f));
+ }
+ item.transform(matrix);
+ }
+
+ function importSvg(svg) {
+ var type = svg.nodeName.toLowerCase(),
+ importer = importers[type],
+ item = importer && importer(svg, type);
+ return item ? applyAttributes(item, svg) : item;
+ }
+
+ Item.inject({
+ importSvg: function(svg) {
+ return this.addChild(importSvg(svg));
+ }
+ });
+
+ Project.inject({
+ importSvg: function(svg) {
+ this.activate();
+ return importSvg(svg);
+ }
+ });
+};
+
+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);
+ },
+
+ _getChildren: function() {
+ return this._item instanceof Group && this._item._children;
+ },
+
+ statics: {
+ create: function(item) {
+ var style = Base.create(this);
+ 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 = /Color$/.test(key),
+ part = Base.capitalize(key),
+ set = 'set' + part,
+ get = 'get' + part;
+ src[set] = function(value) {
+ var children = this._getChildren();
+ value = isColor ? Color.read(arguments, 0, 0, true) : value;
+ if (children) {
+ for (var i = 0, l = children.length; i < l; i++)
+ children[i][styleKey][set](value);
+ } else {
+ var old = this['_' + key];
+ if (!Base.equals(old, value)) {
+ if (isColor) {
+ if (old)
+ delete old._owner;
+ if (value) {
+ value._owner = this._item;
+ }
+ }
+ this['_' + key] = value;
+ if (this._item)
+ this._item._changed(flags[key] || 17);
+ }
+ }
+ return this;
+ };
+ src[get] = function() {
+ var children = this._getChildren(),
+ 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 (!Base.equals(style, 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: 25,
+ strokeCap: 25,
+ strokeJoin: 25,
+ miterLimit: 25
+ }
+
+});
+
+var ParagraphStyle = this.ParagraphStyle = Style.extend({
+ _owner: TextItem,
+ _style: 'paragraphStyle',
+ _defaults: {
+ justification: 'left'
+ },
+ _flags: {
+ justification: 5
+ }
+
+});
+
+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: 5,
+ leading: 5,
+ font: 5
+ }
+
+}, {
+ 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,
+ _readIndex: true,
+
+ initialize: function(arg) {
+ var isArray = Array.isArray(arg),
+ type = this._colorType,
+ res;
+ if (typeof arg === 'object' && !isArray) {
+ if (!type) {
+ res = 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();
+ if (this._read)
+ res._read = 1;
+ } else {
+ res = Color.read(arguments).convert(type);
+ if (this._read)
+ res._read = arguments._read;
+ }
+ } else if (typeof arg === 'string') {
+ var rgbColor = arg.match(/^#[0-9a-f]{3,6}$/i)
+ ? hexToRgbColor(arg)
+ : nameToRgbColor(arg);
+ res = type
+ ? rgbColor.convert(type)
+ : rgbColor;
+ if (this._read)
+ res._read = 1;
+ } else {
+ var components = isArray ? arg
+ : Array.prototype.slice.call(arguments);
+ if (!type) {
+ var ctor = components.length >= 3
+ ? RgbColor
+ : GrayColor;
+ res = new ctor(components);
+ } else {
+ res = Base.each(this._components,
+ function(name, i) {
+ var value = components[i];
+ this['_' + name] = value !== undefined
+ ? value : null;
+ },
+ this);
+ }
+ if (this._read)
+ res._read = res._components.length;
+ }
+ return res;
+ },
+
+ clone: function() {
+ var copy = Base.create(this.constructor),
+ 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);
+ },
+
+ random: function() {
+ return new RgbColor(Math.random(), Math.random(), Math.random());
+ }
+ }
+ };
+
+ 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._css = null;
+ if (this._owner)
+ this._owner._changed(17);
+ },
+
+ 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.formatFloat;
+ 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(', ') + ' }';
+ },
+
+ toCss: function(withAlpha) {
+ if (!this._css) {
+ var color = this.convert('rgb'),
+ alpha = withAlpha === undefined || withAlpha ? color.getAlpha() : 1,
+ components = [
+ Math.round(color._red * 255),
+ Math.round(color._green * 255),
+ Math.round(color._blue * 255)
+ ];
+ if (alpha < 1)
+ components.push(alpha);
+ this._css = (components.length == 4 ? 'rgba(' : 'rgb(')
+ + components.join(', ') + ')';
+ }
+ return this._css;
+ },
+
+ getCanvasStyle: function() {
+ return this.toCss();
+ }
+
+});
+
+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, 0, 0, true);
+ 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, 0, 0, true);
+ 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, 0, 0, true);
+ 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.toCss());
+ }
+ 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++)
+ delete this._stops[i]._owner;
+ }
+ if (stops.length < 2)
+ throw new Error(
+ 'Gradient stop list needs to contain at least two stops.');
+ this._stops = GradientStop.readAll(stops, 0, true);
+ for (var i = 0, l = this._stops.length; i < l; i++) {
+ var stop = this._stops[i];
+ stop._owner = 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 (arg0) {
+ if (arg1 === undefined && Array.isArray(arg0)) {
+ this.setColor(arg0[0]);
+ this.setRampPoint(arg0[1]);
+ } else if (arg0 && 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() {
+ if (this._owner)
+ this._owner._changed(17);
+ },
+
+ 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) {
+ this._color = Color.read(arguments);
+ if (this._color === color)
+ this._color = color.clone();
+ this._color._owner = this;
+ this._changed();
+ },
+
+ equals: function(stop) {
+ return stop == this || stop instanceof GradientStop
+ && this._color.equals(stop._color)
+ && this._rampPoint == stop._rampPoint;
+ }
+});
+
+var DomElement = new function() {
+
+ var special = /^(checked|value|selected|disabled)$/i,
+ translated = { text: 'textContent', html: 'innerHTML' },
+ unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 };
+
+ function create(nodes, parent) {
+ var res = [];
+ for (var i = 0, l = nodes && nodes.length; i < l;) {
+ var el = nodes[i++];
+ if (typeof el === 'string') {
+ el = document.createElement(el);
+ } else if (!el || !el.nodeType) {
+ continue;
+ }
+ if (Base.isObject(nodes[i]))
+ DomElement.set(el, nodes[i++]);
+ if (Array.isArray(nodes[i]))
+ create(nodes[i++], el);
+ if (parent)
+ parent.appendChild(el);
+ res.push(el);
+ }
+ return res;
+ }
+
+ return {
+ create: function(nodes, parent) {
+ var isArray = Array.isArray(nodes),
+ res = create(isArray ? nodes : arguments, isArray ? parent : null);
+ return res.length == 1 ? res[0] : res;
+ },
+
+ find: function(selector, root) {
+ return (root || document).querySelector(selector);
+ },
+
+ findAll: function(selector, root) {
+ return (root || document).querySelectorAll(selector);
+ },
+
+ get: function(el, key) {
+ return el
+ ? special.test(key)
+ ? key === 'value' || typeof el[key] !== 'string'
+ ? el[key]
+ : true
+ : key in translated
+ ? el[translated[key]]
+ : el.getAttribute(key)
+ : null;
+ },
+
+ set: function(el, key, value) {
+ if (typeof key !== 'string') {
+ for (var name in key)
+ if (key.hasOwnProperty(name))
+ this.set(el, name, key[name]);
+ } else if (!el || value === undefined) {
+ return el;
+ } else if (special.test(key)) {
+ el[key] = value;
+ } else if (key in translated) {
+ el[translated[key]] = value;
+ } else if (key === 'style') {
+ this.setStyle(el, value);
+ } else if (key === 'events') {
+ DomEvent.add(el, value);
+ } else {
+ el.setAttribute(key, value);
+ }
+ return el;
+ },
+
+ getStyle: function(el, key) {
+ var style = el.ownerDocument.defaultView.getComputedStyle(el, '');
+ return el.style[key] || style && style[key] || null;
+ },
+
+ setStyle: function(el, key, value) {
+ if (typeof key !== 'string') {
+ for (var name in key)
+ if (key.hasOwnProperty(name))
+ this.setStyle(el, name, key[name]);
+ } else {
+ if (/^-?[\d\.]+$/.test(value) && !(key in unitless))
+ value += 'px';
+ el.style[key] = value;
+ }
+ return el;
+ },
+
+ hasClass: function(el, cls) {
+ return new RegExp('\\s*' + cls + '\\s*').test(el.className);
+ },
+
+ addClass: function(el, cls) {
+ el.className = (el.className + ' ' + cls).trim();
+ },
+
+ removeClass: function(el, cls) {
+ el.className = el.className.replace(
+ new RegExp('\\s*' + cls + '\\s*'), ' ').trim();
+ },
+
+ remove: function(el) {
+ if (el.parentNode)
+ el.parentNode.removeChild(el);
+ },
+
+ removeChildren: function(el) {
+ while (el.firstChild)
+ el.removeChild(el.firstChild);
+ },
+
+ getBounds: function(el, viewport) {
+ var rect = el.getBoundingClientRect(),
+ doc = el.ownerDocument,
+ body = doc.body,
+ html = doc.documentElement,
+ x = rect.left - (html.clientLeft || body.clientLeft || 0),
+ y = rect.top - (html.clientTop || body.clientTop || 0);
+ if (!viewport) {
+ var view = doc.defaultView;
+ x += view.pageXOffset || html.scrollLeft || body.scrollLeft;
+ y += view.pageYOffset || html.scrollTop || body.scrollTop;
+ }
+ return new Rectangle(x, y, rect.width, rect.height);
+ },
+
+ getViewportBounds: function(el) {
+ var doc = el.ownerDocument,
+ view = doc.defaultView,
+ html = doc.documentElement;
+ return Rectangle.create(0, 0,
+ view.innerWidth || html.clientWidth,
+ view.innerHeight || html.clientHeight
+ );
+ },
+
+ 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]);
+ },
+
+ isInView: function(el) {
+ return !this.isInvisible(el) && this.getViewportBounds(el).intersects(
+ this.getBounds(el, true));
+ }
+ };
+};
+
+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 == null)
+ 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 = 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.isInView(el)) {
+ callbacks.splice(i, 1);
+ func(Date.now());
+ }
+ }
+ }, 1000 / 60);
+ };
+};
+
+var View = this.View = Base.extend(Callback, {
+ 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._viewHandlers);
+ if (PaperScript.hasAttribute(element, 'resize')) {
+ var offset = DomElement.getOffset(element, true),
+ that = this;
+ size = DomElement.getViewportBounds(element)
+ .getSize().subtract(offset);
+ 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);
+ }
+ element.width = size.width;
+ element.height = size.height;
+ 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;
+ this._frameItems = {};
+ this._frameItemCount = 0;
+ },
+
+ 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._viewHandlers);
+ DomEvent.remove(window, this._windowHandlers);
+ this._element = this._project = null;
+ this.detach('frame');
+ this._frameItems = {};
+ return true;
+ },
+
+ _events: {
+ onFrame: {
+ install: function() {
+ if (!this._requested) {
+ this._animate = true;
+ this._handleFrame(true);
+ }
+ },
+
+ uninstall: function() {
+ this._animate = false;
+ }
+ },
+
+ onResize: {}
+ },
+
+ _animate: false,
+ _time: 0,
+ _count: 0,
+
+ _handleFrame: function(request) {
+ this._requested = false;
+ if (!this._animate)
+ return;
+ paper = this._scope;
+ if (request) {
+ this._requested = true;
+ var that = this;
+ DomEvent.requestAnimationFrame(function() {
+ that._handleFrame(true);
+ }, this._element);
+ }
+ var now = Date.now() / 1000,
+ delta = this._before ? now - this._before : 0;
+ this._before = now;
+ this.fire('frame', Base.merge({
+ delta: delta,
+ time: this._time += delta,
+ count: this._count++
+ }));
+ if (this._stats)
+ this._stats.update();
+ this.draw(true);
+ },
+
+ _animateItem: function(item, animate) {
+ var items = this._frameItems;
+ if (animate) {
+ items[item._id] = {
+ item: item,
+ time: 0,
+ count: 0
+ };
+ if (++this._frameItemCount == 1)
+ this.attach('frame', this._handleFrameItems);
+ } else {
+ delete items[item._id];
+ if (--this._frameItemCount == 0) {
+ this.detach('frame', this._handleFrameItems);
+ }
+ }
+ },
+
+ _handleFrameItems: function(event) {
+ for (var i in this._frameItems) {
+ var entry = this._frameItems[i];
+ entry.item.fire('frame', Base.merge(event, {
+ time: entry.time += event.delta,
+ count: entry.count++
+ }));
+ }
+ },
+
+ _redraw: function() {
+ this._redrawNeeded = true;
+ if (this._animate) {
+ this._handleFrame();
+ } 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.isInView(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,
+ prevFocus,
+ 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) {
+ prevFocus = View._focused;
+ View._focused = tempFocus = view;
+ } else if (tempFocus && tempFocus == View._focused) {
+ View._focused = prevFocus;
+ 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 {
+ _viewHandlers: {
+ 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,
+ space: 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 Palette = this.Palette = Base.extend(Callback, {
+ _events: [ 'onChange' ],
+
+ initialize: function(title, components, values) {
+ var parent = DomElement.find('.palettejs-panel')
+ || DomElement.find('body').appendChild(
+ DomElement.create('div', { 'class': 'palettejs-panel' }));
+ this._element = parent.appendChild(
+ DomElement.create('table', { 'class': 'palettejs-pane' })),
+ this._title = title;
+ if (!values)
+ values = {};
+ for (var name in (this._components = components)) {
+ var component = components[name];
+ if (!(component instanceof Component)) {
+ if (component.value == null)
+ component.value = values[name];
+ component.name = name;
+ component = components[name] = new Component(component);
+ }
+ this._element.appendChild(component._element);
+ component._palette = this;
+ if (values[name] === undefined)
+ values[name] = component.value;
+ }
+ this._values = Base.each(values, function(value, name) {
+ var component = components[name];
+ if (component) {
+ Base.define(values, name, {
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ get: function() {
+ return component._value;
+ },
+ set: function(val) {
+ component.setValue(val);
+ }
+ });
+ }
+ });
+ if (window.paper)
+ paper.palettes.push(this);
+ },
+
+ reset: function() {
+ for (var i in this._components)
+ this._components[i].reset();
+ },
+
+ remove: function() {
+ DomElement.remove(this._element);
+ }
+});
+
+var Component = this.Component = Base.extend(Callback, {
+ _events: [ 'onChange', 'onClick' ],
+
+ _types: {
+ 'boolean': {
+ type: 'checkbox',
+ value: 'checked'
+ },
+
+ string: {
+ type: 'text'
+ },
+
+ number: {
+ type: 'number',
+ number: true
+ },
+
+ button: {
+ type: 'button'
+ },
+
+ text: {
+ tag: 'div',
+ value: 'text'
+ },
+
+ slider: {
+ type: 'range',
+ number: true
+ },
+
+ list: {
+ tag: 'select',
+
+ options: function() {
+ DomElement.removeChildren(this._inputItem);
+ DomElement.create(Base.each(this._options, function(option) {
+ this.push('option', { value: option, text: option });
+ }, []), this._inputItem);
+ }
+ }
+ },
+
+ initialize: function(obj) {
+ this._type = obj.type in this._types
+ ? obj.type
+ : 'options' in obj
+ ? 'list'
+ : 'onClick' in obj
+ ? 'button'
+ : typeof obj.value;
+ this._info = this._types[this._type] || { type: this._type };
+ var that = this,
+ fireChange = false;
+ this._inputItem = DomElement.create(this._info.tag || 'input', {
+ type: this._info.type,
+ events: {
+ change: function() {
+ that.setValue(
+ DomElement.get(this, that._info.value || 'value'));
+ if (fireChange) {
+ that._palette.fire('change', that, that.name, that._value);
+ that.fire('change', that._value);
+ }
+ },
+ click: function() {
+ that.fire('click');
+ }
+ }
+ });
+ this._element = DomElement.create('tr', [
+ this._labelItem = DomElement.create('td'),
+ 'td', [this._inputItem]
+ ]);
+ Base.each(obj, function(value, key) {
+ this[key] = value;
+ }, this);
+ this._defaultValue = this._value;
+ fireChange = true;
+ },
+
+ getType: function() {
+ return this._type;
+ },
+
+ getLabel: function() {
+ return this._label;
+ },
+
+ setLabel: function(label) {
+ this._label = label;
+ DomElement.set(this._labelItem, 'text', label + ':');
+ },
+
+ getOptions: function() {
+ return this._options;
+ },
+
+ setOptions: function(options) {
+ this._options = options;
+ if (this._info.options)
+ this._info.options.call(this);
+ },
+
+ getValue: function() {
+ return this._value;
+ },
+
+ setValue: function(value) {
+ var key = this._info.value || 'value';
+ DomElement.set(this._inputItem, key, value);
+ value = DomElement.get(this._inputItem, key);
+ this._value = this._info.number ? Base.toFloat(value) : value;
+ },
+
+ getRange: function() {
+ return [Base.toFloat(DomElement.get(this._inputItem, 'min')),
+ Base.toFloat(DomElement.get(this._inputItem, 'max'))];
+ },
+
+ setRange: function(min, max) {
+ var range = Array.isArray(min) ? min : [min, max];
+ DomElement.set(this._inputItem, { min: range[0], max: range[1] });
+ },
+
+ getMin: function() {
+ return this.getRange()[0];
+ },
+
+ setMin: function(min) {
+ this.setRange(min, this.getMax());
+ },
+
+ getMax: function() {
+ return this.getRange()[1];
+ },
+
+ setMax: function(max) {
+ this.setRange(this.getMin(), max);
+ },
+
+ getStep: function() {
+ return Base.toFloat(DomElement.get(this._inputItem, 'step'));
+ },
+
+ setStep: function(step) {
+ DomElement.set(this._inputItem, 'step', step);
+ },
+
+ reset: function() {
+ this.setValue(this._defaultValue);
+ }
+});
+
+var ToolEvent = this.ToolEvent = Event.extend({
+ _item: null,
+
+ initialize: function(tool, type, event) {
+ this.tool = tool;
+ this.type = type;
+ this.event = event;
+ },
+
+ _choosePoint: function(point, toolPoint) {
+ return point ? point : toolPoint ? toolPoint.clone() : null;
+ },
+
+ getPoint: function() {
+ return this._choosePoint(this._point, this.tool._point);
+ },
+
+ setPoint: function(point) {
+ this._point = point;
+ },
+
+ getLastPoint: function() {
+ return this._choosePoint(this._lastPoint, this.tool._lastPoint);
+ },
+
+ setLastPoint: function(lastPoint) {
+ this._lastPoint = lastPoint;
+ },
+
+ getDownPoint: function() {
+ return this._choosePoint(this._downPoint, this.tool._downPoint);
+ },
+
+ setDownPoint: function(downPoint) {
+ this._downPoint = downPoint;
+ },
+
+ getMiddlePoint: function() {
+ if (!this._middlePoint && this.tool._lastPoint) {
+ return this.tool._point.add(this.tool._lastPoint).divide(2);
+ }
+ return this.middlePoint;
+ },
+
+ setMiddlePoint: function(middlePoint) {
+ this._middlePoint = middlePoint;
+ },
+
+ getDelta: function() {
+ return !this._delta && this.tool._lastPoint
+ ? this.tool._point.subtract(this.tool._lastPoint)
+ : this._delta;
+ },
+
+ setDelta: function(delta) {
+ this._delta = delta;
+ },
+
+ getCount: function() {
+ return /^mouse(down|up)$/.test(this.type)
+ ? this.tool._downCount
+ : this.tool._count;
+ },
+
+ setCount: function(count) {
+ this.tool[/^mouse(down|up)$/.test(this.type) ? 'downCount' : 'count']
+ = count;
+ },
+
+ getItem: function() {
+ if (!this._item) {
+ var result = this.tool._scope.project.hitTest(this.getPoint());
+ if (result) {
+ var item = result.item,
+ parent = item._parent;
+ while ((parent instanceof Group && !(parent instanceof Layer))
+ || parent instanceof CompoundPath) {
+ item = parent;
+ parent = parent._parent;
+ }
+ this._item = item;
+ }
+ }
+ return this._item;
+ },
+ setItem: function(item) {
+ this._item = item;
+ },
+
+ toString: function() {
+ return '{ type: ' + this.type
+ + ', point: ' + this.getPoint()
+ + ', count: ' + this.getCount()
+ + ', modifiers: ' + this.getModifiers()
+ + ' }';
+ }
+});
+
+var Tool = this.Tool = PaperScopeItem.extend({
+ _list: 'tools',
+ _reference: '_tool',
+ _events: [ 'onActivate', 'onDeactivate', 'onEditOptions',
+ '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,
+ vector = pt.subtract(this._point),
+ 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,
+ pow = Math.pow,
+ cos = Math.cos,
+ PI = Math.PI;
+
+ function cbrt(x) {
+ return x > 0 ? pow(x, 1 / 3) : x < 0 ? -pow(-x, 1 / 3) : 0;
+ }
+
+ return {
+ TOLERANCE: 10e-6,
+ EPSILON: 10e-12,
+
+ isZero: function(val) {
+ return Math.abs(val) <= this.EPSILON;
+ },
+
+ 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;
+ }
+ return abs(c) < tolerance ? -1 : 0;
+ }
+ var q = b * b - 4 * a * c;
+ if (q < 0)
+ return 0;
+ q = sqrt(q);
+ a *= 2;
+ var n = 0;
+ roots[n++] = (-b - q) / a;
+ if (q > 0)
+ roots[n++] = (-b + 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 bb = b * b,
+ p = 1 / 3 * (-1 / 3 * bb + c),
+ q = 1 / 2 * (2 / 27 * b * bb - 1 / 3 * b * c + d),
+ ppp = p * p * p,
+ D = q * q + ppp;
+ b /= 3;
+ if (abs(D) < tolerance) {
+ if (abs(q) < tolerance) {
+ roots[0] = - b;
+ return 1;
+ } else {
+ var u = cbrt(-q);
+ roots[0] = 2 * u - b;
+ roots[1] = - u - b;
+ return 2;
+ }
+ } else if (D < 0) {
+ var phi = 1 / 3 * Math.acos(-q / sqrt(-ppp));
+ var t = 2 * sqrt(-p);
+ roots[0] = t * cos(phi) - b;
+ roots[1] = - t * cos(phi + PI / 3) - b;
+ roots[2] = - t * cos(phi - PI / 3) - b;
+ return 3;
+ } else {
+ D = sqrt(D);
+ roots[0] = cbrt(D - q) - cbrt(D + q) - 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);
+ }
+};
+
+(function(e){"use strict";function k(e,t){throw typeof e=="number"&&(e=o(n,e)),t+=" ("+e.line+":"+e.column+")",new SyntaxError(t)}function Mt(e){function s(e){if(e.length==1)return t+="return str === "+JSON.stringify(e[0])+";";t+="switch(str){";for(var n=0;n3){n.sort(function(e,t){return t.length-e.length}),t+="switch(str.length){";for(var r=0;r=170&&qt.test(String.fromCharCode(e))}function Xt(e){return e<48?e===36:e<58?!0:e<65?!1:e<91?!0:e<97?e===95:e<123?!0:e>=170&&Rt.test(String.fromCharCode(e))}function Vt(){zt.lastIndex=b;var e=zt.exec(n);return e?e.index+e[0].length:n.length+1}function $t(){while(w<=u)++y,b=w,w=Vt();return{line:y,column:u-b}}function Jt(){y=1,u=b=0,w=Vt(),m=!0,g=null,Yt()}function Kt(e,n){f=u,t.locations&&(c=$t()),h=e,Yt(),p=n,v=g,m=e.beforeExpr}function Qt(){var e=n.indexOf("*/",u+=2);e===-1&&k(u-2,"Unterminated comment"),t.trackComments&&(g||(g=[])).push(n.slice(u,e)),u=e+2}function Gt(){var e=u,i=n.charCodeAt(u+=2);while(u8)++u;else if(e===32||e===160)++u;else{if(!(e>=5760&&jt.test(String.fromCharCode(e))))break;++u}}}function Zt(e){a=u,t.locations&&(l=$t()),d=g;if(e)return tn();if(u>=r)return Kt(_);var i=n.charCodeAt(u);if(Wt(i)||i===92)return ln();var s=n.charCodeAt(u+1);switch(i){case 46:if(s>=48&&s<=57)return sn(String.fromCharCode(i));return++u,Kt(dt);case 40:return++u,Kt(ft);case 41:return++u,Kt(lt);case 59:return++u,Kt(ht);case 44:return++u,Kt(ct);case 91:return++u,Kt(st);case 93:return++u,Kt(ot);case 123:return++u,Kt(ut);case 125:return++u,Kt(at);case 58:return++u,Kt(pt);case 63:return++u,Kt(vt);case 48:if(s===120||s===88)return rn();case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return sn(String.fromCharCode(i));case 34:case 39:return on(i);case 47:if(m)return++u,tn();if(s===61)return en(yt,2);return en(mt,1);case 37:case 42:if(s===61)return en(yt,2);return en(Ot,1);case 124:case 38:if(s===i)return en(i===124?St:xt,2);if(s===61)return en(yt,2);return en(i===124?Tt:Ct,1);case 94:if(s===61)return en(yt,2);return en(Nt,1);case 43:case 45:if(s===i)return en(wt,2);if(s===61)return en(yt,2);return en(bt,1);case 60:case 62:var o=1;if(s===i)return o=i===62&&n.charCodeAt(u+2)===62?3:2,n.charCodeAt(u+o)===61?en(yt,o+1):en(At,o);return s===61&&(o=n.charCodeAt(u+2)===61?3:2),en(Lt,o);case 61:case 33:if(s===61)return en(kt,n.charCodeAt(u+2)===61?3:2);return en(i===61?gt:Et,1);case 126:return en(Et,1)}var f=String.fromCharCode(i);if(f==="\\"||qt.test(f))return ln();k(u,"Unexpected character '"+f+"'")}function en(e,t){var r=n.slice(u,u+t);u+=t,Kt(e,r)}function tn(){var e="",t,i,s=u;for(;;){u>=r&&k(s,"Unterminated regular expression");var o=n.charAt(u);Ut.test(o)&&k(s,"Unterminated regular expression");if(!t){if(o==="[")i=!0;else if(o==="]"&&i)i=!1;else if(o==="/"&&!i)break;t=o==="\\"}else t=!1;++u}var e=n.slice(s,u);++u;var a=fn();return a&&!/^[gmsiy]*$/.test(a)&&k(s,"Invalid regexp flag"),Kt(A,new RegExp(e,a))}function nn(e,t){var r=u,i=0;for(;;){var s=n.charCodeAt(u),o;s>=97?o=s-97+10:s>=65?o=s-65+10:s>=48&&s<=57?o=s-48:o=Infinity;if(o>=e)break;++u,i=i*e+o}return u===r||t!=null&&u-r!==t?null:i}function rn(){u+=2;var e=nn(16);return e==null&&k(a+2,"Expected hexadecimal number"),Wt(n.charCodeAt(u))&&k(u,"Identifier directly after number"),Kt(L,e)}function sn(e){var t=u,r=e===".";!r&&nn(10)==null&&k(t,"Invalid number");if(r||n.charAt(u)==="."){var i=n.charAt(++u);(i==="-"||i==="+")&&++u,nn(10)===null&&e==="."&&k(t,"Invalid number"),r=!0}if(/e/i.test(n.charAt(u))){var i=n.charAt(++u);(i==="-"||i==="+")&&++u,nn(10)===null&&k(t,"Invalid number"),r=!0}Wt(n.charCodeAt(u))&&k(u,"Identifier directly after number");var s=n.slice(t,u),o;return r?o=parseFloat(s):e!=="0"||s.length===1?o=parseInt(s,10):/[89]/.test(s)||C?k(t,"Invalid number"):o=parseInt(s,8),Kt(L,o)}function on(e){u++;var t=[];for(;;){u>=r&&k(a,"Unterminated string constant");var i=n.charCodeAt(u);if(i===e)return++u,Kt(O,String.fromCharCode.apply(null,t));if(i===92){i=n.charCodeAt(++u);var s=/^[0-7]+/.exec(n.slice(u,u+3));s&&(s=s[0]);while(s&&parseInt(s,8)>255)s=s.slice(0,s.length-1);s==="0"&&(s=null),++u;if(s)C&&k(u-2,"Octal literal in strict mode"),t.push(parseInt(s,8)),u+=s.length-1;else switch(i){case 110:t.push(10);break;case 114:t.push(13);break;case 120:t.push(un(2));break;case 117:t.push(un(4));break;case 85:t.push(un(8));break;case 116:t.push(9);break;case 98:t.push(8);break;case 118:t.push(11);break;case 102:t.push(12);break;case 48:t.push(0);break;case 13:n.charCodeAt(u)===10&&++u;case 10:break;default:t.push(i)}}else(i===13||i===10||i===8232||i===8329)&&k(a,"Unterminated string constant"),i!==92&&t.push(i),++u}}function un(e){var t=nn(16,e);return t===null&&k(a,"Bad character escape sequence"),t}function fn(){an=!1;var e,t=!0,r=u;for(;;){var i=n.charCodeAt(u);if(Xt(i))an&&(e+=n.charAt(u)),++u;else{if(i!==92)break;an||(e=n.slice(r,u)),an=!0,n.charCodeAt(++u)!=117&&k(u,"Expecting Unicode escape sequence \\uXXXX"),++u;var s=un(4),o=String.fromCharCode(s);o||k(u-1,"Invalid Unicode escape"),(t?!Wt(s):!Xt(s))&&k(u-4,"Invalid Unicode escape"),e+=o}t=!1}return an?e:n.slice(r,u)}function ln(){var e=fn(),n=M;return an||(Bt(e)?n=it[e]:(t.forbidReserved&&(t.ecmaVersion===3?_t:Dt)(e)||C&&Pt(e))&&k(a,"The keyword '"+e+"' is reserved")),Kt(n,e)}function cn(){E=a,S=f,x=c,Zt()}function hn(e){C=e,u=S,Yt(),Zt()}function pn(){var e={type:null,start:a,end:null};return t.trackComments&&d&&(e.commentsBefore=d,d=null),t.locations&&(e.loc={start:l,end:null,source:i}),t.ranges&&(e.range=[a,0]),e}function dn(e){var n={type:null,start:e.start};return e.commentsBefore&&(n.commentsBefore=e.commentsBefore,e.commentsBefore=null),t.locations&&(n.loc={start:e.loc.start,end:null,source:e.loc.source}),t.ranges&&(n.range=[e.range[0],0]),n}function mn(e,n){return e.type=n,e.end=S,t.trackComments&&(v?(e.commentsAfter=v,v=null):vn&&vn.end===S&&vn.commentsAfter&&(e.commentsAfter=vn.commentsAfter,vn.commentsAfter=null),vn=e),t.locations&&(e.loc.end=x),t.ranges&&(e.range[1]=S),e}function gn(e){return t.ecmaVersion>=5&&e.type==="ExpressionStatement"&&e.expression.type==="Literal"&&e.expression.value==="use strict"}function yn(e){if(h===e)return cn(),!0}function bn(){return!t.strictSemicolons&&(h===_||h===at||Ut.test(n.slice(S,a)))}function wn(){!yn(ht)&&!bn()&&Sn()}function En(e){h===e?cn():Sn()}function Sn(){k(a,"Unexpected token")}function xn(e){e.type!=="Identifier"&&e.type!=="MemberExpression"&&k(e.start,"Assigning to rvalue"),C&&e.type==="Identifier"&&Ht(e.name)&&k(e.start,"Assigning to "+e.name+" in strict mode")}function Tn(e){Jt(),E=S=u,t.locations&&(x=$t()),T=C=null,N=[],Zt();var n=e||pn(),r=!0;e||(n.body=[]);while(h!==_){var i=kn();n.body.push(i),r&&gn(i)&&hn(!0),r=!1}return mn(n,"Program")}function kn(){h===mt&&Zt(!0);var e=h,t=pn();switch(e){case D:case B:cn();var r=e===D;yn(ht)||bn()?t.label=null:h!==M?Sn():(t.label=$n(),wn());for(var i=0;it){var i=dn(e);i.left=e,i.operator=p,cn(),i.right=jn(Fn(n),r,n);var i=mn(i,/&&|\|\|/.test(i.operator)?"LogicalExpression":"BinaryExpression");return jn(i,t,n)}return e}function Fn(e){if(h.prefix){var t=pn(),n=h.isUpdate;return t.operator=p,t.prefix=!0,cn(),t.argument=Fn(e),n?xn(t.argument):C&&t.operator==="delete"&&t.argument.type==="Identifier"&&k(t.start,"Deleting local variable in strict mode"),mn(t,n?"UpdateExpression":"UnaryExpression")}var r=In();while(h.postfix&&!bn()){var t=dn(r);t.operator=p,t.prefix=!1,t.argument=r,xn(r),cn(),r=mn(t,"UpdateExpression")}return r}function In(){return qn(Rn())}function qn(e,t){if(yn(dt)){var n=dn(e);return n.object=e,n.property=$n(!0),n.computed=!1,qn(mn(n,"MemberExpression"),t)}if(yn(st)){var n=dn(e);return n.object=e,n.property=Dn(),n.computed=!0,En(ot),qn(mn(n,"MemberExpression"),t)}if(!t&&yn(ft)){var n=dn(e);return n.callee=e,n.arguments=Vn(lt,!1),qn(mn(n,"CallExpression"),t)}return e}function Rn(){switch(h){case Z:var e=pn();return cn(),mn(e,"ThisExpression");case M:return $n();case L:case O:case A:var e=pn();return e.value=p,e.raw=n.slice(a,f),cn(),mn(e,"Literal");case et:case tt:case nt:var e=pn();return e.value=h.atomValue,cn(),mn(e,"Literal");case ft:var r=l,i=a;cn();var s=Dn();return s.start=i,s.end=f,t.locations&&(s.loc.start=r,s.loc.end=c),t.ranges&&(s.range=[i,f]),En(lt),s;case st:var e=pn();return cn(),e.elements=Vn(ot,!0,!0),mn(e,"ArrayExpression");case ut:return zn();case z:var e=pn();return cn(),Xn(e,!1);case Y:return Un();default:Sn()}}function Un(){var e=pn();return cn(),e.callee=qn(Rn(!1),!0),yn(ft)?e.arguments=Vn(lt,!1):e.arguments=[],mn(e,"NewExpression")}function zn(){var e=pn(),n=!0,r=!1;e.properties=[],cn();while(!yn(at)){if(!n){En(ct);if(t.allowTrailingCommas&&yn(at))break}else n=!1;var i={key:Wn()},s=!1,o;yn(pt)?(i.value=Dn(!0),o=i.kind="init"):t.ecmaVersion>=5&&i.key.type==="Identifier"&&(i.key.name==="get"||i.key.name==="set")?(s=r=!0,o=i.kind=i.key.name,i.key=Wn(),!h===ft&&Sn(),i.value=Xn(pn(),!1)):Sn();if(i.key.type==="Identifier"&&(C||r))for(var u=0;u=0)for(var u=0;u= offset)
+ break;
+ offset += insertion[1];
+ }
+ return offset;
+ }
+
+ function getCode(node) {
+ return code.substring(getOffset(node.range[0]),
+ getOffset(node.range[1]));
+ }
+
+ function replaceCode(node, str) {
+ var start = getOffset(node.range[0]),
+ end = getOffset(node.range[1]);
+ var insert = 0;
+ for (var i = insertions.length - 1; i >= 0; i--) {
+ if (start > insertions[i][0]) {
+ insert = i + 1;
+ break;
+ }
+ }
+ insertions.splice(insert, 0, [start, str.length - end + start]);
+ code = code.substring(0, start) + str + code.substring(end);
+ }
+
+ function walkAst(node) {
+ for (var key in node) {
+ if (key === 'range')
+ continue;
+ var value = node[key];
+ if (Array.isArray(value)) {
+ for (var i = 0, l = value.length; i < l; i++)
+ walkAst(value[i]);
+ } else if (Base.isObject(value)) {
+ walkAst(value);
+ }
+ }
+ switch (node && node.type) {
+ case 'BinaryExpression':
+ if (node.operator in binaryOperators
+ && node.left.type !== 'Literal') {
+ var left = getCode(node.left),
+ right = getCode(node.right);
+ replaceCode(node, '_$_(' + left + ', "' + node.operator
+ + '", ' + right + ')');
+ }
+ break;
+ case 'AssignmentExpression':
+ if (/^.=$/.test(node.operator)
+ && node.left.type !== 'Literal') {
+ var left = getCode(node.left),
+ right = getCode(node.right);
+ replaceCode(node, left + ' = _$_(' + left + ', "'
+ + node.operator[0] + '", ' + right + ')');
+ }
+ break;
+ case 'UpdateExpression':
+ if (!node.prefix) {
+ var arg = getCode(node.argument);
+ replaceCode(node, arg + ' = _$_(' + arg + ', "'
+ + node.operator[0] + '", 1)');
+ }
+ break;
+ case 'UnaryExpression':
+ if (node.operator in unaryOperators
+ && node.argument.type !== 'Literal') {
+ var arg = getCode(node.argument);
+ replaceCode(node, '$_("' + node.operator + '", '
+ + arg + ')');
+ }
+ break;
+ }
+ }
+ walkAst(acorn.parse(code, { ranges: true }));
+ return code;
+ }
+
+ function evaluate(code, scope) {
+ paper = scope;
+ var view = scope.project && scope.project.view,
+ res;
+ with (scope) {
+ (function() {
+ var onActivate, onDeactivate, onEditOptions,
+ 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-ignore')) {
+ 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-ignore', 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));
+};