paper.js/dist/paper.js
2011-06-27 15:28:11 +02:00

7091 lines
194 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/***
*
* Paper.js
*
* A JavaScript Vector Graphics Library, based on Scriptographer.org and
* designed to be largely API compatible.
* http://paperjs.org/
* http://scriptographer.org/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
* http://lehni.org/ & http://jonathanpuckey.com/
*
* All rights reserved.
*
***
*
* Bootstrap.js JavaScript Framework.
* http://bootstrapjs.org/
*
* Distributed under the MIT license.
*
* Copyright (c) 2006 - 2011 Juerg Lehni
* http://lehni.org/
*
***
*
* Parse-JS, A JavaScript tokenizer / parser / generator.
*
* Distributed under the BSD license.
*
* Copyright (c) 2010, Mihai Bazon <mihai.bazon@gmail.com>
* http://mihai.bazon.net/blog/
*
***/
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({
clone: function() {
return new this.constructor(this);
},
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);
}, {}, 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);
}
);
},
formatNumber: function(num) {
return (Math.round(num * 100000) / 100000).toString();
},
formatObject: function(obj) {
return '{ ' + Base.each(obj, function(value, key) {
this.push(key + ': ' + value);
}, []).join(', ') + ' }';
}
}
});
var PaperScope = this.PaperScope = Base.extend({
version: 0.1,
initialize: function(id) {
this.project = null;
this.projects = [];
this.view = null;
this.views = [];
this.tool = null;
this.tools = [];
this.id = id;
PaperScope._scopes[id] = this;
},
evaluate: function(code) {
var res = PaperScript.evaluate(code, this);
View.updateFocus();
return res;
},
install: function(scope) {
return Base.each(this, function(value, key) {
this[key] = value;
}, scope);
},
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: {},
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 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._transformPoint(this);
},
getDistance: function(point) {
point = Point.read(arguments);
var x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
},
getLength: function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
},
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) {
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.width == 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) {
var point = new LinkedSize(LinkedSize.dont);
point._width = width;
point._height = height;
point._owner = owner;
point._setter = setter;
return point;
}
}
});
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);
},
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);
},
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());
},
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(rect) {
if (rect.width !== undefined) {
return rect.x >= this.x && rect.y >= this.y
&& rect.x + rect.width <= this.x + this.width
&& rect.y + rect.height <= this.y + this.height;
} else {
var point = Point.read(arguments);
return point.x >= this.x && point.y >= this.y
&& point.x <= this.x + this.width
&& point.y <= 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);
},
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]());
};
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(m00, m10, m01, m11, m02, m12) {
var ok = true;
if (arguments.length == 6) {
this.set(m00, m10, m01, m11, m02, m12);
} else if (arguments.length == 1) {
if (m00 instanceof Matrix) {
this.set(m00._m00, m00._m10, m00._m01,
m00._m11, m00._m02, m00._m12);
} else if (Array.isArray(m00)) {
this.set.apply(this, m00);
} else {
ok = false;
}
} else if (arguments.length > 0) {
ok = false;
} else {
this._m00 = this._m11 = 1;
this._m10 = this._m01 = this._m02 = this._m12 = 0;
}
if (!ok)
throw new Error('Unsupported matrix parameters');
},
clone: function() {
return Matrix.create(this._m00, this._m10, this._m01,
this._m11, this._m02, this._m12);
},
set: function(m00, m10, m01, m11, m02, m12) {
this._m00 = m00;
this._m10 = m10;
this._m01 = m01;
this._m11 = m11;
this._m02 = m02;
this._m12 = m12;
return this;
},
scale: function(sx, sy , center) {
if (arguments.length < 2 || typeof sy === 'object') {
center = Point.read(arguments, 1);
sy = sx;
} else {
center = Point.read(arguments, 2);
}
if (center)
this.translate(center);
this._m00 *= sx;
this._m10 *= sx;
this._m01 *= sy;
this._m11 *= sy;
if (center)
this.translate(center.negate());
return this;
},
translate: function(point) {
point = Point.read(arguments);
var x = point.x, y = point.y;
this._m02 += x * this._m00 + y * this._m01;
this._m12 += x * this._m10 + y * this._m11;
return this;
},
rotate: function(angle, center) {
return this.concatenate(
Matrix.getRotateInstance.apply(Matrix, arguments));
},
shear: function(shx, shy, center) {
if (arguments.length < 2 || typeof shy === 'object') {
center = Point.read(arguments, 1);
sy = sx;
} else {
center = Point.read(arguments, 2);
}
if (center)
this.translate(center);
var m00 = this._m00;
var m10 = this._m10;
this._m00 += shy * this._m01;
this._m10 += shy * this._m11;
this._m01 += shx * m00;
this._m11 += shx * m10;
if (center)
this.translate(center.negate());
return this;
},
toString: function() {
var format = Base.formatNumber;
return '[[' + [format(this._m00), format(this._m01),
format(this._m02)].join(', ') + '], ['
+ [format(this._m10), format(this._m11),
format(this._m12)].join(', ') + ']]';
},
concatenate: function(mx) {
var m0 = this._m00,
m1 = this._m01;
this._m00 = mx._m00 * m0 + mx._m10 * m1;
this._m01 = mx._m01 * m0 + mx._m11 * m1;
this._m02 += mx._m02 * m0 + mx._m12 * m1;
m0 = this._m10;
m1 = this._m11;
this._m10 = mx._m00 * m0 + mx._m10 * m1;
this._m11 = mx._m01 * m0 + mx._m11 * m1;
this._m12 += mx._m02 * m0 + mx._m12 * m1;
return this;
},
preConcatenate: function(mx) {
var m0 = this._m00,
m1 = this._m10;
this._m00 = mx._m00 * m0 + mx._m01 * m1;
this._m10 = mx._m10 * m0 + mx._m11 * m1;
m0 = this._m01;
m1 = this._m11;
this._m01 = mx._m00 * m0 + mx._m01 * m1;
this._m11 = mx._m10 * m0 + mx._m11 * m1;
m0 = this._m02;
m1 = this._m12;
this._m02 = mx._m00 * m0 + mx._m01 * m1 + mx._m02;
this._m12 = mx._m10 * m0 + mx._m11 * m1 + mx._m12;
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._m00 + y * this._m01 + this._m02,
x * this._m10 + y * this._m11 + this._m12,
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._m00 + y * this._m01 + this._m02;
dst[j++] = x * this._m10 + y * this._m11 + this._m12;
}
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]);
},
getDeterminant: function() {
return this._m00 * this._m11 - this._m01 * this._m10;
},
getTranslation: function() {
return new Point(this._m02, this._m12);
},
getScaling: function() {
var sx = Math.sqrt(this._m00 * this._m00 + this._m10 * this._m10),
sy = Math.sqrt(this._m01 * this._m01 + this._m11 * this._m11);
return new Point(this._m00 < 0 ? -sx : sx, this._m01 < 0 ? -sy : sy);
},
getRotation: function() {
var angle1 = -Math.atan2(this._m01, this._m11),
angle2 = Math.atan2(this._m10, this._m00);
return Math.abs(angle1 - angle2) < Numerical.TOLERANCE
? angle1 * 180 / Math.PI : undefined;
},
isIdentity: function() {
return this._m00 == 1 && this._m10 == 0 && this._m01 == 0 &&
this._m11 == 1 && this._m02 == 0 && this._m12 == 0;
},
isInvertible: function() {
var det = this.getDeterminant();
return isFinite(det) && det != 0 && isFinite(this._m02)
&& isFinite(this._m12);
},
isSingular: function() {
return !this.isInvertible();
},
createInverse: function() {
var det = this.getDeterminant();
if (isFinite(det) && det != 0 && isFinite(this._m02)
&& isFinite(this._m12)) {
return Matrix.create(
this._m11 / det,
-this._m10 / det,
-this._m01 / det,
this._m00 / det,
(this._m01 * this._m12 - this._m11 * this._m02) / det,
(this._m10 * this._m02 - this._m00 * this._m12) / det);
}
return null;
},
createShiftless: function() {
return Matrix.create(this._m00, this._m10, this._m01, this._m11, 0, 0);
},
setToScale: function(sx, sy) {
return this.set(sx, 0, 0, sy, 0, 0);
},
setToTranslation: function(delta) {
delta = Point.read(arguments);
return this.set(1, 0, 0, 1, delta.x, delta.y);
},
setToShear: function(shx, shy) {
return this.set(1, shy, shx, 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._m00, this._m10, this._m01,
this._m11, this._m02, this._m12
);
return this;
},
statics: {
create: function(m00, m10, m01, m11, m02, m12) {
return new Matrix(Matrix.dont).set(m00, m10, m01, m11, m02, m12);
},
getScaleInstance: function(sx, sy) {
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(shx, shy, 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: '_m00',
scaleY: '_m11',
translateX: '_m02',
translateY: '_m12',
shearX: '_m01',
shearY: '_m10'
}, 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.TOLERANCE)
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 = Base.extend({
initialize: function() {
this._scope = paper;
this._index = this._scope.projects.push(this) - 1;
this._currentStyle = new PathStyle();
this._selectedItems = {};
this._selectedItemCount = 0;
this.activate();
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);
},
activate: function() {
if (this._scope) {
this._scope.project = this;
return true;
}
return false;
},
remove: function() {
if (this._scope) {
Base.splice(this._scope.projects, null, this._index, 1);
if (this._scope.project == this)
this._scope.project = null;
this._scope = null;
return true;
}
return false;
},
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);
},
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);
},
getDefinition: function() {
return this._definition;
},
setDefinition: function(item) {
this._definition = item;
item.remove();
item.setPosition(new Point());
},
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() {
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) {
if (this._project)
this._project._needsRedraw();
}
},
getId: function() {
if (this._id == null)
this._id = Item._id = (Item._id || 0) + 1;
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);
}
}, new function() {
return Base.each(['locked', 'visible', 'blendMode', 'opacity'],
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,
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,
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);
}
}
}
},
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', '_opacity', '_blendMode',
'_clipMask'];
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.setPosition(this.getPosition());
raster.scale(1 / scale);
return raster;
},
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() {
return this._getBounds(false);
},
getStrokeBounds: function() {
return this._getBounds(true);
},
_getBounds: function(includeStroke) {
var children = this._children;
if (!children || children.length == 0)
return new Rectangle();
var getBounds = includeStroke ? 'getStrokeBounds' : 'getBounds',
x1 = Infinity,
x2 = -Infinity,
y1 = x1,
y2 = x2;
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i];
if (child._visible) {
var rect = child[getBounds]();
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 includeStroke ? bounds : this._createBounds(bounds);
},
_createBounds: function(rect) {
return LinkedRectangle.create(this, 'setBounds',
rect.x, rect.y, rect.width, rect.height);
},
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);
},
scale: function(sx, sy , center) {
if (arguments.length < 2 || typeof sy === 'object') {
center = sy;
sy = sx;
}
return this.transform(new Matrix().scale(sx, sy,
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(shearX, shearY, center) {
if (arguments.length < 2 || typeof sy === 'object') {
center = shearY;
shearY = shearX;
}
return this.transform(new Matrix().shear(shearX, shearY,
center || this.getPosition()));
},
transform: function(matrix, flags) {
var bounds = this._bounds,
position = this._position;
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 = this._children && this._children.length; i < l; i++)
this._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);
},
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 Raster = this.Raster = Item.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);
},
_transform: function(matrix, flags) {
this.matrix.preConcatenate(matrix);
},
getBounds: function() {
if (!this._bounds)
this._bounds = this._createBounds(this.matrix._transformBounds(
new Rectangle(this._size).setCenter(0, 0)));
return this._bounds;
},
getStrokeBounds: function() {
return this.getBounds();
},
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 = Item.extend({
initialize: function(symbol, matrixOrOffset) {
this.base();
this.symbol = symbol instanceof Symbol ? symbol : new Symbol(symbol);
this.matrix = matrixOrOffset !== undefined
? matrixOrOffset instanceof Matrix
? matrixOrOffset
: new Matrix().translate(Point.read(arguments, 1))
: new Matrix();
},
clone: function() {
return this._clone(new PlacedSymbol(this.symbol, this.matrix.clone()));
},
_transform: function(matrix, flags) {
this.matrix.preConcatenate(matrix);
},
getBounds: function() {
if (!this._bounds)
this._bounds = this._createBounds(
this.symbol._definition.getStrokeBounds(this.matrix))
return this._bounds;
},
getStrokeBounds: function() {
return this.getBounds();
},
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();
}
}
});
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);
},
getHandleInIfSet: function() {
return this._handleIn._x == 0 && this._handleIn._y == 0
? null : this._handleIn;
},
getHandleOut: function() {
return this._handleOut;
},
setHandleOut: function(point) {
point = Point.read(arguments);
this._handleOut.set(point.x, point.y);
},
getHandleOutIfSet: function() {
return this._handleOut._x == 0 && this._handleOut._y == 0
? null : this._handleOut;
},
_isSelected: function(point) {
var state = this._selectionState;
return point == this._point ? !!(state & SelectionState.POINT)
: point == this._handleIn ? !!(state & SelectionState.HANDLE_IN)
: point == this._handleOut ? !!(state & SelectionState.HANDLE_OUT)
: false;
},
_setSelected: function(point, selected) {
var path = this._path,
selected = !!selected,
state = this._selectionState || 0,
selection = [
!!(state & SelectionState.POINT),
!!(state & SelectionState.HANDLE_IN),
!!(state & SelectionState.HANDLE_OUT)
];
if (point == this._point) {
if (selected) {
selection[1] = selection[2] = false;
} else {
var previous = this.getPrevious(),
next = this.getNext();
selection[1] = previous && (previous._point.isSelected()
|| previous._handleOut.isSelected());
selection[2] = next && (next._point.isSelected()
|| next._handleIn.isSelected());
}
selection[0] = selected;
} else {
var index = point == this._handleIn ? 1 : 2;
if (selection[index] != selected) {
if (selected)
selection[0] = false;
selection[index] = selected;
}
}
this._selectionState = (selection[0] ? SelectionState.POINT : 0)
| (selection[1] ? SelectionState.HANDLE_IN : 0)
| (selection[2] ? SelectionState.HANDLE_OUT : 0);
if (path && state != this._selectionState)
path._updateSelection(this, state, this._selectionState);
},
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 = matrix && this.getHandleInIfSet() || this._handleIn,
handleOut = matrix && this.getHandleOutIfSet() || this._handleOut,
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) {
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);
},
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 (pt.x !== undefined) {
x = pt.x;
y = pt.y;
selected = pt.selected;
} else {
x = pt[0];
y = pt[1];
}
point._x = x;
point._y = y;
point._owner = segment;
segment[key] = point;
if (selected)
point.setSelected(true);
return point;
}
}
});
var SelectionState = {
HANDLE_IN: 1,
HANDLE_OUT: 2,
POINT: 4
};
var Curve = this.Curve = Base.extend({
initialize: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
var count = arguments.length;
if (count == 0) {
this._segment1 = new Segment();
this._segment2 = new Segment();
} else if (count == 1) {
this._segment1 = new Segment(arg0.segment1);
this._segment2 = new Segment(arg0.segment2);
} else if (count == 2) {
this._segment1 = new Segment(arg0);
this._segment2 = new Segment(arg1);
} else if (count == 4) {
this._segment1 = new Segment(arg0, null, arg1);
this._segment2 = new Segment(arg3, arg2, null);
} else if (count == 8) {
var p1 = Point.create(arg0, arg1),
p2 = Point.create(arg6, arg7);
this._segment1 = new Segment(p1, null,
Point.create(arg2, arg3).subtract(p1));
this._segment2 = new Segment(p2,
Point.create(arg4, arg5).subtract(p2), null);
}
},
_changed: function() {
delete this._length;
},
getPoint1: function() {
return this._segment1._point;
},
setPoint1: function(point) {
point = Point.read(arguments);
this._segment1._point.set(point.x, point.y);
},
getPoint2: function() {
return this._segment2._point;
},
setPoint2: function(point) {
point = Point.read(arguments);
this._segment2._point.set(point.x, point.y);
},
getHandle1: function() {
return this._segment1._handleOut;
},
setHandle1: function(point) {
point = Point.read(arguments);
this._segment1._handleOut.set(point.x, point.y);
},
getHandle2: function() {
return this._segment2._handleIn;
},
setHandle2: function(point) {
point = Point.read(arguments);
this._segment2._handleIn.set(point.x, point.y);
},
getSegment1: function() {
return this._segment1;
},
getSegment2: function() {
return this._segment2;
},
getPath: function() {
return this._path;
},
getIndex: function() {
return this._segment1._index;
},
getNext: function() {
var curves = this._path && this._path._curves;
return curves && (curves[this._segment1._index + 1]
|| this._path._closed && curves[0]) || null;
},
getPrevious: function() {
var curves = this._path && this._path._curves;
return curves && (curves[this._segment1._index - 1]
|| this._path._closed && curves[curves.length - 1]) || null;
},
isSelected: function() {
return this.getHandle1().isSelected() && this.getHandle2().isSelected();
},
setSelected: function(selected) {
this.getHandle1().setSelected(selected);
this.getHandle2().setSelected(selected);
},
getValues: function() {
return Curve.getValues(this._segment1, this._segment2);
},
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 args = this.getValues();
if (!fullLength)
args.push(from, to);
var length = Curve.getLength.apply(Curve, args);
if (fullLength)
this._length = length;
return length;
},
getPart: function(from, to) {
var args = this.getValues();
args.push(from, to);
return new Curve(Curve.getPart.apply(Curve, args));
},
isLinear: function() {
return this._segment1._handleOut.isZero()
&& this._segment2._handleIn.isZero();
},
getParameter: function(length, start) {
var args = this.getValues();
args.push(length, start !== undefined ? start : length < 0 ? 1 : 0);
return Curve.getParameter.apply(Curve, args);
},
_evaluate: function(parameter, type) {
var args = this.getValues();
args.push(parameter, type);
return Curve.evaluate.apply(Curve, args);
},
getPoint: function(parameter) {
return this._evaluate(parameter, 0);
},
getTangent: function(parameter) {
return this._evaluate(parameter, 1);
},
getNormal: function(parameter) {
return this._evaluate(parameter, 2);
},
reverse: function() {
return new Curve(this._segment2.reverse(), this._segment1.reverse());
},
clone: function() {
return new Curve(this._segment1, this._segment2);
},
toString: function() {
var parts = [ 'point1: ' + this._segment1._point ];
if (!this._segment1._handleOut.isZero())
parts.push('handle1: ' + this._segment1._handleOut);
if (!this._segment2._handleIn.isZero())
parts.push('handle2: ' + this._segment2._handleIn);
parts.push('point2: ' + this._segment2._point);
return '{ ' + parts.join(', ') + ' }';
},
statics: {
create: function(path, segment1, segment2) {
var curve = new Curve(Curve.dont);
curve._path = path;
curve._segment1 = segment1;
curve._segment2 = segment2;
return curve;
},
getValues: function(segment1, segment2) {
var p1 = segment1._point,
h1 = segment1._handleOut,
h2 = segment2._handleIn,
p2 = segment2._point;
return [
p1._x, p1._y,
p1._x + h1._x, p1._y + h1._y,
p2._x + h2._x, p2._y + h2._y,
p2._x, p2._y
];
},
evaluate: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t, type) {
var x, y;
if (t == 0 || t == 1) {
var point;
switch (type) {
case 0:
x = t == 0 ? p1x : p2x;
y = t == 0 ? p1y : p2y;
break;
case 1:
case 2:
var px, py;
if (t == 0) {
if (c1x == p1x && c1y == p1y) {
if (c2x == p2x && c2y == p2y) {
px = p2x; py = p2y;
} else {
px = c2x; py = c2y;
}
} else {
px = c1x; py = c1y;
}
x = px - p1x;
y = py - p1y;
} else {
if (c2x == p2x && c2y == p2y) {
if (c1x == p1x && c1y == p1y) {
px = p1x; py = p1y;
} else {
px = c1x; py = c1y;
}
} else {
px = c2x; py = c2y;
}
x = p2x - px;
y = p2y - py;
}
break;
}
} else {
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(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
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]
];
},
getPart: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, from, to) {
var curve = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
if (from > 0) {
curve[8] = from;
curve = Curve.subdivide.apply(Curve, curve)[1];
}
if (to < 1) {
curve[8] = (to - from) / (1 - from);
curve = Curve.subdivide.apply(Curve, curve)[0];
}
return curve;
},
isSufficientlyFlat: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
var vx = (p2x - p1x) / 3,
vy = (p2y - p1y) / 3,
m1x = p1x + vx,
m1y = p1y + vy,
m2x = p2x - vx,
m2y = p2y - vy;
return Math.max(
Math.abs(m1x - c1x), Math.abs(m1y - c1y),
Math.abs(m2x - c1x), Math.abs(m1y - c1y)) < 1 / 2;
}
}
}, new function() {
function getLengthIntegrand(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
var 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(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, a, b) {
if (a === undefined)
a = 0;
if (b === undefined)
b = 1;
if (p1x == c1x && p1y == c1y && p2x == c2x && p2y == c2y) {
var dx = p2x - p1x,
dy = p2y - p1y;
return (b - a) * Math.sqrt(dx * dx + dy * dy);
}
var ds = getLengthIntegrand(
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
return Numerical.integrate(ds, a, b, getIterations(a, b));
},
getParameter: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
length, start) {
if (length == 0)
return start;
var forward = length > 0,
a = forward ? start : 0,
b = forward ? 1 : start,
length = Math.abs(length),
ds = getLengthIntegrand(
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y),
rangeLength = Numerical.integrate(ds, a, b,
getIterations(a, b));
if (length >= rangeLength)
return forward ? b : a;
var guess = length / rangeLength,
len = 0;
function f(t) {
var count = getIterations(start, t);
if (start < t) {
len += Numerical.integrate(ds, start, t, count);
} else {
len -= Numerical.integrate(ds, t, start, count);
}
start = t;
return len - length;
}
return Numerical.findRoot(f, ds,
forward ? a + guess : b - guess,
a, b, 16, Numerical.TOLERANCE);
}
};
});
CurveLocation = Base.extend({
initialize: function(curve, parameter, point) {
this._curve = curve;
this._parameter = parameter;
this._point = point;
},
getSegment: function() {
if (!this._segment) {
var 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;
},
getItem: 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._curve && this.getParameter();
return parameter != null ? this._curve.getLength(0, parameter) : null;
},
getParameter: function() {
if (this._parameter == null && this._point)
this._parameter = this._curve.getParameter(this._point);
return this._parameter;
},
getPoint: function() {
if (!this._point && this._curve) {
var parameter = this.getParameter();
if (parameter != null)
this._point = this._curve.getPoint(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);
},
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));
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._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 + step / 2;
var segments = [];
while (pos <= end) {
segments.push(new Segment(flattener.evaluate(pos, 0)));
pos += step;
}
this.setSegments(segments);
},
simplify: function(tolerance) {
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;
},
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.getParameter(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();
}
}, 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 b2ac = b * b - 4 * a * c;
if (b2ac < 0)
continue;
var sqrt = Math.sqrt(b2ac),
f = 1 / (a * -2),
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.length == 0;
if (!useCache || !this._bounds) {
var bounds = this._createBounds(getBounds(this, arguments[0]));
if (useCache)
this._bounds = bounds;
return bounds;
}
return this._bounds;
},
getStrokeBounds: function() {
if (!this._style._strokeColor || !this._style._strokeWidth)
return this.getBounds.apply(this, arguments);
var useCache = arguments.length == 0;
if (this._strokeBounds && useCache)
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) {
var handleIn = segment.getHandleInIfSet(),
handleOut = segment.getHandleOutIfSet();
if (join === 'round' || handleIn && handleOut) {
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;
},
getControlBounds: function() {
}
};
});
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;
for (var i = 0, l = items.length; i < l; i++) {
var path = items[i];
if (path._clockwise === undefined)
path.setClockwise(i < l - 1);
this.addChild(path);
}
},
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 firstChild = this._children[0];
ctx.beginPath();
param.compound = true;
for (var i = 0, l = this._children.length; 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;
});
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 path = new Path(),
corners = ['getBottomLeft', 'getTopLeft', 'getTopRight',
'getBottomRight'],
segments = new Array(4);
for (var i = 0; i < 4; i++)
segments[i] = new Segment(rect[corners[i]]());
path._add(segments);
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().divide(2));
var path = new Path(),
uSize = size.multiply(kappa * 2),
bl = rect.getBottomLeft(),
tl = rect.getTopLeft(),
tr = rect.getTopRight(),
br = rect.getBottomRight();
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(),
topLeft = rect.getTopLeft(),
size = new Size(rect.width, rect.height),
segments = new Array(4);
for (var i = 0; i < 4; i++) {
var segment = ovalSegments[i];
segments[i] = new Segment(
segment._point.multiply(size).add(topLeft),
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),
new Size(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 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.isSufficientlyFlat.apply(Curve, curve)) {
var curves = Curve.subdivide.apply(Curve, 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
});
}
}
},
getParameter: 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.getParameter(offset);
return Curve.evaluate.apply(Curve,
this.curves[param.index].concat([param.value, type]));
},
drawPart: function(ctx, from, to) {
from = this.getParameter(from);
to = this.getParameter(to);
for (var i = from.index; i <= to.index; i++) {
var curve = Curve.getPart.apply(Curve, this.curves[i].concat(
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.TOLERANCE,
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();
var point = Point.read(arguments);
this._point = LinkedPoint.create(this, 'setPoint', point.x, point.y);
this._matrix = new Matrix().translate(point);
},
clone: function() {
var copy = this._clone(new PointText(this._point));
copy._matrix.initialize(this._matrix);
return copy;
},
getPoint: function() {
return this._point;
},
setPoint: function(point) {
this._transform(new Matrix().translate(
Point.read(arguments).subtract(this._point)));
},
getPosition: function() {
return this._point;
},
setPosition: function(point) {
this.setPoint.apply(this, arguments);
},
_transform: function(matrix, flags) {
this._matrix.preConcatenate(matrix);
matrix._transformPoint(this._point, this._point, true);
},
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']
};
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,
s = max == 0 ? 0 : delta / max,
v = max;
if (delta == 0) {
h = 0;
} else {
switch (max) {
case r: h = (g - b) / delta + (g < b ? 6 : 0); break;
case g: h = (b - r) / delta + 2; break;
case b: h = (r - g) / delta + 4; break;
}
h /= 6;
}
return new HSBColor(h * 360, 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-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);
},
'hsb-gray': function(color) {
return converters['rgb-gray'](converters['hsb-rgb'](color));
},
'gray-hsb': function(color) {
return new HSBColor(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.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) {
return this._colorType == type
? this.clone()
: converters[this._colorType + '-' + type](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 = Color.extend({
_colorType: 'rgb'
});
var HSBColor = this.HSBColor = Color.extend({
_colorType: 'hsb'
});
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 = new function() {
function cumulateOffset(el, name, parent, test) {
var left = name + 'Left',
top = name + 'Top',
x = 0,
y = 0,
style;
while (el && el.style && (!test || !test.test(
style = DomElement.getComputedStyle(el, 'position')))) {
x += el[left] || 0;
y += el[top] || 0;
el = el[parent];
}
return {
offset: Point.create(x, y),
element: el,
style: style
};
}
function getScrollOffset(el, test) {
return cumulateOffset(el, 'scroll', 'parentNode', test).offset;
}
return {
getOffset: function(el, positioned, viewport) {
var res = cumulateOffset(el, 'offset', 'offsetParent',
positioned ? /^(relative|absolute|fixed)$/ : /^fixed$/);
if (res.style == 'fixed' && !viewport)
return res.offset.add(getScrollOffset(res.element));
return viewport
? res.offset.subtract(getScrollOffset(el, /^fixed$/))
: res.offset;
},
getSize: function(el) {
return Size.create(el.offsetWidth, el.offsetHeight);
},
getBounds: function(el, positioned, viewport) {
return new Rectangle(this.getOffset(el, positioned, viewport),
this.getSize(el));
},
isInvisible: function(el) {
return this.getSize(el).equals([0, 0]);
},
isVisible: function(el) {
return !this.isInvisible(el)
&& new Rectangle([0, 0], this.getViewportSize(el))
.intersects(this.getBounds(el, false, true));
},
getViewport: function(doc) {
return doc.defaultView || doc.parentWindow;
},
getViewportSize: function(el) {
var doc = el.ownerDocument,
view = this.getViewport(doc),
body = doc.getElementsByTagName(
doc.compatMode === 'CSS1Compat' ? 'html' : 'body')[0];
return Size.create(
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), true));
},
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) {
timer = window.setInterval(function() {
for (var i = callbacks.length - 1; i >= 0; i--) {
var entry = callbacks[i],
func = entry[0],
element = entry[1];
if (!element || (element.getAttribute('keepalive') == 'true'
|| focused) && DomElement.isVisible(element)) {
callbacks.splice(i, 1);
func(Date.now());
}
}
}, 1000 / 60);
}
};
};
var View = this.View = Base.extend({
initialize: function(canvas) {
this._scope = paper;
this._index = this._scope.views.push(this) - 1;
var size;
if (canvas && canvas instanceof HTMLCanvasElement) {
this._canvas = canvas;
if (canvas.attributes.resize) {
var offset = DomElement.getOffset(canvas, false, true),
that = this;
size = DomElement.getViewportSize(canvas).subtract(offset);
canvas.width = size.width;
canvas.height = size.height;
DomEvent.add(window, {
resize: function(event) {
if (!DomElement.isInvisible(canvas))
offset = DomElement.getOffset(canvas, false, true);
that.setViewSize(DomElement.getViewportSize(canvas)
.subtract(offset));
if (that._onFrameCallback) {
that._onFrameCallback(0, true);
} else {
that.draw(true);
}
}
});
} else {
size = DomElement.isInvisible(canvas)
? Size.create(parseInt(canvas.getAttribute('width')),
parseInt(canvas.getAttribute('height')))
: DomElement.getSize(canvas);
}
if (canvas.attributes.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;
},
getCanvas: function() {
return this._canvas;
},
getViewSize: function() {
return this._viewSize;
},
setViewSize: function(size) {
size = Size.read(arguments);
var delta = size.subtract(this._viewSize);
this._canvas.width = size.width;
this._canvas.height = size.height;
if (this.onResize) {
this.onResize({
size: size,
delta: delta
});
}
this._viewSize.set(size.width, size.height, true);
this._bounds = null;
},
getBounds: function() {
if (!this._bounds)
this._bounds = this._matrix._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()));
},
_transform: function(matrix, flags) {
this._matrix.preConcatenate(matrix);
this._bounds = null;
this._inverse = null;
},
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;
},
activate: function() {
this._scope.view = this;
},
remove: function() {
if (this._index == null)
return false;
if (View._focused == this)
View._focused = null;
delete View._views[this._id];
Base.splice(this._scope.views, null, this._index, 1);
DomEvent.remove(this._canvas, this._events);
this._scope = this._canvas = this._events = this._onFrame = null;
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({
delta: delta,
time: time += delta,
count: count++
});
before = now;
that.draw(true);
};
if (!requested)
this._onFrameCallback();
},
onResize: null
}, 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: {
_views: {},
_id: 0,
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'
},
modifiers = {
shift: false,
control: false,
option: false,
command: false,
capsLock: false,
toString: function() {
return Base.formatObject(this);
}
},
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 (modifiers[name = Base.camelize(key)] !== undefined) {
modifiers[name] = true;
} else {
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 && modifiers[name = Base.camelize(key)] !== undefined) {
modifiers[name] = false;
} else 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;
},
toString: function() {
return '{ type: ' + this.type
+ ', point: ' + this.getPoint()
+ ', count: ' + this.getCount()
+ ', modifiers: ' + this.getModifiers()
+ ' }';
}
});
var Tool = this.Tool = Base.extend({
initialize: function(handlers, scope) {
this._scope = scope;
this._firstMove = true;
this._count = 0;
this._downCount = 0;
for (var i in handlers)
this[i] = handlers[i];
},
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]
],
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]
];
return {
TOLERANCE: 10e-6,
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, tol) {
for (var i = 0; i < n; i++) {
var fx = f(x),
dx = fx / df(x);
if (Math.abs(dx) < tol)
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;
}
}
}
};
};
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 S(a,b,c){var d=[];for(var e=0;e<a.length;++e)d.push(b.call(c,a[e],e));return d}function R(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function Q(c){return/^[a-z_$][a-z0-9_$]*$/i.test(c)&&c!="this"&&!R(d,c)&&!R(b,c)&&!R(a,c)}function P(a,b){var c={};a===!0&&(a={});for(var d in b)R(b,d)&&(c[d]=a&&R(a,d)?a[d]:b[d]);return c}function O(a,b){for(var c=b.length;--c>=0;)if(b[c]===a)return!0;return!1}function N(a){return a.split("")}function M(a,b){return Array.prototype.slice.call(a,b==null?0:b)}function L(a){var b={};for(var c=0;c<a.length;++c)b[a[c]]=!0;return b}function K(a){a instanceof Function&&(a=a());for(var b=1,c=arguments.length;--c>0;++b)arguments[b]();return a}function J(a){var b=M(arguments,1);return function(){return a.apply(this,b.concat(M(arguments)))}}function I(a,b){function y(a){var b=a[0],c=p[b];if(!c)throw new Error("Can't find generator for \""+b+'"');x.push(a);var d=c.apply(b,a.slice(1));x.pop();return d}function w(a){var b=a[0],c=a[1];c!=null&&(b=h([b,"=",y(c)]));return b}function v(a){if(!a)return";";if(a.length==0)return"{}";return"{"+d+g(function(){return t(a).join(d)})+d+f("}")}function u(a){var c=a.length;if(c==0)return"{}";return"{"+d+S(a,function(a,e){var i=a[1].length>0,j=g(function(){return f(a[0]?h(["case",y(a[0])+":"]):"default:")},.5)+(i?d+g(function(){return t(a[1]).join(d)}):"");!b&&i&&e<c-1&&(j+=";");return j}).join(d)+d+f("}")}function t(a){for(var c=[],d=a.length-1,e=0;e<=d;++e){var g=a[e],h=y(g);h!=";"&&(!b&&e==d&&(g[0]=="while"&&F(g[2])||O(g[0],["for","for-in"])&&F(g[4])||g[0]=="if"&&F(g[2])&&!g[3]||g[0]=="if"&&g[3]&&F(g[3])?h=h.replace(/;*\s*$/,";"):h=h.replace(/;+\s*$/,"")),c.push(h))}return S(c,f)}function s(a){return a.toString()}function r(a,b,c,d){var e=d||"function";a&&(e+=" "+s(a)),e+="("+j(S(b,s))+")";return h([e,v(c)])}function q(a){if(a[0]=="do")return y(["block",[a]]);var b=a;for(;;){var c=b[0];if(c=="if"){if(!b[3])return y(["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 y(a)}function o(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 l(c)}function m(a){if(a[0]=="function"){var b=M(x),c=b.pop(),d=b.pop();while(d){if(d[0]=="stat")return!0;if(d[0]=="seq"&&d[1]===c||d[0]=="call"&&d[1]===c||d[0]=="binary"&&d[2]===c)c=d,d=b.pop();else return!1}}return!R(G,a[0])}function l(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 l([a[0],l(a.slice(1))])}function k(a){var b=y(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 j(a){return a.join(","+e)}function h(a){if(b)return a.join(" ");var c=[];for(var d=0;d<a.length;++d){var e=a[d+1];c.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()))&&c.push(" ")}return c.join("")}function g(a,b){b==null&&(b=1),c+=b;try{return a.apply(null,M(arguments,1))}finally{c-=b}}function f(a){a==null&&(a=""),b&&(a=Array(b.indent_start+c*b.indent_level).join(" ")+a);return a}b&&(b=P(b,{indent_start:0,indent_level:4,quote_keys:!1,space_colon:!1}));var c=0,d=b?"\n":"",e=b?" ":"",p={string:H,num:o,name:s,toplevel:function(a){return t(a).join(d+d)},block:v,"var":function(a){return"var "+j(S(a,w))+";"},"const":function(a){return"const "+j(S(a,w))+";"},"try":function(a,b,c){var d=["try",v(a)];b&&d.push("catch","("+b[0]+")",v(b[1])),c&&d.push("finally",v(c));return h(d)},"throw":function(a){return h(["throw",y(a)])+";"},"new":function(a,b){b=b.length>0?"("+j(S(b,y))+")":"";return h(["new",k(a,"seq","binary","conditional","assign",function(a){var b=E(),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 h(["switch","("+y(a)+")",u(b)])},"break":function(a){var b="break";a!=null&&(b+=" "+s(a));return b+";"},"continue":function(a){var b="continue";a!=null&&(b+=" "+s(a));return b+";"},conditional:function(a,b,c){return h([k(a,"assign","seq","conditional"),"?",k(b,"seq"),":",k(c,"seq")])},assign:function(a,b,c){a&&a!==!0?a+="=":a="=";return h([y(b),a,k(c,"seq")])},dot:function(a){var b=y(a),c=1;a[0]=="num"?b+=".":m(a)&&(b="("+b+")");while(c<arguments.length)b+="."+s(arguments[c++]);return b},call:function(a,b){var c=y(a);m(a)&&(c="("+c+")");return c+"("+j(S(b,function(a){return k(a,"seq")}))+")"},"function":r,defun:r,"if":function(a,b,c){var d=["if","("+y(a)+")",c?q(b):y(b)];c&&d.push("else",y(c));return h(d)},"for":function(a,b,c,d){var f=["for"];a=(a!=null?y(a):"").replace(/;*\s*$/,";"+e),b=(b!=null?y(b):"").replace(/;*\s*$/,";"+e),c=(c!=null?y(c):"").replace(/;*\s*$/,"");var g=a+b+c;g=="; ; "&&(g=";;"),f.push("("+g+")",y(d));return h(f)},"for-in":function(a,b,c,d){var e=h(["for","("]);a&&(e+="var "),e+=h([s(b)+" in "+y(c)+")",y(d)]);return e},"while":function(a,b){return h(["while","("+y(a)+")",y(b)])},"do":function(a,b){return h(["do",y(b),"while","("+y(a)+")"])+";"},"return":function(a){var b=["return"];a!=null&&b.push(y(a));return h(b)+";"},binary:function(a,b,c){var d=y(b),e=y(c);if(O(b[0],["assign","conditional","seq"])||b[0]=="binary"&&z[a]>z[b[1]])d="("+d+")";if(O(c[0],["assign","conditional","seq"])||c[0]=="binary"&&z[a]>=z[c[1]]&&(c[1]!=a||!O(a,["&&","||","*"])))e="("+e+")";return h([d,a,e])},"unary-prefix":function(a,b){var c=y(b);b[0]=="num"||b[0]=="unary-prefix"&&!R(i,a+b[1])||!m(b)||(c="("+c+")");return a+(n(a.charAt(0))?" ":"")+c},"unary-postfix":function(a,b){var c=y(b);b[0]=="num"||b[0]=="unary-postfix"&&!R(i,a+b[1])||!m(b)||(c="("+c+")");return c+a},sub:function(a,b){var c=y(a);m(a)&&(c="("+c+")");return c+"["+y(b)+"]"},object:function(a){if(a.length==0)return"{}";return"{"+d+g(function(){return S(a,function(a){if(a.length==3)return f(r(a[0],a[1][2],a[1][3],a[2]));var c=a[0],d=y(a[1]);b&&b.quote_keys?c=H(c):(typeof c=="number"||!b&&+c+""==c)&&parseFloat(c)>=0?c=o(+c):Q(c)||(c=H(c));return f(h(b&&b.space_colon?[c,":",d]:[c+":",d]))}).join(","+d)})+d+f("}")},regexp:function(a,b){return"/"+a+"/"+b},array:function(a){if(a.length==0)return"[]";return h(["[",j(S(a,function(a){if(!b&&a[0]=="atom"&&a[1]=="undefined")return"";return k(a,"seq")})),"]"])},stat:function(a){return y(a).replace(/;*\s*$/,";")},seq:function(){return j(S(M(arguments),y))},label:function(a,b){return h([s(a),":",y(b)])},"with":function(a,b){return h(["with","("+y(a)+")",y(b)])},atom:function(a){return s(a)}},x=[];return y(a)}function H(a){var b=0,c=0;a=a.replace(/[\\\b\f\n\r\t\x22\x27]/g,function(a){switch(a){case"\\":return"\\\\";case"\b":return"\\b";case"\f":return"\\f";case"\n":return"\\n";case"\r":return"\\r";case"\t":return"\\t";case'"':++b;return'"';case"'":++c;return"'"}return a});return b>c?"'"+a.replace(/\x27/g,"\\'")+"'":'"'+a.replace(/\x22/g,'\\"')+'"'}function F(a){return!a||a[0]=="block"&&(!a[1]||a[1].length==0)}function E(){function f(a,b){var d={},e;for(e in a)R(a,e)&&(d[e]=c[e],c[e]=a[e]);var f=b();for(e in d)R(d,e)&&(d[e]?c[e]=d[e]:delete c[e]);return f}function e(a){if(a==null)return null;try{d.push(a);var e=a[0],f=c[e];if(f){var g=f.apply(a,a.slice(1));if(g!=null)return g}f=b[e];return f.apply(a,a.slice(1))}finally{d.pop()}}function a(a){return[this[0],S(a,function(a){var b=[a[0]];a.length>1&&(b[1]=e(a[1]));return b})]}var b={string:function(a){return[this[0],a]},num:function(a){return[this[0],a]},name:function(a){return[this[0],a]},toplevel:function(a){return[this[0],S(a,e)]},block:function(a){var b=[this[0]];a!=null&&b.push(S(a,e));return b},"var":a,"const":a,"try":function(a,b,c){return[this[0],S(a,e),b!=null?[b[0],S(b[1],e)]:null,c!=null?S(c,e):null]},"throw":function(a){return[this[0],e(a)]},"new":function(a,b){return[this[0],e(a),S(b,e)]},"switch":function(a,b){return[this[0],e(a),S(b,function(a){return[a[0]?e(a[0]):null,S(a[1],e)]})]},"break":function(a){return[this[0],a]},"continue":function(a){return[this[0],a]},conditional:function(a,b,c){return[this[0],e(a),e(b),e(c)]},assign:function(a,b,c){return[this[0],a,e(b),e(c)]},dot:function(a){return[this[0],e(a)].concat(M(arguments,1))},call:function(a,b){return[this[0],e(a),S(b,e)]},"function":function(a,b,c){return[this[0],a,b.slice(),S(c,e)]},defun:function(a,b,c){return[this[0],a,b.slice(),S(c,e)]},"if":function(a,b,c){return[this[0],e(a),e(b),e(c)]},"for":function(a,b,c,d){return[this[0],e(a),e(b),e(c),e(d)]},"for-in":function(a,b,c,d){return[this[0],a,b,e(c),e(d)]},"while":function(a,b){return[this[0],e(a),e(b)]},"do":function(a,b){return[this[0],e(a),e(b)]},"return":function(a){return[this[0],e(a)]},binary:function(a,b,c){return[this[0],a,e(b),e(c)]},"unary-prefix":function(a,b){return[this[0],a,e(b)]},"unary-postfix":function(a,b){return[this[0],a,e(b)]},sub:function(a,b){return[this[0],e(a),e(b)]},object:function(a){return[this[0],S(a,function(a){return a.length==2?[a[0],e(a[1])]:[a[0],e(a[1]),a[2]]})]},regexp:function(a,b){return[this[0],a,b]},array:function(a){return[this[0],S(a,e)]},stat:function(a){return[this[0],e(a)]},seq:function(){return[this[0]].concat(S(M(arguments),e))},label:function(a,b){return[this[0],a,e(b)]},"with":function(a,b){return[this[0],e(a),e(b)]},atom:function(a){return[this[0],a]}},c={},d=[];return{walk:e,with_walkers:f,parent:function(){return d[d.length-2]},stack:function(){return d}}}function D(a,b,c){function bj(a){try{++d.in_loop;return a()}finally{--d.in_loop}}function bi(a){arguments.length==0&&(a=!0);var b=bh();if(a&&e("punc",",")){g();return p("seq",b,bi())}return b}function bh(){var a=bf(),b=d.token.value;if(e("operator")&&R(y,b)){if(bg(a)){g();return p("assign",y[b],a,bh())}i("Invalid assignment")}return a}function bg(a){switch(a[0]){case"dot":case"sub":return!0;case"name":return a[1]!="this"}}function bf(){var a=be();if(e("operator","?")){g();var b=bi(!1);m(":");return p("conditional",a,b,bi(!1))}return a}function be(){return bd(X(!0),0)}function bd(a,b){var c=e("operator")?d.token.value:null,f=c!=null?z[c]:null;if(f!=null&&f>b){g();var h=bd(X(!0),f);return bd(p("binary",c,a,h),b)}return a}function bc(a,b,c){(b=="++"||b=="--")&&!bg(c)&&i("Invalid use of "+b+" operator");return p(a,b,c)}function bb(a,b){if(e("punc",".")){g();return bb(p("dot",a,ba()),b)}if(e("punc","[")){g();return bb(p("sub",a,K(bi,J(m,"]"))),b)}if(b&&e("punc","(")){g();return bb(p("call",a,Y(")")),!0)}if(b&&e("operator")&&R(x,d.token.value))return K(J(bc,"unary-postfix",d.token.value,a),g);return a}function ba(){switch(d.token.type){case"name":case"operator":case"keyword":case"atom":return K(d.token.value,g);default:k()}}function _(){switch(d.token.type){case"num":case"string":return K(d.token.value,g)}return ba()}function $(){var a=!0,c=[];while(!e("punc","}")){a?a=!1:m(",");if(!b&&e("punc","}"))break;var f=d.token.type,h=_();f!="name"||h!="get"&&h!="set"||!!e("punc",":")?(m(":"),c.push([h,bi(!1)])):c.push([ba(),I(!1),h])}g();return p("object",c)}function Z(){return p("array",Y("]",!b,!0))}function Y(a,b,c){var d=!0,f=[];while(!e("punc",a)){d?d=!1:m(",");if(b&&e("punc",a))break;e("punc",",")&&c?f.push(["atom","undefined"]):f.push(bi(!1))}g();return f}function X(a){if(e("operator","new")){g();return W()}if(e("operator")&&R(w,d.token.value))return bc("unary-prefix",K(d.token.value,g),X(a));if(e("punc")){switch(d.token.value){case"(":g();return bb(K(bi,J(m,")")),a);case"[":g();return bb(Z(),a);case"{":g();return bb($(),a)}k()}if(e("keyword","function")){g();return bb(I(!1),a)}if(R(B,d.token.type)){var b=d.token.type=="regexp"?p("regexp",d.token.value[0],d.token.value[1]):p(d.token.type,d.token.value);return bb(K(b,g),a)}k()}function W(){var a=X(!1),b;e("punc","(")?(g(),b=Y(")")):b=[];return bb(p("new",a,b),!0)}function V(){return p("const",T())}function U(){return p("var",T())}function T(){var a=[];for(;;){e("name")||k();var b=d.token.value;g(),e("operator","=")?(g(),a.push([b,bi(!1)])):a.push([b]);if(!e("punc",","))break;g()}return a}function S(){var a=P(),b,c;if(e("keyword","catch")){g(),m("("),e("name")||i("Name expected");var f=d.token.value;g(),m(")"),b=[f,P()]}e("keyword","finally")&&(g(),c=P()),!b&&!c&&i("Missing catch/finally blocks");return p("try",a,b,c)}function P(){m("{");var a=[];while(!e("punc","}"))e("eof")&&k(),a.push(u());g();return a}function N(){var a=q(),b=u(),c;e("keyword","else")&&(g(),c=u());return p("if",a,b,c)}function L(a){var b=e("name")?K(d.token.value,g):null;a&&!b&&k(),m("(");return p(a?"defun":"function",b,function(a,b){while(!e("punc",")"))a?a=!1:m(","),e("name")||k(),b.push(d.token.value),g();g();return b}(!0,[]),function(){++d.in_function;var a=d.in_loop;d.in_loop=0;var b=P();--d.in_function,d.in_loop=a;return b}())}function H(){m("(");var a=e("keyword","var");a&&g();if(e("name")&&t(f(),"operator","in")){var b=d.token.value;g(),g();var c=bi();m(")");return p("for-in",a,b,c,bj(u))}var h=e("punc",";")?null:a?U():bi();m(";");var i=e("punc",";")?null:bi();m(";");var j=e("punc",")")?null:bi();m(")");return p("for",h,i,j,bj(u))}function G(a){var b=e("name")?d.token.value:null;b!=null?(g(),O(b,d.labels)||i("Label "+b+" without matching loop or statement")):d.in_loop==0&&i(a+" not inside a loop or switch"),o();return p(a,b)}function F(){return p("stat",K(bi,o))}function E(a){d.labels.push(a);var c=d.token,e=u();b&&!R(A,e[0])&&k(c),d.labels.pop();return p("label",a,e)}function D(){e("operator","/")&&(d.peeked=null,d.token=d.input(!0));switch(d.token.type){case"num":case"string":case"regexp":case"operator":case"atom":return F();case"name":return t(f(),"punc",":")?E(K(d.token.value,g,g)):F();case"punc":switch(d.token.value){case"{":return p("block",P());case"[":case"(":return F();case";":g();return p("block");default:k()};case"keyword":switch(K(d.token.value,g)){case"break":return G("break");case"continue":return G("continue");case"debugger":o();return p("debugger");case"do":return function(a){l("keyword","while");return p("do",K(q,o),a)}(bj(u));case"for":return H();case"function":return I(!0);case"if":return N();case"return":d.in_function==0&&i("'return' outside of function");return p("return",e("punc",";")?(g(),null):n()?null:K(bi,o));case"switch":return p("switch",q(),Q());case"throw":return p("throw",K(bi,o));case"try":return S();case"var":return K(U,o);case"const":return K(V,o);case"while":return p("while",q(),bj(u));case"with":return p("with",q(),u());default:k()}}}function r(a,b,c){return a instanceof C?a:new C(a,b,c)}function q(){m("(");var a=bi();m(")");return a}function p(){return M(arguments)}function o(){e("punc",";")?g():n()||k()}function n(){return!b&&(d.token.nlb||e("eof")||e("punc","}"))}function m(a){return l("punc",a)}function l(a,b){if(e(a,b))return g();j(d.token,"Unexpected token "+d.token.type+", expected "+a)}function k(a){a==null&&(a=d.token),j(a,"Unexpected token: "+a.type+" ("+a.value+")")}function j(a,b){i(b,a.line,a.col)}function i(a,b,c,e){var f=d.input.context();s(a,b!=null?b:f.tokline,c!=null?c:f.tokcol,e!=null?e:f.tokpos)}function h(){return d.prev}function g(){d.prev=d.token,d.peeked?(d.token=d.peeked,d.peeked=null):d.token=d.input();return d.token}function f(){return d.peeked||(d.peeked=d.input())}function e(a,b){return t(d.token,a,b)}var d={input:typeof a=="string"?v(a,!0):a,token:null,prev:null,peeked:null,in_function:0,in_loop:0,labels:[]};d.token=g();var u=c?function(){var a=d.token,b=D.apply(this,arguments);b[0]=r(b[0],a,h());return b}:D,I=c?function(){var a=h(),b=L.apply(this,arguments);b[0]=r(b[0],a,h());return b}:L,Q=J(bj,function(){m("{");var a=[],b=null;while(!e("punc","}"))e("eof")&&k(),e("keyword","case")?(g(),b=[],a.push([bi(),b]),m(":")):e("keyword","default")?(g(),m(":"),b=[],a.push([null,b])):(b||k(),b.push(u()));g();return a});return p("toplevel",function(a){while(!e("eof"))a.push(u());return a}([]))}function C(a,b,c){this.name=a,this.start=b,this.end=c}function v(b){function N(a){if(a)return H();y(),v();var b=g();if(!b)return w("eof");if(p(b))return B();if(b=='"'||b=="'")return E();if(R(l,b))return w("punc",h());if(b==".")return K();if(b=="/")return J();if(R(e,b))return I();if(o(b))return L();A("Unexpected character '"+b+"'")}function M(a,b){try{return b()}catch(c){if(c===u)A(a);else throw c}}function L(){var b=z(o);return R(a,b)?R(i,b)?w("operator",b):R(d,b)?w("atom",b):w("keyword",b):w("name",b)}function K(){h();return p(g())?B("."):w("punc",".")}function J(){h();var a=f.regex_allowed;switch(g()){case"/":f.comments_before.push(F()),f.regex_allowed=a;return N();case"*":f.comments_before.push(G()),f.regex_allowed=a;return N()}return f.regex_allowed?H():I("/")}function I(a){function b(a){if(!g())return a;var c=a+g();if(R(i,c)){h();return b(c)}return a}return w("operator",b(a||h()))}function H(){return M("Unterminated regular expression",function(){var a=!1,b="",c,d=!1;while(c=h(!0))if(a)b+="\\"+c,a=!1;else if(c=="[")d=!0,b+=c;else if(c=="]"&&d)d=!1,b+=c;else{if(c=="/"&&!d)break;c=="\\"?a=!0:b+=c}var e=z(function(a){return R(m,a)});return w("regexp",[b,e])})}function G(){h();return M("Unterminated multiline comment",function(){var a=t("*/",!0),b=f.text.substring(f.pos,a),c=w("comment2",b,!0);f.pos=a+2,f.line+=b.split("\n").length-1,f.newline_before=b.indexOf("\n")>=0;return c})}function F(){h();var a=t("\n"),b;a==-1?(b=f.text.substr(f.pos),f.pos=f.text.length):(b=f.text.substring(f.pos,a),f.pos=a);return w("comment1",b,!0)}function E(){return M("Unterminated string constant",function(){var a=h(),b="";for(;;){var c=h(!0);if(c=="\\")c=C();else if(c==a)break;b+=c}return w("string",b)})}function D(a){var b=0;for(;a>0;--a){var c=parseInt(h(!0),16);isNaN(c)&&A("Invalid hex-character pattern in string"),b=b<<4|c}return b}function C(){var a=h(!0);switch(a){case"n":return"\n";case"r":return"\r";case"t":return"\t";case"b":return"\b";case"v":return" ";case"f":return"\f";case"0":return"";case"x":return String.fromCharCode(D(2));case"u":return String.fromCharCode(D(4));default:return a}}function B(a){var b=!1,c=!1,d=!1,e=a==".",f=z(function(f,g){if(f=="x"||f=="X"){if(d)return!1;return d=!0}if(!d&&(f=="E"||f=="e")){if(b)return!1;return b=c=!0}if(f=="-"){if(c||g==0&&!a)return!0;return!1}if(f=="+")return c;c=!1;if(f=="."){if(!e)return e=!0;return!1}return n(f)});a&&(f=a+f);var g=q(f);if(!isNaN(g))return w("num",g);A("Invalid syntax: "+f)}function A(a){s(a,f.tokline,f.tokcol,f.tokpos)}function z(a){var b="",c=g(),d=0;while(c&&a(c,d++))b+=h(),c=g();return b}function y(){while(R(j,g()))h()}function w(a,b,d){f.regex_allowed=a=="operator"&&!R(x,b)||a=="keyword"&&R(c,b)||a=="punc"&&R(k,b);var e={type:a,value:b,line:f.tokline,col:f.tokcol,pos:f.tokpos,nlb:f.newline_before};d||(e.comments_before=f.comments_before,f.comments_before=[]),f.newline_before=!1;return e}function v(){f.tokline=f.line,f.tokcol=f.col,f.tokpos=f.pos}function t(a,b){var c=f.text.indexOf(a,f.pos);if(b&&c==-1)throw u;return c}function r(){return!f.peek()}function h(a){var b=f.text.charAt(f.pos++);if(a&&!b)throw u;b=="\n"?(f.newline_before=!0,++f.line,f.col=0):++f.col;return b}function g(){return f.text.charAt(f.pos)}var f={text:b.replace(/\r\n?|[\n\u2028\u2029]/g,"\n").replace(/^\uFEFF/,""),pos:0,tokpos:0,line:0,tokline:0,col:0,tokcol:0,newline_before:!1,regex_allowed:!1,comments_before:[]};N.context=function(a){a&&(f=a);return f};return N}function t(a,b,c){return a.type==b&&(c==null||a.value==c)}function s(a,b,c,d){throw new r(a,b,c,d)}function r(a,b,c,d){this.message=a,this.line=b,this.col=c,this.pos=d;try{({})()}catch(e){this.stack=e.stack}}function q(a){if(f.test(a))return parseInt(a.substr(2),16);if(g.test(a))return parseInt(a.substr(1),8);if(h.test(a))return parseFloat(a)}function p(a){a=a.charCodeAt(0);return a>=48&&a<=57}function o(a){return n(a)||a=="$"||a=="_"}function n(a){a=a.charCodeAt(0);return a>=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122}var a=L(["break","case","catch","const","continue","default","delete","do","else","finally","for","function","if","in","instanceof","new","return","switch","throw","try","typeof","var","void","while","with"]),b=L(["abstract","boolean","byte","char","class","debugger","double","enum","export","extends","final","float","goto","implements","import","int","interface","long","native","package","private","protected","public","short","static","super","synchronized","throws","transient","volatile"]),c=L(["return","new","delete","throw","else","case"]),d=L(["false","null","true","undefined"]),e=L(N("+-*&%=<>!?|~^")),f=/^0x[0-9a-f]+$/i,g=/^0[0-7]+$/,h=/^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i,i=L(["in","instanceof","typeof","new","void","delete","++","--","+","-","!","~","&","|","^","*","/","%",">>","<<",">>>","<",">","<=",">=","==","===","!=","!==","?","=","+=","-=","/=","*=","%=",">>=","<<=",">>>=","%=","|=","^=","&=","&&","||"]),j=L(N(" \n\r\t")),k=L(N("[{}(,.;:")),l=L(N("[]{}(),;:")),m=L(N("gmsiy"));r.prototype.toString=function(){return this.message+" (line: "+this.line+", col: "+this.col+", pos: "+this.pos+")"+"\n\n"+this.stack};var u={},w=L(["typeof","void","delete","--","++","!","~","-","+"]),x=L(["--","++"]),y=function(a,b,c){while(c<a.length)b[a[c]]=a[c].substr(0,a[c].length-1),c++;return b}(["+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&="],{"=":!0},0),z=function(a,b){for(var c=0,d=1;c<a.length;++c,++d){var e=a[c];for(var f=0;f<e.length;++f)b[e[f]]=d}return b}([["||"],["&&"],["|"],["^"],["&"],["==","===","!=","!=="],["<",">","<=",">=","in","instanceof"],[">>","<<",">>>"],["+","-"],["*","/","%"]],{}),A=L(["for","do","while","switch"]),B=L(["atom","num","string","regexp","name"]);C.prototype.toString=function(){return this.name};var G=L(["name","array","string","dot","sub","call","regexp"]);return{parse:D,stringify:I,tokenizer:v,walker:E}}
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);
}
};
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);
}
}
function isDynamic(exp) {
var type = exp[0];
return type != 'num' && type != 'string';
}
function handleOperator(operator, left, right) {
if (operators[operator] && isDynamic(left)) {
return ['call', ['name', '$eval'],
[left, ['string', operator], right]];
}
}
function compile(code) {
var ast = parse_js.parse(code),
walker = parse_js.walker(),
walk = walker.walk;
ast = walker.with_walkers({
'binary': function(operator, left, right) {
return handleOperator(operator, left = walk(left),
right = walk(right))
|| [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.stringify(ast, true);
}
function evaluate(code, scope) {
if (typeof code !== 'string') {
var canvas = code.getAttribute('canvas');
if (canvas = canvas && document.getElementById(canvas)) {
paper = scope;
new Project();
new View(canvas).activate();
}
if (code.src) {
return request(code.src, scope);
} else {
code = code.innerHTML;
}
}
var view = scope.view,
tool = scope.tool = /on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)
&& new Tool(null, scope),
res;
paper = scope;
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;
if (onFrame) {
view.setOnFrame(onFrame);
} else {
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'),
count = 0;
for (var i = 0, l = scripts.length; i < l; i++) {
var script = scripts[i];
if (script.type === 'text/paperscript'
&& !script.getAttribute('loaded')) {
var scope = new PaperScope(script.getAttribute('id')
|| script.src || ('paperscript-' + (count++)));
script.setAttribute('id', scope.id);
evaluate(script, scope);
script.setAttribute('loaded', true);
}
}
}
DomEvent.add(window, { load: load });
return {
compile: compile,
evaluate: evaluate,
load: load
};
};
this.load = PaperScript.load;
return new (PaperScope.inject(this));
};