paper.js/dist/paper.js
2012-07-20 19:56:39 +02:00

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