soundmap_theme/carnaval/js/paper.js

7842 lines
214 KiB
JavaScript
Raw Normal View History

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