web/res/js/paper.js
changeset 1514 5869151a1f2f
parent 1304 10974bff4dae
--- a/web/res/js/paper.js	Wed Dec 11 11:04:27 2019 +0100
+++ b/web/res/js/paper.js	Wed Dec 11 14:30:18 2019 +0100
@@ -1,21 +1,21 @@
 /*!
- * Paper.js v0.9.24 - The Swiss Army Knife of Vector Graphics Scripting.
+ * Paper.js v0.12.3 - The Swiss Army Knife of Vector Graphics Scripting.
  * http://paperjs.org/
  *
- * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
- * http://scratchdisk.com/ & http://jonathanpuckey.com/
+ * Copyright (c) 2011 - 2019, Juerg Lehni & Jonathan Puckey
+ * http://scratchdisk.com/ & https://puckey.studio/
  *
  * Distributed under the MIT license. See LICENSE file for details.
  *
  * All rights reserved.
  *
- * Date: Fri Aug 21 16:39:41 2015 +0200
+ * Date: Sat Jun 22 14:16:49 2019 +0200
  *
  ***
  *
  * Straps.js - Class inheritance library with support for bean-style accessors
  *
- * Copyright (c) 2006 - 2013 Juerg Lehni
+ * Copyright (c) 2006 - 2019 Juerg Lehni
  * http://scratchdisk.com/
  *
  * Distributed under the MIT license.
@@ -23,59 +23,58 @@
  ***
  *
  * Acorn.js
- * http://marijnhaverbeke.nl/acorn/
+ * https://marijnhaverbeke.nl/acorn/
  *
  * Acorn is a tiny, fast JavaScript parser written in JavaScript,
  * created by Marijn Haverbeke and released under an MIT license.
  *
  */
 
-var paper = new function(undefined) {
+var paper = function(self, undefined) {
+
+self = self || require('./node/self.js');
+var window = self.window,
+	document = self.document;
 
 var Base = new function() {
 	var hidden = /^(statics|enumerable|beans|preserve)$/,
-
-		forEach = [].forEach || function(iter, bind) {
-			for (var i = 0, l = this.length; i < l; i++)
+		array = [],
+		slice = array.slice,
+		create = Object.create,
+		describe = Object.getOwnPropertyDescriptor,
+		define = Object.defineProperty,
+
+		forEach = array.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)
+			for (var i in this) {
 				if (this.hasOwnProperty(i))
 					iter.call(bind, this[i], i, this);
-		},
-
-		create = Object.create || function(proto) {
-			return { __proto__: proto };
-		},
-
-		describe = Object.getOwnPropertyDescriptor || function(obj, name) {
-			var get = obj.__lookupGetter__ && obj.__lookupGetter__(name);
-			return get
-					? { get: get, set: obj.__lookupSetter__(name),
-						enumerable: true, configurable: true }
-					: obj.hasOwnProperty(name)
-						? { value: obj[name], enumerable: true,
-							configurable: true, writable: true }
-						: null;
-		},
-
-		_define = Object.defineProperty || function(obj, name, desc) {
-			if ((desc.get || desc.set) && obj.__defineGetter__) {
-				if (desc.get)
-					obj.__defineGetter__(name, desc.get);
-				if (desc.set)
-					obj.__defineSetter__(name, desc.set);
-			} else {
-				obj[name] = desc.value;
-			}
-			return obj;
-		},
-
-		define = function(obj, name, desc) {
-			delete obj[name];
-			return _define(obj, name, desc);
+			}
+		},
+
+		set = Object.assign || function(dst) {
+			for (var i = 1, l = arguments.length; i < l; i++) {
+				var src = arguments[i];
+				for (var key in src) {
+					if (src.hasOwnProperty(key))
+						dst[key] = src[key];
+				}
+			}
+			return dst;
+		},
+
+		each = function(obj, iter, bind) {
+			if (obj) {
+				var desc = describe(obj, 'length');
+				(desc && typeof desc.value === 'number' ? forEach : forIn)
+					.call(obj, iter, bind = bind || obj);
+			}
+			return bind;
 		};
 
 	function inject(dest, src, enumerable, beans, preserve) {
@@ -99,12 +98,13 @@
 						&& (bean = name.match(/^([gs]et|is)(([A-Z])(.*))$/)))
 					beansNames[bean[3].toLowerCase() + bean[4]] = bean[2];
 				if (!res || isFunc || !res.get || typeof res.get !== 'function'
-						|| !Base.isPlainObject(res))
+						|| !Base.isPlainObject(res)) {
 					res = { value: res, writable: true };
+				}
 				if ((describe(dest, name)
 						|| { configurable: true }).configurable) {
 					res.configurable = true;
-					res.enumerable = enumerable;
+					res.enumerable = enumerable != null ? enumerable : !bean;
 				}
 				define(dest, name, res);
 			}
@@ -125,26 +125,16 @@
 		return dest;
 	}
 
-	function each(obj, iter, bind) {
-		if (obj)
-			('length' in obj && !obj.getLength
-					&& typeof obj.length === 'number'
-				? forEach
-				: forIn).call(obj, iter, bind = bind || obj);
-		return bind;
-	}
-
-	function set(obj, props, exclude) {
-		for (var key in props)
-			if (props.hasOwnProperty(key) && !(exclude && exclude[key]))
-				obj[key] = props[key];
-		return obj;
-	}
-
-	return inject(function Base() {
-		for (var i = 0, l = arguments.length; i < l; i++)
-			set(this, arguments[i]);
-	}, {
+	function Base() {
+		for (var i = 0, l = arguments.length; i < l; i++) {
+			var src = arguments[i];
+			if (src)
+				set(this, src);
+		}
+		return this;
+	}
+
+	return inject(Base, {
 		inject: function(src) {
 			if (src) {
 				var statics = src.statics === true ? src : src.statics,
@@ -152,7 +142,7 @@
 					preserve = src.preserve;
 				if (statics !== src)
 					inject(this.prototype, src, src.enumerable, beans, preserve);
-				inject(this, statics, true, beans, preserve);
+				inject(this, statics, null, beans, preserve);
 			}
 			for (var i = 1, l = arguments.length; i < l; i++)
 				this.inject(arguments[i]);
@@ -163,27 +153,37 @@
 			var base = this,
 				ctor,
 				proto;
-			for (var i = 0, l = arguments.length; i < l; i++)
-				if (ctor = arguments[i].initialize)
-					break;
+			for (var i = 0, obj, l = arguments.length;
+					i < l && !(ctor && proto); i++) {
+				obj = arguments[i];
+				ctor = ctor || obj.initialize;
+				proto = proto || obj.prototype;
+			}
 			ctor = ctor || function() {
 				base.apply(this, arguments);
 			};
-			proto = ctor.prototype = create(this.prototype);
+			proto = ctor.prototype = proto || create(this.prototype);
 			define(proto, 'constructor',
 					{ value: ctor, writable: true, configurable: true });
-			inject(ctor, this, true);
+			inject(ctor, this);
 			if (arguments.length)
 				this.inject.apply(ctor, arguments);
 			ctor.base = base;
 			return ctor;
 		}
-	}, true).inject({
+	}).inject({
+		enumerable: false,
+
+		initialize: Base,
+
+		set: Base,
+
 		inject: function() {
 			for (var i = 0, l = arguments.length; i < l; i++) {
 				var src = arguments[i];
-				if (src)
+				if (src) {
 					inject(this, src, src.enumerable, src.beans, src.preserve);
+				}
 			}
 			return this;
 		},
@@ -197,20 +197,16 @@
 			return each(this, iter, bind);
 		},
 
-		set: function(props) {
-			return set(this, props);
-		},
-
 		clone: function() {
 			return new this.constructor(this);
 		},
 
 		statics: {
+			set: set,
 			each: each,
 			create: create,
 			define: define,
 			describe: describe,
-			set: set,
 
 			clone: function(obj) {
 				return set(new obj.constructor(), obj);
@@ -224,6 +220,10 @@
 
 			pick: function(a, b) {
 				return a !== undefined ? a : b;
+			},
+
+			slice: function(list, begin, end) {
+				return slice.call(list, begin, end);
 			}
 		}
 	});
@@ -233,6 +233,8 @@
 	module.exports = Base;
 
 Base.inject({
+	enumerable: false,
+
 	toString: function() {
 		return this._id != null
 			?  (this._class || 'Object') + (this._name
@@ -252,6 +254,10 @@
 		return this._class || '';
 	},
 
+	importJSON: function(json) {
+		return Base.importJSON(json, this);
+	},
+
 	exportJSON: function(options) {
 		return Base.exportJSON(this, options);
 	},
@@ -260,313 +266,354 @@
 		return Base.serialize(this);
 	},
 
-	_set: function(props, exclude, dontCheck) {
-		if (props && (dontCheck || Base.isPlainObject(props))) {
-			var keys = Object.keys(props._filtering || props);
-			for (var i = 0, l = keys.length; i < l; i++) {
-				var key = keys[i];
-				if (!(exclude && exclude[key])) {
-					var value = props[key];
-					if (value !== undefined)
-						this[key] = value;
+	set: function(props, exclude) {
+		if (props)
+			Base.filter(this, props, exclude, this._prioritize);
+		return this;
+	}
+}, {
+
+beans: false,
+statics: {
+	exports: {},
+
+	extend: function extend() {
+		var res = extend.base.apply(this, arguments),
+			name = res.prototype._class;
+		if (name && !Base.exports[name])
+			Base.exports[name] = res;
+		return res;
+	},
+
+	equals: function(obj1, obj2) {
+		if (obj1 === obj2)
+			return true;
+		if (obj1 && obj1.equals)
+			return obj1.equals(obj2);
+		if (obj2 && obj2.equals)
+			return obj2.equals(obj1);
+		if (obj1 && obj2
+				&& typeof obj1 === 'object' && typeof obj2 === 'object') {
+			if (Array.isArray(obj1) && Array.isArray(obj2)) {
+				var length = obj1.length;
+				if (length !== obj2.length)
+					return false;
+				while (length--) {
+					if (!Base.equals(obj1[length], obj2[length]))
+						return false;
+				}
+			} else {
+				var keys = Object.keys(obj1),
+					length = keys.length;
+				if (length !== Object.keys(obj2).length)
+					return false;
+				while (length--) {
+					var key = keys[length];
+					if (!(obj2.hasOwnProperty(key)
+							&& Base.equals(obj1[key], obj2[key])))
+						return false;
 				}
 			}
 			return true;
 		}
-	},
-
-	statics: {
-
-		exports: {
-			enumerable: true
-		},
-
-		extend: function extend() {
-			var res = extend.base.apply(this, arguments),
-				name = res.prototype._class;
-			if (name && !Base.exports[name])
-				Base.exports[name] = res;
-			return res;
-		},
-
-		equals: function(obj1, obj2) {
-			if (obj1 === obj2)
-				return true;
-			if (obj1 && obj1.equals)
-				return obj1.equals(obj2);
-			if (obj2 && obj2.equals)
-				return obj2.equals(obj1);
-			if (obj1 && obj2
-					&& typeof obj1 === 'object' && typeof obj2 === 'object') {
-				if (Array.isArray(obj1) && Array.isArray(obj2)) {
-					var length = obj1.length;
-					if (length !== obj2.length)
-						return false;
-					while (length--) {
-						if (!Base.equals(obj1[length], obj2[length]))
-							return false;
-					}
-				} else {
-					var keys = Object.keys(obj1),
-						length = keys.length;
-					if (length !== Object.keys(obj2).length)
-						return false;
-					while (length--) {
-						var key = keys[length];
-						if (!(obj2.hasOwnProperty(key)
-								&& Base.equals(obj1[key], obj2[key])))
-							return false;
-					}
-				}
-				return true;
-			}
-			return false;
-		},
-
-		read: function(list, start, options, length) {
-			if (this === Base) {
-				var value = this.peek(list, start);
-				list.__index++;
-				return value;
-			}
-			var proto = this.prototype,
-				readIndex = proto._readIndex,
-				index = start || readIndex && list.__index || 0;
-			if (!length)
-				length = list.length - index;
-			var obj = list[index];
-			if (obj instanceof this
-				|| options && options.readNull && obj == null && length <= 1) {
-				if (readIndex)
-					list.__index = index + 1;
-				return obj && options && options.clone ? obj.clone() : obj;
-			}
-			obj = Base.create(this.prototype);
+		return false;
+	},
+
+	read: function(list, start, options, amount) {
+		if (this === Base) {
+			var value = this.peek(list, start);
+			list.__index++;
+			return value;
+		}
+		var proto = this.prototype,
+			readIndex = proto._readIndex,
+			begin = start || readIndex && list.__index || 0,
+			length = list.length,
+			obj = list[begin];
+		amount = amount || length - begin;
+		if (obj instanceof this
+			|| options && options.readNull && obj == null && amount <= 1) {
 			if (readIndex)
-				obj.__read = true;
-			obj = obj.initialize.apply(obj, index > 0 || length < list.length
-				? Array.prototype.slice.call(list, index, index + length)
+				list.__index = begin + 1;
+			return obj && options && options.clone ? obj.clone() : obj;
+		}
+		obj = Base.create(proto);
+		if (readIndex)
+			obj.__read = true;
+		obj = obj.initialize.apply(obj, begin > 0 || begin + amount < length
+				? Base.slice(list, begin, begin + amount)
 				: list) || obj;
-			if (readIndex) {
-				list.__index = index + obj.__read;
-				obj.__read = undefined;
-			}
-			return obj;
-		},
-
-		peek: function(list, start) {
-			return list[list.__index = start || list.__index || 0];
-		},
-
-		remain: function(list) {
-			return list.length - (list.__index || 0);
-		},
-
-		readAll: function(list, start, options) {
-			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, options)
-						: this.read(list, i, options, 1));
-			}
-			return res;
-		},
-
-		readNamed: function(list, name, start, options, length) {
-			var value = this.getNamed(list, name),
-				hasObject = value !== undefined;
-			if (hasObject) {
-				var filtered = list._filtered;
-				if (!filtered) {
-					filtered = list._filtered = Base.create(list[0]);
-					filtered._filtering = list[0];
-				}
-				filtered[name] = undefined;
-			}
-			return this.read(hasObject ? [value] : list, start, options, length);
-		},
-
-		getNamed: function(list, name) {
-			var arg = list[0];
-			if (list._hasObject === undefined)
-				list._hasObject = list.length === 1 && Base.isPlainObject(arg);
-			if (list._hasObject)
-				return name ? arg[name] : list._filtered || arg;
-		},
-
-		hasNamed: function(list, name) {
-			return !!this.getNamed(list, name);
-		},
-
-		isPlainValue: function(obj, asString) {
-			return this.isPlainObject(obj) || Array.isArray(obj)
-					|| asString && typeof obj === 'string';
-		},
-
-		serialize: function(obj, options, compact, dictionary) {
-			options = options || {};
-
-			var root = !dictionary,
-				res;
-			if (root) {
-				options.formatter = new Formatter(options.precision);
-				dictionary = {
-					length: 0,
-					definitions: {},
-					references: {},
-					add: function(item, create) {
-						var id = '#' + item._id,
-							ref = this.references[id];
-						if (!ref) {
-							this.length++;
-							var res = create.call(item),
-								name = item._class;
-							if (name && res[0] !== name)
-								res.unshift(name);
-							this.definitions[id] = res;
-							ref = this.references[id] = [id];
+		if (readIndex) {
+			list.__index = begin + obj.__read;
+			var filtered = obj.__filtered;
+			if (filtered) {
+				list.__filtered = filtered;
+				obj.__filtered = undefined;
+			}
+			obj.__read = undefined;
+		}
+		return obj;
+	},
+
+	peek: function(list, start) {
+		return list[list.__index = start || list.__index || 0];
+	},
+
+	remain: function(list) {
+		return list.length - (list.__index || 0);
+	},
+
+	readList: function(list, start, options, amount) {
+		var res = [],
+			entry,
+			begin = start || 0,
+			end = amount ? begin + amount : list.length;
+		for (var i = begin; i < end; i++) {
+			res.push(Array.isArray(entry = list[i])
+					? this.read(entry, 0, options)
+					: this.read(list, i, options, 1));
+		}
+		return res;
+	},
+
+	readNamed: function(list, name, start, options, amount) {
+		var value = this.getNamed(list, name),
+			hasObject = value !== undefined;
+		if (hasObject) {
+			var filtered = list.__filtered;
+			if (!filtered) {
+				filtered = list.__filtered = Base.create(list[0]);
+				filtered.__unfiltered = list[0];
+			}
+			filtered[name] = undefined;
+		}
+		var l = hasObject ? [value] : list,
+			res = this.read(l, start, options, amount);
+		return res;
+	},
+
+	getNamed: function(list, name) {
+		var arg = list[0];
+		if (list._hasObject === undefined)
+			list._hasObject = list.length === 1 && Base.isPlainObject(arg);
+		if (list._hasObject)
+			return name ? arg[name] : list.__filtered || arg;
+	},
+
+	hasNamed: function(list, name) {
+		return !!this.getNamed(list, name);
+	},
+
+	filter: function(dest, source, exclude, prioritize) {
+		var processed;
+
+		function handleKey(key) {
+			if (!(exclude && key in exclude) &&
+				!(processed && key in processed)) {
+				var value = source[key];
+				if (value !== undefined)
+					dest[key] = value;
+			}
+		}
+
+		if (prioritize) {
+			var keys = {};
+			for (var i = 0, key, l = prioritize.length; i < l; i++) {
+				if ((key = prioritize[i]) in source) {
+					handleKey(key);
+					keys[key] = true;
+				}
+			}
+			processed = keys;
+		}
+
+		Object.keys(source.__unfiltered || source).forEach(handleKey);
+		return dest;
+	},
+
+	isPlainValue: function(obj, asString) {
+		return Base.isPlainObject(obj) || Array.isArray(obj)
+				|| asString && typeof obj === 'string';
+	},
+
+	serialize: function(obj, options, compact, dictionary) {
+		options = options || {};
+
+		var isRoot = !dictionary,
+			res;
+		if (isRoot) {
+			options.formatter = new Formatter(options.precision);
+			dictionary = {
+				length: 0,
+				definitions: {},
+				references: {},
+				add: function(item, create) {
+					var id = '#' + item._id,
+						ref = this.references[id];
+					if (!ref) {
+						this.length++;
+						var res = create.call(item),
+							name = item._class;
+						if (name && res[0] !== name)
+							res.unshift(name);
+						this.definitions[id] = res;
+						ref = this.references[id] = [id];
+					}
+					return ref;
+				}
+			};
+		}
+		if (obj && obj._serialize) {
+			res = obj._serialize(options, dictionary);
+			var name = obj._class;
+			if (name && !obj._compactSerialize && (isRoot || !compact)
+					&& res[0] !== name) {
+				res.unshift(name);
+			}
+		} else if (Array.isArray(obj)) {
+			res = [];
+			for (var i = 0, l = obj.length; i < l; i++)
+				res[i] = Base.serialize(obj[i], options, compact, dictionary);
+		} else if (Base.isPlainObject(obj)) {
+			res = {};
+			var keys = Object.keys(obj);
+			for (var i = 0, l = keys.length; i < l; i++) {
+				var key = keys[i];
+				res[key] = Base.serialize(obj[key], options, compact,
+						dictionary);
+			}
+		} else if (typeof obj === 'number') {
+			res = options.formatter.number(obj, options.precision);
+		} else {
+			res = obj;
+		}
+		return isRoot && dictionary.length > 0
+				? [['dictionary', dictionary.definitions], res]
+				: res;
+	},
+
+	deserialize: function(json, create, _data, _setDictionary, _isRoot) {
+		var res = json,
+			isFirst = !_data,
+			hasDictionary = isFirst && json && json.length
+				&& json[0][0] === 'dictionary';
+		_data = _data || {};
+		if (Array.isArray(json)) {
+			var type = json[0],
+				isDictionary = type === 'dictionary';
+			if (json.length == 1 && /^#/.test(type)) {
+				return _data.dictionary[type];
+			}
+			type = Base.exports[type];
+			res = [];
+			for (var i = type ? 1 : 0, l = json.length; i < l; i++) {
+				res.push(Base.deserialize(json[i], create, _data,
+						isDictionary, hasDictionary));
+			}
+			if (type) {
+				var args = res;
+				if (create) {
+					res = create(type, args, isFirst || _isRoot);
+				} else {
+					res = new type(args);
+				}
+			}
+		} else if (Base.isPlainObject(json)) {
+			res = {};
+			if (_setDictionary)
+				_data.dictionary = res;
+			for (var key in json)
+				res[key] = Base.deserialize(json[key], create, _data);
+		}
+		return hasDictionary ? res[1] : res;
+	},
+
+	exportJSON: function(obj, options) {
+		var json = Base.serialize(obj, options);
+		return options && options.asString == false
+				? json
+				: JSON.stringify(json);
+	},
+
+	importJSON: function(json, target) {
+		return Base.deserialize(
+				typeof json === 'string' ? JSON.parse(json) : json,
+				function(ctor, args, isRoot) {
+					var useTarget = isRoot && target
+							&& target.constructor === ctor,
+						obj = useTarget ? target
+							: Base.create(ctor.prototype);
+					if (args.length === 1 && obj instanceof Item
+							&& (useTarget || !(obj instanceof Layer))) {
+						var arg = args[0];
+						if (Base.isPlainObject(arg)) {
+							arg.insert = false;
+							if (useTarget) {
+								args = args.concat([{ insert: true }]);
+							}
 						}
-						return ref;
-					}
-				};
-			}
-			if (obj && obj._serialize) {
-				res = obj._serialize(options, dictionary);
-				var name = obj._class;
-				if (name && !compact && !res._compact && res[0] !== name)
-					res.unshift(name);
-			} else if (Array.isArray(obj)) {
-				res = [];
-				for (var i = 0, l = obj.length; i < l; i++)
-					res[i] = Base.serialize(obj[i], options, compact,
-							dictionary);
-				if (compact)
-					res._compact = true;
-			} else if (Base.isPlainObject(obj)) {
-				res = {};
-				var keys = Object.keys(obj);
-				for (var i = 0, l = keys.length; i < l; i++) {
-					var key = keys[i];
-					res[key] = Base.serialize(obj[key], options, compact,
-							dictionary);
-				}
-			} else if (typeof obj === 'number') {
-				res = options.formatter.number(obj, options.precision);
-			} else {
-				res = obj;
-			}
-			return root && dictionary.length > 0
-					? [['dictionary', dictionary.definitions], res]
-					: res;
-		},
-
-		deserialize: function(json, create, _data, _isDictionary) {
-			var res = json,
-				isRoot = !_data;
-			_data = _data || {};
-			if (Array.isArray(json)) {
-				var type = json[0],
-					isDictionary = type === 'dictionary';
-				if (json.length == 1 && /^#/.test(type))
-					return _data.dictionary[type];
-				type = Base.exports[type];
-				res = [];
-				if (_isDictionary)
-					_data.dictionary = res;
-				for (var i = type ? 1 : 0, l = json.length; i < l; i++)
-					res.push(Base.deserialize(json[i], create, _data,
-							isDictionary));
-				if (type) {
-					var args = res;
-					if (create) {
-						res = create(type, args);
-					} else {
-						res = Base.create(type.prototype);
-						type.apply(res, args);
-					}
-				}
-			} else if (Base.isPlainObject(json)) {
-				res = {};
-				if (_isDictionary)
-					_data.dictionary = res;
-				for (var key in json)
-					res[key] = Base.deserialize(json[key], create, _data);
-			}
-			return isRoot && json && json.length && json[0][0] === 'dictionary'
-					? res[1]
-					: res;
-		},
-
-		exportJSON: function(obj, options) {
-			var json = Base.serialize(obj, options);
-			return options && options.asString === false
-					? json
-					: JSON.stringify(json);
-		},
-
-		importJSON: function(json, target) {
-			return Base.deserialize(
-					typeof json === 'string' ? JSON.parse(json) : json,
-					function(type, args) {
-						var obj = target && target.constructor === type
-								? target
-								: Base.create(type.prototype),
-							isTarget = obj === target;
-						if (args.length === 1 && obj instanceof Item
-								&& (isTarget || !(obj instanceof Layer))) {
-							var arg = args[0];
-							if (Base.isPlainObject(arg))
-								arg.insert = false;
-						}
-						type.apply(obj, args);
-						if (isTarget)
-							target = null;
-						return obj;
-					});
-		},
-
-		splice: function(list, items, index, remove) {
-			var amount = items && items.length,
-				append = index === undefined;
-			index = append ? list.length : index;
-			if (index > list.length)
-				index = list.length;
-			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++)
-					removed[i]._index = undefined;
-				for (var i = index + amount, l = list.length; i < l; i++)
-					list[i]._index = i;
-				return removed;
-			}
-		},
-
-		capitalize: function(str) {
-			return str.replace(/\b[a-z]/g, function(match) {
-				return match.toUpperCase();
-			});
-		},
-
-		camelize: function(str) {
-			return str.replace(/-(.)/g, function(all, chr) {
-				return chr.toUpperCase();
-			});
-		},
-
-		hyphenate: function(str) {
-			return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
-		}
-	}
-});
+					}
+					(useTarget ? obj.set : ctor).apply(obj, args);
+					if (useTarget)
+						target = null;
+					return obj;
+				});
+	},
+
+	push: function(list, items) {
+		var itemsLength = items.length;
+		if (itemsLength < 4096) {
+			list.push.apply(list, items);
+		} else {
+			var startLength = list.length;
+			list.length += itemsLength;
+			for (var i = 0; i < itemsLength; i++) {
+				list[startLength + i] = items[i];
+			}
+		}
+		return list;
+	},
+
+	splice: function(list, items, index, remove) {
+		var amount = items && items.length,
+			append = index === undefined;
+		index = append ? list.length : index;
+		if (index > list.length)
+			index = list.length;
+		for (var i = 0; i < amount; i++)
+			items[i]._index = index + i;
+		if (append) {
+			Base.push(list, items);
+			return [];
+		} else {
+			var args = [index, remove];
+			if (items)
+				Base.push(args, items);
+			var removed = list.splice.apply(list, args);
+			for (var i = 0, l = removed.length; i < l; i++)
+				removed[i]._index = undefined;
+			for (var i = index + amount, l = list.length; i < l; i++)
+				list[i]._index = i;
+			return removed;
+		}
+	},
+
+	capitalize: function(str) {
+		return str.replace(/\b[a-z]/g, function(match) {
+			return match.toUpperCase();
+		});
+	},
+
+	camelize: function(str) {
+		return str.replace(/-(.)/g, function(match, chr) {
+			return chr.toUpperCase();
+		});
+	},
+
+	hyphenate: function(str) {
+		return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
+	}
+}});
 
 var Emitter = {
 	on: function(type, func) {
@@ -581,7 +628,7 @@
 			handlers = handlers[type] = handlers[type] || [];
 			if (handlers.indexOf(func) === -1) {
 				handlers.push(func);
-				if (entry && entry.install && handlers.length == 1)
+				if (entry && entry.install && handlers.length === 1)
 					entry.install.call(this, type);
 			}
 		}
@@ -613,9 +660,9 @@
 	},
 
 	once: function(type, func) {
-		return this.on(type, function() {
+		return this.on(type, function handler() {
 			func.apply(this, arguments);
-			this.off(type, func);
+			this.off(type, handler);
 		});
 	},
 
@@ -623,15 +670,20 @@
 		var handlers = this._callbacks && this._callbacks[type];
 		if (!handlers)
 			return false;
-		var args = [].slice.call(arguments, 1);
+		var args = Base.slice(arguments, 1),
+			setTarget = event && event.target && !event.currentTarget;
 		handlers = handlers.slice();
+		if (setTarget)
+			event.currentTarget = this;
 		for (var i = 0, l = handlers.length; i < l; i++) {
-			if (handlers[i].apply(this, args) === false) {
+			if (handlers[i].apply(this, args) == false) {
 				if (event && event.stop)
 					event.stop();
 				break;
-			}
-		}
+		   }
+		}
+		if (setTarget)
+			delete event.currentTarget;
 		return true;
 	},
 
@@ -644,15 +696,17 @@
 	fire: '#emit',
 
 	_installEvents: function(install) {
-		var handlers = this._callbacks,
+		var types = this._eventTypes,
+			handlers = this._callbacks,
 			key = install ? 'install' : 'uninstall';
-		for (var type in handlers) {
-			if (handlers[type].length > 0) {
-				var types = this._eventTypes,
-					entry = types && types[type],
-					func = entry && entry[key];
-				if (func)
-					func.call(this, type);
+		if (types) {
+			for (var type in handlers) {
+				if (handlers[type].length > 0) {
+					var entry = types[type],
+						func = entry && entry[key];
+					if (func)
+						func.call(this, type);
+				}
 			}
 		}
 	},
@@ -695,70 +749,67 @@
 		paper = this;
 		this.settings = new Base({
 			applyMatrix: true,
+			insertItems: true,
 			handleSize: 4,
 			hitTolerance: 0
 		});
 		this.project = null;
 		this.projects = [];
 		this.tools = [];
-		this.palettes = [];
 		this._id = PaperScope._id++;
 		PaperScope._scopes[this._id] = this;
 		var proto = PaperScope.prototype;
 		if (!this.support) {
-			var ctx = CanvasProvider.getContext(1, 1);
+			var ctx = CanvasProvider.getContext(1, 1) || {};
 			proto.support = {
 				nativeDash: 'setLineDash' in ctx || 'mozDash' in ctx,
 				nativeBlendModes: BlendMode.nativeModes
 			};
 			CanvasProvider.release(ctx);
 		}
-
-		if (!this.browser) {
-			var agent = navigator.userAgent.toLowerCase(),
-				platform = (/(win)/.exec(agent)
-						|| /(mac)/.exec(agent)
-						|| /(linux)/.exec(agent)
-						|| [])[0],
-				browser = proto.browser = { platform: platform };
+		if (!this.agent) {
+			var user = self.navigator.userAgent.toLowerCase(),
+				os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0],
+				platform = os === 'darwin' ? 'mac' : os,
+				agent = proto.agent = proto.browser = { platform: platform };
 			if (platform)
-				browser[platform] = true;
-			agent.replace(
-				/(opera|chrome|safari|webkit|firefox|msie|trident|atom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:([.\d]+))?/g,
-				function(all, n, v1, v2, rv) {
-					if (!browser.chrome) {
-						var v = n === 'opera' ? v2 : v1;
-						if (n === 'trident') {
-							v = rv;
-							n = 'msie';
-						}
-						browser.version = v;
-						browser.versionNumber = parseFloat(v);
-						browser.name = n;
-						browser[n] = true;
+				agent[platform] = true;
+			user.replace(
+				/(opera|chrome|safari|webkit|firefox|msie|trident|atom|node|jsdom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g,
+				function(match, n, v1, v2, rv) {
+					if (!agent.chrome) {
+						var v = n === 'opera' ? v2 :
+								/^(node|trident)$/.test(n) ? rv : v1;
+						agent.version = v;
+						agent.versionNumber = parseFloat(v);
+						n = { trident: 'msie', jsdom: 'node' }[n] || n;
+						agent.name = n;
+						agent[n] = true;
 					}
 				}
 			);
-			if (browser.chrome)
-				delete browser.webkit;
-			if (browser.atom)
-				delete browser.chrome;
-		}
-	},
-
-	version: '0.9.24',
+			if (agent.chrome)
+				delete agent.webkit;
+			if (agent.atom)
+				delete agent.chrome;
+		}
+	},
+
+	version: "0.12.3",
 
 	getView: function() {
-		return this.project && this.project.getView();
+		var project = this.project;
+		return project && project._view;
 	},
 
 	getPaper: function() {
 		return this;
 	},
 
-	execute: function(code, url, options) {
-		paper.PaperScript.execute(code, this, url, options);
-		View.updateFocus();
+	execute: function(code, options) {
+			var exports = paper.PaperScript.execute(code, this, options);
+			View.updateFocus();
+			return exports;
 	},
 
 	install: function(scope) {
@@ -782,17 +833,21 @@
 		return this;
 	},
 
+	createCanvas: function(width, height) {
+		return CanvasProvider.getCanvas(width, height);
+	},
+
 	activate: function() {
 		paper = this;
 	},
 
 	clear: function() {
-		for (var i = this.projects.length - 1; i >= 0; i--)
-			this.projects[i].remove();
-		for (var i = this.tools.length - 1; i >= 0; i--)
-			this.tools[i].remove();
-		for (var i = this.palettes.length - 1; i >= 0; i--)
-			this.palettes[i].remove();
+		var projects = this.projects,
+			tools = this.tools;
+		for (var i = projects.length - 1; i >= 0; i--)
+			projects[i].remove();
+		for (var i = tools.length - 1; i >= 0; i--)
+			tools[i].remove();
 	},
 
 	remove: function() {
@@ -854,17 +909,22 @@
 			this._scope[this._reference] = null;
 		this._scope = null;
 		return true;
+	},
+
+	getView: function() {
+		return this._scope.getView();
 	}
 });
 
 var Formatter = Base.extend({
 	initialize: function(precision) {
-		this.precision = precision || 5;
+		this.precision = Base.pick(precision, 5);
 		this.multiplier = Math.pow(10, this.precision);
 	},
 
 	number: function(val) {
-		return Math.round(val * this.multiplier) / this.multiplier;
+		return this.precision < 16
+				? Math.round(val * this.multiplier) / this.multiplier : val;
 	},
 
 	pair: function(val1, val2, separator) {
@@ -929,19 +989,61 @@
 	var abs = Math.abs,
 		sqrt = Math.sqrt,
 		pow = Math.pow,
-		TOLERANCE = 1e-6,
+		log2 = Math.log2 || function(x) {
+			return Math.log(x) * Math.LOG2E;
+		},
 		EPSILON = 1e-12,
 		MACHINE_EPSILON = 1.12e-16;
 
+	function clamp(value, min, max) {
+		return value < min ? min : value > max ? max : value;
+	}
+
+	function getDiscriminant(a, b, c) {
+		function split(v) {
+			var x = v * 134217729,
+				y = v - x,
+				hi = y + x,
+				lo = v - hi;
+			return [hi, lo];
+		}
+
+		var D = b * b - a * c,
+			E = b * b + a * c;
+		if (abs(D) * 3 < E) {
+			var ad = split(a),
+				bd = split(b),
+				cd = split(c),
+				p = b * b,
+				dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1],
+				q = a * c,
+				dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0])
+						+ ad[1] * cd[1];
+			D = (p - q) + (dp - dq);
+		}
+		return D;
+	}
+
+	function getNormalizationFactor() {
+		var norm = Math.max.apply(Math, arguments);
+		return norm && (norm < 1e-8 || norm > 1e8)
+				? pow(2, -Math.round(log2(norm)))
+				: 0;
+	}
+
 	return {
-		TOLERANCE: TOLERANCE,
 		EPSILON: EPSILON,
 		MACHINE_EPSILON: MACHINE_EPSILON,
+		CURVETIME_EPSILON: 1e-8,
+		GEOMETRIC_EPSILON: 1e-7,
+		TRIGONOMETRIC_EPSILON: 1e-8,
 		KAPPA: 4 * (sqrt(2) - 1) / 3,
 
 		isZero: function(val) {
-			return abs(val) <= EPSILON;
-		},
+			return val >= -EPSILON && val <= EPSILON;
+		},
+
+		clamp: clamp,
 
 		integrate: function(f, a, b, n) {
 			var x = abscissas[n - 2],
@@ -963,8 +1065,10 @@
 				var fx = f(x),
 					dx = fx / df(x),
 					nx = x - dx;
-				if (abs(dx) < tolerance)
-					return nx;
+				if (abs(dx) < tolerance) {
+					x = nx;
+					break;
+				}
 				if (fx > 0) {
 					b = x;
 					x = nx <= a ? (a + b) * 0.5 : nx;
@@ -973,58 +1077,70 @@
 					x = nx >= b ? (a + b) * 0.5 : nx;
 				}
 			}
-			return x;
+			return clamp(x, a, b);
 		},
 
 		solveQuadratic: function(a, b, c, roots, min, max) {
-			var count = 0,
-				x1, x2 = Infinity,
-				B = b,
-				D;
-			b /= 2;
-			D = b * b - a * c;
-			if (D !== 0 && abs(D) < MACHINE_EPSILON) {
-				var gmC = pow(abs(a * b * c), 1 / 3);
-				if (gmC < 1e-8) {
-					var mult = pow(10, abs(
-						Math.floor(Math.log(gmC) * Math.LOG10E)));
-					if (!isFinite(mult))
-						mult = 0;
-					a *= mult;
-					b *= mult;
-					c *= mult;
-					D = b * b - a * c;
-				}
-			}
+			var x1, x2 = Infinity;
 			if (abs(a) < EPSILON) {
-				if (abs(B) < EPSILON)
+				if (abs(b) < EPSILON)
 					return abs(c) < EPSILON ? -1 : 0;
-				x1 = -c / B;
-			} else {
+				x1 = -c / b;
+			} else {
+				b *= -0.5;
+				var D = getDiscriminant(a, b, c);
+				if (D && abs(D) < MACHINE_EPSILON) {
+					var f = getNormalizationFactor(abs(a), abs(b), abs(c));
+					if (f) {
+						a *= f;
+						b *= f;
+						c *= f;
+						D = getDiscriminant(a, b, c);
+					}
+				}
 				if (D >= -MACHINE_EPSILON) {
-					D = D < 0 ? 0 : D;
-					var R = sqrt(D);
-					if (b >= MACHINE_EPSILON && b <= MACHINE_EPSILON) {
-						x1 = abs(a) >= abs(c) ? R / a : -c / R;
+					var Q = D < 0 ? 0 : sqrt(D),
+						R = b + (b < 0 ? -Q : Q);
+					if (R === 0) {
+						x1 = c / a;
 						x2 = -x1;
 					} else {
-						var q = -(b + (b < 0 ? -1 : 1) * R);
-						x1 = q / a;
-						x2 = c / q;
-					}
-				}
-			}
-			if (isFinite(x1) && (min == null || x1 >= min && x1 <= max))
-				roots[count++] = x1;
+						x1 = R / a;
+						x2 = c / R;
+					}
+				}
+			}
+			var count = 0,
+				boundless = min == null,
+				minB = min - EPSILON,
+				maxB = max + EPSILON;
+			if (isFinite(x1) && (boundless || x1 > minB && x1 < maxB))
+				roots[count++] = boundless ? x1 : clamp(x1, min, max);
 			if (x2 !== x1
-					&& isFinite(x2) && (min == null || x2 >= min && x2 <= max))
-				roots[count++] = x2;
+					&& isFinite(x2) && (boundless || x2 > minB && x2 < maxB))
+				roots[count++] = boundless ? x2 : clamp(x2, min, max);
 			return count;
 		},
 
 		solveCubic: function(a, b, c, d, roots, min, max) {
-			var count = 0,
-				x, b1, c2;
+			var f = getNormalizationFactor(abs(a), abs(b), abs(c), abs(d)),
+				x, b1, c2, qd, q;
+			if (f) {
+				a *= f;
+				b *= f;
+				c *= f;
+				d *= f;
+			}
+
+			function evaluate(x0) {
+				x = x0;
+				var tmp = a * x;
+				b1 = tmp + b;
+				c2 = b1 * x + c;
+				qd = (tmp + b1) * x + c2;
+				q = c2 * x + d;
+			}
+
 			if (abs(a) < EPSILON) {
 				a = b;
 				b1 = c;
@@ -1035,33 +1151,17 @@
 				c2 = c;
 				x = 0;
 			} else {
-				var ec = 1 + MACHINE_EPSILON,
-					x0, q, qd, t, r, s, tmp;
-				x = -(b / a) / 3;
-				tmp = a * x,
-				b1 = tmp + b,
-				c2 = b1 * x + c,
-				qd = (tmp + b1) * x + c2,
-				q = c2 * x + d;
-				t = q /a;
-				r = pow(abs(t), 1/3);
-				s = t < 0 ? -1 : 1;
-				t = -qd / a;
-				r = t > 0 ? 1.3247179572 * Math.max(r, sqrt(t)) : r;
-				x0 = x - s * r;
+				evaluate(-(b / a) / 3);
+				var t = q / a,
+					r = pow(abs(t), 1/3),
+					s = t < 0 ? -1 : 1,
+					td = -qd / a,
+					rd = td > 0 ? 1.324717957244746 * Math.max(r, sqrt(td)) : r,
+					x0 = x - s * rd;
 				if (x0 !== x) {
 					do {
-						x = x0;
-						tmp = a * x,
-						b1 = tmp + b,
-						c2 = b1 * x + c,
-						qd = (tmp + b1) * x + c2,
-						q = c2 * x + d;
-						x0 = qd === 0 ? x : x - q / qd / ec;
-						if (x0 === x) {
-							x = x0;
-							break;
-						}
+						evaluate(x0);
+						x0 = qd === 0 ? x : x - q / qd / (1 + MACHINE_EPSILON);
 					} while (s * x0 > s * x);
 					if (abs(a) * x * x > abs(d / x)) {
 						c2 = -d / x;
@@ -1069,10 +1169,12 @@
 					}
 				}
 			}
-			var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max);
-			if (isFinite(x) && (count === 0 || x !== roots[count - 1])
-					&& (min == null || x >= min && x <= max))
-				roots[count++] = x;
+			var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max),
+				boundless = min == null;
+			if (isFinite(x) && (count === 0
+					|| count > 0 && x !== roots[0] && x !== roots[1])
+					&& (boundless || x > min - EPSILON && x < max + EPSILON))
+				roots[count++] = boundless ? x : clamp(x, min, max);
 			return count;
 		}
 	};
@@ -1082,10 +1184,9 @@
 	_id: 1,
 	_pools: {},
 
-	get: function(ctor) {
-		if (ctor) {
-			var name = ctor._class,
-				pool = this._pools[name];
+	get: function(name) {
+		if (name) {
+			var pool = this._pools[name];
 			if (!pool)
 				pool = this._pools[name] = { _id: 1 };
 			return pool._id++;
@@ -1100,42 +1201,43 @@
 	_readIndex: true,
 
 	initialize: function Point(arg0, arg1) {
-		var type = typeof arg0;
+		var type = typeof arg0,
+			reading = this.__read,
+			read = 0;
 		if (type === 'number') {
 			var hasY = typeof arg1 === 'number';
-			this.x = arg0;
-			this.y = hasY ? arg1 : arg0;
-			if (this.__read)
-				this.__read = hasY ? 2 : 1;
+			this._set(arg0, hasY ? arg1 : arg0);
+			if (reading)
+				read = hasY ? 2 : 1;
 		} else if (type === 'undefined' || arg0 === null) {
-			this.x = this.y = 0;
-			if (this.__read)
-				this.__read = arg0 === null ? 1 : 0;
+			this._set(0, 0);
+			if (reading)
+				read = arg0 === null ? 1 : 0;
 		} else {
-			if (Array.isArray(arg0)) {
-				this.x = arg0[0];
-				this.y = arg0.length > 1 ? arg0[1] : arg0[0];
-			} else if (arg0.x != null) {
-				this.x = arg0.x;
-				this.y = arg0.y;
-			} else if (arg0.width != null) {
-				this.x = arg0.width;
-				this.y = arg0.height;
-			} else if (arg0.angle != null) {
-				this.x = arg0.length;
-				this.y = 0;
-				this.setAngle(arg0.angle);
-			} else {
-				this.x = this.y = 0;
-				if (this.__read)
-					this.__read = 0;
-			}
-			if (this.__read)
-				this.__read = 1;
-		}
-	},
-
-	set: function(x, y) {
+			var obj = type === 'string' ? arg0.split(/[\s,]+/) || [] : arg0;
+			read = 1;
+			if (Array.isArray(obj)) {
+				this._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0]));
+			} else if ('x' in obj) {
+				this._set(obj.x || 0, obj.y || 0);
+			} else if ('width' in obj) {
+				this._set(obj.width || 0, obj.height || 0);
+			} else if ('angle' in obj) {
+				this._set(obj.length || 0, 0);
+				this.setAngle(obj.angle || 0);
+			} else {
+				this._set(0, 0);
+				read = 0;
+			}
+		}
+		if (reading)
+			this.__read = read;
+		return this;
+	},
+
+	set: '#initialize',
+
+	_set: function(x, y) {
 		this.x = x;
 		this.y = y;
 		return this;
@@ -1170,7 +1272,7 @@
 	setLength: function(length) {
 		if (this.isZero()) {
 			var angle = this._angle || 0;
-			this.set(
+			this._set(
 				Math.cos(angle) * length,
 				Math.sin(angle) * length
 			);
@@ -1178,7 +1280,7 @@
 			var scale = length / this.getLength();
 			if (Numerical.isZero(scale))
 				this.getAngle();
-			this.set(
+			this._set(
 				this.x * scale,
 				this.y * scale
 			);
@@ -1216,7 +1318,7 @@
 		this._angle = angle;
 		if (!this.isZero()) {
 			var length = this.getLength();
-			this.set(
+			this._set(
 				Math.cos(angle) * length,
 				Math.sin(angle) * length
 			);
@@ -1259,11 +1361,11 @@
 			return this.clone();
 		angle = angle * Math.PI / 180;
 		var point = center ? this.subtract(center) : this,
-			s = Math.sin(angle),
-			c = Math.cos(angle);
+			sin = Math.sin(angle),
+			cos = Math.cos(angle);
 		point = new Point(
-			point.x * c - point.y * s,
-			point.x * s + point.y * c
+			point.x * cos - point.y * sin,
+			point.x * sin + point.y * cos
 		);
 		return center ? point.add(center) : point;
 	},
@@ -1305,28 +1407,38 @@
 		return Rectangle.read(arguments).contains(this);
 	},
 
-	isClose: function(point, tolerance) {
-		return this.getDistance(point) < tolerance;
-	},
-
-	isCollinear: function(point) {
-		return Math.abs(this.cross(point)) < 0.000001;
+	isClose: function() {
+		var point = Point.read(arguments),
+			tolerance = Base.read(arguments);
+		return this.getDistance(point) <= tolerance;
+	},
+
+	isCollinear: function() {
+		var point = Point.read(arguments);
+		return Point.isCollinear(this.x, this.y, point.x, point.y);
 	},
 
 	isColinear: '#isCollinear',
 
-	isOrthogonal: function(point) {
-		return Math.abs(this.dot(point)) < 0.000001;
+	isOrthogonal: function() {
+		var point = Point.read(arguments);
+		return Point.isOrthogonal(this.x, this.y, point.x, point.y);
 	},
 
 	isZero: function() {
-		return Numerical.isZero(this.x) && Numerical.isZero(this.y);
+		var isZero = Numerical.isZero;
+		return isZero(this.x) && isZero(this.y);
 	},
 
 	isNaN: function() {
 		return isNaN(this.x) || isNaN(this.y);
 	},
 
+	isInQuadrant: function(q) {
+		return this.x * (q > 1 && q < 4 ? -1 : 1) >= 0
+			&& this.y * (q > 2 ? -1 : 1) >= 0;
+	},
+
 	dot: function() {
 		var point = Point.read(arguments);
 		return this.x * point.x + this.y * point.y;
@@ -1338,16 +1450,12 @@
 	},
 
 	project: function() {
-		var point = Point.read(arguments);
-		if (point.isZero()) {
-			return new Point(0, 0);
-		} else {
-			var scale = this.dot(point) / point.dot(point);
-			return new Point(
-				point.x * scale,
-				point.y * scale
-			);
-		}
+		var point = Point.read(arguments),
+			scale = point.isZero() ? 0 : this.dot(point) / point.dot(point);
+		return new Point(
+			point.x * scale,
+			point.y * scale
+		);
 	},
 
 	statics: {
@@ -1371,11 +1479,23 @@
 
 		random: function() {
 			return new Point(Math.random(), Math.random());
-		}
-	}
-}, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
-	var op = Math[name];
-	this[name] = function() {
+		},
+
+		isCollinear: function(x1, y1, x2, y2) {
+			return Math.abs(x1 * y2 - y1 * x2)
+					<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+						* 1e-8;
+		},
+
+		isOrthogonal: function(x1, y1, x2, y2) {
+			return Math.abs(x1 * x2 + y1 * y2)
+					<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+						* 1e-8;
+		}
+	}
+}, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) {
+	var op = Math[key];
+	this[key] = function() {
 		return new Point(op(this.x), op(this.y));
 	};
 }, {}));
@@ -1388,7 +1508,7 @@
 		this._setter = setter;
 	},
 
-	set: function(x, y, _dontNotify) {
+	_set: function(x, y, _dontNotify) {
 		this._x = x;
 		this._y = y;
 		if (!_dontNotify)
@@ -1412,6 +1532,18 @@
 	setY: function(y) {
 		this._y = y;
 		this._owner[this._setter](this);
+	},
+
+	isSelected: function() {
+		return !!(this._owner._selection & this._getSelection());
+	},
+
+	setSelected: function(selected) {
+		this._owner._changeSelection(this._getSelection(), selected);
+	},
+
+	_getSelection: function() {
+		return this._setter === 'setPosition' ? 4 : 0;
 	}
 });
 
@@ -1420,38 +1552,40 @@
 	_readIndex: true,
 
 	initialize: function Size(arg0, arg1) {
-		var type = typeof arg0;
+		var type = typeof arg0,
+			reading = this.__read,
+			read = 0;
 		if (type === 'number') {
 			var hasHeight = typeof arg1 === 'number';
-			this.width = arg0;
-			this.height = hasHeight ? arg1 : arg0;
-			if (this.__read)
-				this.__read = hasHeight ? 2 : 1;
+			this._set(arg0, hasHeight ? arg1 : arg0);
+			if (reading)
+				read = hasHeight ? 2 : 1;
 		} else if (type === 'undefined' || arg0 === null) {
-			this.width = this.height = 0;
-			if (this.__read)
-				this.__read = arg0 === null ? 1 : 0;
+			this._set(0, 0);
+			if (reading)
+				read = arg0 === null ? 1 : 0;
 		} else {
-			if (Array.isArray(arg0)) {
-				this.width = arg0[0];
-				this.height = arg0.length > 1 ? arg0[1] : arg0[0];
-			} else if (arg0.width != null) {
-				this.width = arg0.width;
-				this.height = arg0.height;
-			} else if (arg0.x != null) {
-				this.width = arg0.x;
-				this.height = arg0.y;
-			} else {
-				this.width = this.height = 0;
-				if (this.__read)
-					this.__read = 0;
-			}
-			if (this.__read)
-				this.__read = 1;
-		}
-	},
-
-	set: function(width, height) {
+			var obj = type === 'string' ? arg0.split(/[\s,]+/) || [] : arg0;
+			read = 1;
+			if (Array.isArray(obj)) {
+				this._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0]));
+			} else if ('width' in obj) {
+				this._set(obj.width || 0, obj.height || 0);
+			} else if ('x' in obj) {
+				this._set(obj.x || 0, obj.y || 0);
+			} else {
+				this._set(0, 0);
+				read = 0;
+			}
+		}
+		if (reading)
+			this.__read = read;
+		return this;
+	},
+
+	set: '#initialize',
+
+	_set: function(width, height) {
 		this.width = width;
 		this.height = height;
 		return this;
@@ -1510,7 +1644,8 @@
 	},
 
 	isZero: function() {
-		return Numerical.isZero(this.width) && Numerical.isZero(this.height);
+		var isZero = Numerical.isZero;
+		return isZero(this.width) && isZero(this.height);
 	},
 
 	isNaN: function() {
@@ -1534,9 +1669,9 @@
 			return new Size(Math.random(), Math.random());
 		}
 	}
-}, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
-	var op = Math[name];
-	this[name] = function() {
+}, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) {
+	var op = Math[key];
+	this[key] = function() {
 		return new Size(op(this.width), op(this.height));
 	};
 }, {}));
@@ -1549,7 +1684,7 @@
 		this._setter = setter;
 	},
 
-	set: function(width, height, _dontNotify) {
+	_set: function(width, height, _dontNotify) {
 		this._width = width;
 		this._height = height;
 		if (!_dontNotify)
@@ -1583,64 +1718,66 @@
 
 	initialize: function Rectangle(arg0, arg1, arg2, arg3) {
 		var type = typeof arg0,
-			read = 0;
+			read;
 		if (type === 'number') {
-			this.x = arg0;
-			this.y = arg1;
-			this.width = arg2;
-			this.height = arg3;
+			this._set(arg0, arg1, arg2, arg3);
 			read = 4;
 		} else if (type === 'undefined' || arg0 === null) {
-			this.x = this.y = this.width = this.height = 0;
+			this._set(0, 0, 0, 0);
 			read = arg0 === null ? 1 : 0;
 		} else if (arguments.length === 1) {
 			if (Array.isArray(arg0)) {
-				this.x = arg0[0];
-				this.y = arg0[1];
-				this.width = arg0[2];
-				this.height = arg0[3];
+				this._set.apply(this, arg0);
 				read = 1;
 			} else if (arg0.x !== undefined || arg0.width !== undefined) {
-				this.x = arg0.x || 0;
-				this.y = arg0.y || 0;
-				this.width = arg0.width || 0;
-				this.height = arg0.height || 0;
+				this._set(arg0.x || 0, arg0.y || 0,
+						arg0.width || 0, arg0.height || 0);
 				read = 1;
 			} else if (arg0.from === undefined && arg0.to === undefined) {
-				this.x = this.y = this.width = this.height = 0;
-				this._set(arg0);
+				this._set(0, 0, 0, 0);
+				Base.filter(this, arg0);
 				read = 1;
 			}
 		}
-		if (!read) {
-			var point = Point.readNamed(arguments, 'from'),
-				next = Base.peek(arguments);
-			this.x = point.x;
-			this.y = point.y;
-			if (next && next.x !== undefined || Base.hasNamed(arguments, 'to')) {
+		if (read === undefined) {
+			var frm = Point.readNamed(arguments, 'from'),
+				next = Base.peek(arguments),
+				x = frm.x,
+				y = frm.y,
+				width,
+				height;
+			if (next && next.x !== undefined
+					|| Base.hasNamed(arguments, 'to')) {
 				var to = Point.readNamed(arguments, 'to');
-				this.width = to.x - point.x;
-				this.height = to.y - point.y;
-				if (this.width < 0) {
-					this.x = to.x;
-					this.width = -this.width;
-				}
-				if (this.height < 0) {
-					this.y = to.y;
-					this.height = -this.height;
+				width = to.x - x;
+				height = to.y - y;
+				if (width < 0) {
+					x = to.x;
+					width = -width;
+				}
+				if (height < 0) {
+					y = to.y;
+					height = -height;
 				}
 			} else {
 				var size = Size.read(arguments);
-				this.width = size.width;
-				this.height = size.height;
-			}
+				width = size.width;
+				height = size.height;
+			}
+			this._set(x, y, width, height);
 			read = arguments.__index;
+			var filtered = arguments.__filtered;
+			if (filtered)
+				this.__filtered = filtered;
 		}
 		if (this.__read)
 			this.__read = read;
-	},
-
-	set: function(x, y, width, height) {
+		return this;
+	},
+
+	set: '#initialize',
+
+	_set: function(x, y, width, height) {
 		this.x = x;
 		this.y = y;
 		this.width = width;
@@ -1695,16 +1832,24 @@
 		return new ctor(this.width, this.height, this, 'setSize');
 	},
 
+	_fw: 1,
+	_fh: 1,
+
 	setSize: function() {
-		var size = Size.read(arguments);
-		if (this._fixX)
-			this.x += (this.width - size.width) * this._fixX;
-		if (this._fixY)
-			this.y += (this.height - size.height) * this._fixY;
-		this.width = size.width;
-		this.height = size.height;
-		this._fixW = 1;
-		this._fixH = 1;
+		var size = Size.read(arguments),
+			sx = this._sx,
+			sy = this._sy,
+			w = size.width,
+			h = size.height;
+		if (sx) {
+			this.x += (this.width - w) * sx;
+		}
+		if (sy) {
+			this.y += (this.height - h) * sy;
+		}
+		this.width = w;
+		this.height = h;
+		this._fw = this._fh = 1;
 	},
 
 	getLeft: function() {
@@ -1712,10 +1857,12 @@
 	},
 
 	setLeft: function(left) {
-		if (!this._fixW)
-			this.width -= left - this.x;
+		if (!this._fw) {
+			var amount = left - this.x;
+			this.width -= this._sx === 0.5 ? amount * 2 : amount;
+		}
 		this.x = left;
-		this._fixX = 0;
+		this._sx = this._fw = 0;
 	},
 
 	getTop: function() {
@@ -1723,10 +1870,12 @@
 	},
 
 	setTop: function(top) {
-		if (!this._fixH)
-			this.height -= top - this.y;
+		if (!this._fh) {
+			var amount = top - this.y;
+			this.height -= this._sy === 0.5 ? amount * 2 : amount;
+		}
 		this.y = top;
-		this._fixY = 0;
+		this._sy = this._fh = 0;
 	},
 
 	getRight: function() {
@@ -1734,13 +1883,13 @@
 	},
 
 	setRight: function(right) {
-		if (this._fixX !== undefined && this._fixX !== 1)
-			this._fixW = 0;
-		if (this._fixW)
-			this.x = right - this.width;
-		else
-			this.width = right - this.x;
-		this._fixX = 1;
+		if (!this._fw) {
+			var amount = right - this.x;
+			this.width = this._sx === 0.5 ? amount * 2 : amount;
+		}
+		this.x = right - this.width;
+		this._sx = 1;
+		this._fw = 0;
 	},
 
 	getBottom: function() {
@@ -1748,31 +1897,47 @@
 	},
 
 	setBottom: function(bottom) {
-		if (this._fixY !== undefined && this._fixY !== 1)
-			this._fixH = 0;
-		if (this._fixH)
-			this.y = bottom - this.height;
-		else
-			this.height = bottom - this.y;
-		this._fixY = 1;
+		if (!this._fh) {
+			var amount = bottom - this.y;
+			this.height = this._sy === 0.5 ? amount * 2 : amount;
+		}
+		this.y = bottom - this.height;
+		this._sy = 1;
+		this._fh = 0;
 	},
 
 	getCenterX: function() {
-		return this.x + this.width * 0.5;
+		return this.x + this.width / 2;
 	},
 
 	setCenterX: function(x) {
-		this.x = x - this.width * 0.5;
-		this._fixX = 0.5;
+		if (this._fw || this._sx === 0.5) {
+			this.x = x - this.width / 2;
+		} else {
+			if (this._sx) {
+				this.x += (x - this.x) * 2 * this._sx;
+			}
+			this.width = (x - this.x) * 2;
+		}
+		this._sx = 0.5;
+		this._fw = 0;
 	},
 
 	getCenterY: function() {
-		return this.y + this.height * 0.5;
+		return this.y + this.height / 2;
 	},
 
 	setCenterY: function(y) {
-		this.y = y - this.height * 0.5;
-		this._fixY = 0.5;
+		if (this._fh || this._sy === 0.5) {
+			this.y = y - this.height / 2;
+		} else {
+			if (this._sy) {
+				this.y += (y - this.y) * 2 * this._sy;
+			}
+			this.height = (y - this.y) * 2;
+		}
+		this._sy = 0.5;
+		this._fh = 0;
 	},
 
 	getCenter: function(_dontLink) {
@@ -1797,7 +1962,7 @@
 
 	contains: function(arg) {
 		return arg && arg.width !== undefined
-				|| (Array.isArray(arg) ? arg : arguments).length == 4
+				|| (Array.isArray(arg) ? arg : arguments).length === 4
 				? this._containsRectangle(Rectangle.read(arguments))
 				: this._containsPoint(Point.read(arguments));
 	},
@@ -1819,19 +1984,12 @@
 	},
 
 	intersects: function() {
-		var 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;
-	},
-
-	touches: function() {
-		var 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;
+		var rect = Rectangle.read(arguments),
+			epsilon = Base.read(arguments) || 0;
+		return rect.x + rect.width > this.x - epsilon
+				&& rect.y + rect.height > this.y - epsilon
+				&& rect.x < this.x + this.width + epsilon
+				&& rect.y < this.y + this.height + epsilon;
 	},
 
 	intersect: function() {
@@ -1880,8 +2038,8 @@
 		['Right', 'Center'], ['Bottom', 'Center']
 	],
 	function(parts, index) {
-		var part = parts.join('');
-		var xFirst = /^[RL]/.test(part);
+		var part = parts.join(''),
+			xFirst = /^[RL]/.test(part);
 		if (index >= 4)
 			parts[1] += xFirst ? 'Y' : 'X';
 		var x = parts[xFirst ? 0 : 1],
@@ -1908,12 +2066,12 @@
 
 var LinkedRectangle = Rectangle.extend({
 	initialize: function Rectangle(x, y, width, height, owner, setter) {
-		this.set(x, y, width, height, true);
+		this._set(x, y, width, height, true);
 		this._owner = owner;
 		this._setter = setter;
 	},
 
-	set: function(x, y, width, height, _dontNotify) {
+	_set: function(x, y, width, height, _dontNotify) {
 		this._x = x;
 		this._y = y;
 		this._width = width;
@@ -1922,12 +2080,13 @@
 			this._owner[this._setter](this);
 		return this;
 	}
-}, new function() {
+},
+new function() {
 	var proto = Rectangle.prototype;
 
 	return Base.each(['x', 'y', 'width', 'height'], function(key) {
-		var part = Base.capitalize(key);
-		var internal = '_' + key;
+		var part = Base.capitalize(key),
+			internal = '_' + key;
 		this['get' + part] = function() {
 			return this[internal];
 		};
@@ -1951,14 +2110,13 @@
 			};
 		}, {
 			isSelected: function() {
-				return this._owner._boundsSelected;
+				return !!(this._owner._selection & 2);
 			},
 
 			setSelected: function(selected) {
 				var owner = this._owner;
-				if (owner.setSelected) {
-					owner._boundsSelected = selected;
-					owner.setSelected(selected || owner._selectedSegmentState > 0);
+				if (owner._changeSelection) {
+					owner._changeSelection(2, selected);
 				}
 			}
 		})
@@ -1968,32 +2126,38 @@
 var Matrix = Base.extend({
 	_class: 'Matrix',
 
-	initialize: function Matrix(arg) {
+	initialize: function Matrix(arg, _dontNotify) {
 		var count = arguments.length,
 			ok = true;
-		if (count === 6) {
-			this.set.apply(this, arguments);
-		} else if (count === 1) {
+		if (count >= 6) {
+			this._set.apply(this, arguments);
+		} else if (count === 1 || count === 2) {
 			if (arg instanceof Matrix) {
-				this.set(arg._a, arg._c, arg._b, arg._d, arg._tx, arg._ty);
+				this._set(arg._a, arg._b, arg._c, arg._d, arg._tx, arg._ty,
+						_dontNotify);
 			} else if (Array.isArray(arg)) {
-				this.set.apply(this, arg);
+				this._set.apply(this,
+						_dontNotify ? arg.concat([_dontNotify]) : arg);
 			} else {
 				ok = false;
 			}
-		} else if (count === 0) {
+		} else if (!count) {
 			this.reset();
 		} else {
 			ok = false;
 		}
-		if (!ok)
+		if (!ok) {
 			throw new Error('Unsupported matrix parameters');
-	},
-
-	set: function(a, c, b, d, tx, ty, _dontNotify) {
+		}
+		return this;
+	},
+
+	set: '#initialize',
+
+	_set: function(a, b, c, d, tx, ty, _dontNotify) {
 		this._a = a;
+		this._b = b;
 		this._c = c;
-		this._b = b;
 		this._d = d;
 		this._tx = tx;
 		this._ty = ty;
@@ -2002,8 +2166,8 @@
 		return this;
 	},
 
-	_serialize: function(options) {
-		return Base.serialize(this.getValues(), options);
+	_serialize: function(options, dictionary) {
+		return Base.serialize(this.getValues(), options, true, dictionary);
 	},
 
 	_changed: function() {
@@ -2012,34 +2176,33 @@
 			if (owner._applyMatrix) {
 				owner.transform(null, true);
 			} else {
-				owner._changed(9);
+				owner._changed(25);
 			}
 		}
 	},
 
 	clone: function() {
-		return new Matrix(this._a, this._c, this._b, this._d,
+		return new Matrix(this._a, this._b, this._c, this._d,
 				this._tx, this._ty);
 	},
 
 	equals: function(mx) {
 		return mx === this || mx && this._a === mx._a && this._b === mx._b
 				&& this._c === mx._c && this._d === mx._d
-				&& this._tx === mx._tx && this._ty === mx._ty
-				|| false;
+				&& this._tx === mx._tx && this._ty === mx._ty;
 	},
 
 	toString: function() {
 		var f = Formatter.instance;
-		return '[[' + [f.number(this._a), f.number(this._b),
+		return '[[' + [f.number(this._a), f.number(this._c),
 					f.number(this._tx)].join(', ') + '], ['
-				+ [f.number(this._c), f.number(this._d),
+				+ [f.number(this._b), f.number(this._d),
 					f.number(this._ty)].join(', ') + ']]';
 	},
 
 	reset: function(_dontNotify) {
 		this._a = this._d = 1;
-		this._c = this._b = this._tx = this._ty = 0;
+		this._b = this._c = this._tx = this._ty = 0;
 		if (!_dontNotify)
 			this._changed();
 		return this;
@@ -2059,8 +2222,8 @@
 		var point = Point.read(arguments),
 			x = point.x,
 			y = point.y;
-		this._tx += x * this._a + y * this._b;
-		this._ty += x * this._c + y * this._d;
+		this._tx += x * this._a + y * this._c;
+		this._ty += x * this._b + y * this._d;
 		this._changed();
 		return this;
 	},
@@ -2071,8 +2234,8 @@
 		if (center)
 			this.translate(center);
 		this._a *= scale.x;
-		this._c *= scale.x;
-		this._b *= scale.y;
+		this._b *= scale.x;
+		this._c *= scale.y;
 		this._d *= scale.y;
 		if (center)
 			this.translate(center.negate());
@@ -2093,12 +2256,12 @@
 			b = this._b,
 			c = this._c,
 			d = this._d;
-		this._a = cos * a + sin * b;
-		this._b = -sin * a + cos * b;
-		this._c = cos * c + sin * d;
-		this._d = -sin * c + cos * d;
-		this._tx += tx * a + ty * b;
-		this._ty += tx * c + ty * d;
+		this._a = cos * a + sin * c;
+		this._b = cos * b + sin * d;
+		this._c = -sin * a + cos * c;
+		this._d = -sin * b + cos * d;
+		this._tx += tx * a + ty * c;
+		this._ty += tx * b + ty * d;
 		this._changed();
 		return this;
 	},
@@ -2109,11 +2272,11 @@
 		if (center)
 			this.translate(center);
 		var a = this._a,
-			c = this._c;
-		this._a += shear.y * this._b;
-		this._c += shear.y * this._d;
-		this._b += shear.x * a;
-		this._d += shear.x * c;
+			b = this._b;
+		this._a += shear.y * this._c;
+		this._b += shear.y * this._d;
+		this._c += shear.x * a;
+		this._d += shear.x * b;
 		if (center)
 			this.translate(center.negate());
 		this._changed();
@@ -2129,87 +2292,113 @@
 		return this.shear(shear, center);
 	},
 
-	concatenate: function(mx) {
-		var a1 = this._a,
-			b1 = this._b,
-			c1 = this._c,
-			d1 = this._d,
-			a2 = mx._a,
-			b2 = mx._b,
-			c2 = mx._c,
-			d2 = mx._d,
-			tx2 = mx._tx,
-			ty2 = mx._ty;
-		this._a = a2 * a1 + c2 * b1;
-		this._b = b2 * a1 + d2 * b1;
-		this._c = a2 * c1 + c2 * d1;
-		this._d = b2 * c1 + d2 * d1;
-		this._tx += tx2 * a1 + ty2 * b1;
-		this._ty += tx2 * c1 + ty2 * d1;
-		this._changed();
+	append: function(mx, _dontNotify) {
+		if (mx) {
+			var a1 = this._a,
+				b1 = this._b,
+				c1 = this._c,
+				d1 = this._d,
+				a2 = mx._a,
+				b2 = mx._c,
+				c2 = mx._b,
+				d2 = mx._d,
+				tx2 = mx._tx,
+				ty2 = mx._ty;
+			this._a = a2 * a1 + c2 * c1;
+			this._c = b2 * a1 + d2 * c1;
+			this._b = a2 * b1 + c2 * d1;
+			this._d = b2 * b1 + d2 * d1;
+			this._tx += tx2 * a1 + ty2 * c1;
+			this._ty += tx2 * b1 + ty2 * d1;
+			if (!_dontNotify)
+				this._changed();
+		}
 		return this;
 	},
 
-	preConcatenate: function(mx) {
-		var a1 = this._a,
-			b1 = this._b,
-			c1 = this._c,
-			d1 = this._d,
-			tx1 = this._tx,
-			ty1 = this._ty,
-			a2 = mx._a,
-			b2 = mx._b,
-			c2 = mx._c,
-			d2 = mx._d,
-			tx2 = mx._tx,
-			ty2 = mx._ty;
-		this._a = a2 * a1 + b2 * c1;
-		this._b = a2 * b1 + b2 * d1;
-		this._c = c2 * a1 + d2 * c1;
-		this._d = c2 * b1 + d2 * d1;
-		this._tx = a2 * tx1 + b2 * ty1 + tx2;
-		this._ty = c2 * tx1 + d2 * ty1 + ty2;
-		this._changed();
+	prepend: function(mx, _dontNotify) {
+		if (mx) {
+			var a1 = this._a,
+				b1 = this._b,
+				c1 = this._c,
+				d1 = this._d,
+				tx1 = this._tx,
+				ty1 = this._ty,
+				a2 = mx._a,
+				b2 = mx._c,
+				c2 = mx._b,
+				d2 = mx._d,
+				tx2 = mx._tx,
+				ty2 = mx._ty;
+			this._a = a2 * a1 + b2 * b1;
+			this._c = a2 * c1 + b2 * d1;
+			this._b = c2 * a1 + d2 * b1;
+			this._d = c2 * c1 + d2 * d1;
+			this._tx = a2 * tx1 + b2 * ty1 + tx2;
+			this._ty = c2 * tx1 + d2 * ty1 + ty2;
+			if (!_dontNotify)
+				this._changed();
+		}
 		return this;
 	},
 
-	chain: function(mx) {
-		var a1 = this._a,
-			b1 = this._b,
-			c1 = this._c,
-			d1 = this._d,
-			tx1 = this._tx,
-			ty1 = this._ty,
-			a2 = mx._a,
-			b2 = mx._b,
-			c2 = mx._c,
-			d2 = mx._d,
-			tx2 = mx._tx,
-			ty2 = mx._ty;
-		return new Matrix(
-				a2 * a1 + c2 * b1,
-				a2 * c1 + c2 * d1,
-				b2 * a1 + d2 * b1,
-				b2 * c1 + d2 * d1,
-				tx1 + tx2 * a1 + ty2 * b1,
-				ty1 + tx2 * c1 + ty2 * d1);
+	appended: function(mx) {
+		return this.clone().append(mx);
+	},
+
+	prepended: function(mx) {
+		return this.clone().prepend(mx);
+	},
+
+	invert: function() {
+		var a = this._a,
+			b = this._b,
+			c = this._c,
+			d = this._d,
+			tx = this._tx,
+			ty = this._ty,
+			det = a * d - b * c,
+			res = null;
+		if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) {
+			this._a = d / det;
+			this._b = -b / det;
+			this._c = -c / det;
+			this._d = a / det;
+			this._tx = (c * ty - d * tx) / det;
+			this._ty = (b * tx - a * ty) / det;
+			res = this;
+		}
+		return res;
+	},
+
+	inverted: function() {
+		return this.clone().invert();
+	},
+
+	concatenate: '#append',
+	preConcatenate: '#prepend',
+	chain: '#appended',
+
+	_shiftless: function() {
+		return new Matrix(this._a, this._b, this._c, this._d, 0, 0);
+	},
+
+	_orNullIfIdentity: function() {
+		return this.isIdentity() ? null : this;
 	},
 
 	isIdentity: function() {
-		return this._a === 1 && this._c === 0 && this._b === 0 && this._d === 1
+		return this._a === 1 && this._b === 0 && this._c === 0 && this._d === 1
 				&& this._tx === 0 && this._ty === 0;
 	},
 
-	orNullIfIdentity: function() {
-		return this.isIdentity() ? null : this;
-	},
-
 	isInvertible: function() {
-		return !!this._getDeterminant();
+		var det = this._a * this._d - this._c * this._b;
+		return det && !isNaN(det) && isFinite(this._tx) && isFinite(this._ty);
 	},
 
 	isSingular: function() {
-		return !this._getDeterminant();
+		return !this.isInvertible();
 	},
 
 	transform: function( src, dst, count) {
@@ -2223,22 +2412,18 @@
 			y = point.y;
 		if (!dest)
 			dest = new Point();
-		return dest.set(
-			x * this._a + y * this._b + this._tx,
-			x * this._c + y * this._d + this._ty,
-			_dontNotify
-		);
+		return dest._set(
+				x * this._a + y * this._c + this._tx,
+				x * this._b + y * this._d + this._ty,
+				_dontNotify);
 	},
 
 	_transformCoordinates: function(src, dst, count) {
-		var i = 0,
-			j = 0,
-			max = 2 * count;
-		while (i < max) {
-			var x = src[i++],
-				y = src[i++];
-			dst[j++] = x * this._a + y * this._b + this._tx;
-			dst[j++] = x * this._c + y * this._d + this._ty;
+		for (var i = 0, max = 2 * count; i < max; i += 2) {
+			var x = src[i],
+				y = src[i + 1];
+			dst[i] = x * this._a + y * this._c + this._tx;
+			dst[i + 1] = x * this._b + y * this._d + this._ty;
 		}
 		return dst;
 	},
@@ -2255,18 +2440,19 @@
 	_transformBounds: function(bounds, dest, _dontNotify) {
 		var coords = this._transformCorners(bounds),
 			min = coords.slice(0, 2),
-			max = coords.slice();
+			max = min.slice();
 		for (var i = 2; i < 8; i++) {
 			var val = coords[i],
 				j = i & 1;
-			if (val < min[j])
+			if (val < min[j]) {
 				min[j] = val;
-			else if (val > max[j])
+			} else if (val > max[j]) {
 				max[j] = val;
+			}
 		}
 		if (!dest)
 			dest = new Rectangle();
-		return dest.set(min[0], min[1], max[0] - min[0], max[1] - min[1],
+		return dest._set(min[0], min[1], max[0] - min[0], max[1] - min[1],
 				_dontNotify);
 	},
 
@@ -2274,62 +2460,64 @@
 		return this._inverseTransform(Point.read(arguments));
 	},
 
-	_getDeterminant: function() {
-		var det = this._a * this._d - this._b * this._c;
-		return isFinite(det) && !Numerical.isZero(det)
-				&& isFinite(this._tx) && isFinite(this._ty)
-				? det : null;
-	},
-
 	_inverseTransform: function(point, dest, _dontNotify) {
-		var det = this._getDeterminant();
-		if (!det)
-			return null;
-		var x = point.x - this._tx,
-			y = point.y - this._ty;
-		if (!dest)
-			dest = new Point();
-		return dest.set(
-			(x * this._d - y * this._b) / det,
-			(y * this._a - x * this._c) / det,
-			_dontNotify
-		);
+		var a = this._a,
+			b = this._b,
+			c = this._c,
+			d = this._d,
+			tx = this._tx,
+			ty = this._ty,
+			det = a * d - b * c,
+			res = null;
+		if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) {
+			var x = point.x - this._tx,
+				y = point.y - this._ty;
+			if (!dest)
+				dest = new Point();
+			res = dest._set(
+					(x * d - y * c) / det,
+					(y * a - x * b) / det,
+					_dontNotify);
+		}
+		return res;
 	},
 
 	decompose: function() {
-		var a = this._a, b = this._b, c = this._c, d = this._d;
-		if (Numerical.isZero(a * d - b * c))
-			return null;
-
-		var scaleX = Math.sqrt(a * a + b * b);
-		a /= scaleX;
-		b /= scaleX;
-
-		var shear = a * c + b * d;
-		c -= a * shear;
-		d -= b * shear;
-
-		var scaleY = Math.sqrt(c * c + d * d);
-		c /= scaleY;
-		d /= scaleY;
-		shear /= scaleY;
-
-		if (a * d < b * c) {
-			a = -a;
-			b = -b;
-			shear = -shear;
-			scaleX = -scaleX;
-		}
-
+		var a = this._a,
+			b = this._b,
+			c = this._c,
+			d = this._d,
+			det = a * d - b * c,
+			sqrt = Math.sqrt,
+			atan2 = Math.atan2,
+			degrees = 180 / Math.PI,
+			rotate,
+			scale,
+			skew;
+		if (a !== 0 || b !== 0) {
+			var r = sqrt(a * a + b * b);
+			rotate = Math.acos(a / r) * (b > 0 ? 1 : -1);
+			scale = [r, det / r];
+			skew = [atan2(a * c + b * d, r * r), 0];
+		} else if (c !== 0 || d !== 0) {
+			var s = sqrt(c * c + d * d);
+			rotate = Math.asin(c / s)  * (d > 0 ? 1 : -1);
+			scale = [det / s, s];
+			skew = [0, atan2(a * c + b * d, s * s)];
+		} else {
+			rotate = 0;
+			skew = scale = [0, 0];
+		}
 		return {
-			scaling: new Point(scaleX, scaleY),
-			rotation: -Math.atan2(b, a) * 180 / Math.PI,
-			shearing: shear
+			translation: this.getTranslation(),
+			rotation: rotate * degrees,
+			scaling: new Point(scale),
+			skewing: new Point(skew[0] * degrees, skew[1] * degrees)
 		};
 	},
 
 	getValues: function() {
-		return [ this._a, this._c, this._b, this._d, this._tx, this._ty ];
+		return [ this._a, this._b, this._c, this._d, this._tx, this._ty ];
 	},
 
 	getTranslation: function() {
@@ -2337,34 +2525,22 @@
 	},
 
 	getScaling: function() {
-		return (this.decompose() || {}).scaling;
+		return this.decompose().scaling;
 	},
 
 	getRotation: function() {
-		return (this.decompose() || {}).rotation;
-	},
-
-	inverted: function() {
-		var det = this._getDeterminant();
-		return det && new Matrix(
-				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);
-	},
-
-	shiftless: function() {
-		return new Matrix(this._a, this._c, this._b, this._d, 0, 0);
+		return this.decompose().rotation;
 	},
 
 	applyToContext: function(ctx) {
-		ctx.transform(this._a, this._c, this._b, this._d, this._tx, this._ty);
-	}
-}, Base.each(['a', 'c', 'b', 'd', 'tx', 'ty'], function(name) {
-	var part = Base.capitalize(name),
-		prop = '_' + name;
+		if (!this.isIdentity()) {
+			ctx.transform(this._a, this._b, this._c, this._d,
+					this._tx, this._ty);
+		}
+	}
+}, Base.each(['a', 'b', 'c', 'd', 'tx', 'ty'], function(key) {
+	var part = Base.capitalize(key),
+		prop = '_' + key;
 	this['get' + part] = function() {
 		return this[prop];
 	};
@@ -2417,41 +2593,60 @@
 				true, isInfinite);
 	},
 
-	getSide: function(point) {
+	getSide: function(point, isInfinite) {
 		return Line.getSide(
 				this._px, this._py, this._vx, this._vy,
-				point.x, point.y, true);
+				point.x, point.y, true, isInfinite);
 	},
 
 	getDistance: function(point) {
-		return Math.abs(Line.getSignedDistance(
-				this._px, this._py, this._vx, this._vy,
-				point.x, point.y, true));
+		return Math.abs(this.getSignedDistance(point));
+	},
+
+	getSignedDistance: function(point) {
+		return Line.getSignedDistance(this._px, this._py, this._vx, this._vy,
+				point.x, point.y, true);
+	},
+
+	isCollinear: function(line) {
+		return Point.isCollinear(this._vx, this._vy, line._vx, line._vy);
+	},
+
+	isOrthogonal: function(line) {
+		return Point.isOrthogonal(this._vx, this._vy, line._vx, line._vy);
 	},
 
 	statics: {
-		intersect: function(apx, apy, avx, avy, bpx, bpy, bvx, bvy, asVector,
+		intersect: function(p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector,
 				isInfinite) {
 			if (!asVector) {
-				avx -= apx;
-				avy -= apy;
-				bvx -= bpx;
-				bvy -= bpy;
-			}
-			var cross = avx * bvy - avy * bvx;
+				v1x -= p1x;
+				v1y -= p1y;
+				v2x -= p2x;
+				v2y -= p2y;
+			}
+			var cross = v1x * v2y - v1y * v2x;
 			if (!Numerical.isZero(cross)) {
-				var dx = apx - bpx,
-					dy = apy - bpy,
-					ta = (bvx * dy - bvy * dx) / cross,
-					tb = (avx * dy - avy * dx) / cross;
-				if (isInfinite || 0 <= ta && ta <= 1 && 0 <= tb && tb <= 1)
+				var dx = p1x - p2x,
+					dy = p1y - p2y,
+					u1 = (v2x * dy - v2y * dx) / cross,
+					u2 = (v1x * dy - v1y * dx) / cross,
+					epsilon = 1e-12,
+					uMin = -epsilon,
+					uMax = 1 + epsilon;
+				if (isInfinite
+						|| uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) {
+					if (!isInfinite) {
+						u1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1;
+					}
 					return new Point(
-								apx + ta * avx,
-								apy + ta * avy);
-			}
-		},
-
-		getSide: function(px, py, vx, vy, x, y, asVector) {
+							p1x + u1 * v1x,
+							p1y + u1 * v1y);
+				}
+			}
+		},
+
+		getSide: function(px, py, vx, vy, x, y, asVector, isInfinite) {
 			if (!asVector) {
 				vx -= px;
 				vy -= py;
@@ -2459,15 +2654,10 @@
 			var v2x = x - px,
 				v2y = y - py,
 				ccw = v2x * vy - v2y * vx;
-			if (ccw === 0) {
-				ccw = v2x * vx + v2y * vy;
-				if (ccw > 0) {
-					v2x -= vx;
-					v2y -= vy;
-					ccw = v2x * vx + v2y * vy;
-					if (ccw < 0)
-						ccw = 0;
-				}
+			if (!isInfinite && Numerical.isZero(ccw)) {
+				ccw = (v2x * vx + v2x * vx) / (vx * vx + vy * vy);
+				if (ccw >= 0 && ccw <= 1)
+					ccw = 0;
 			}
 			return ccw < 0 ? -1 : ccw > 0 ? 1 : 0;
 		},
@@ -2477,11 +2667,14 @@
 				vx -= px;
 				vy -= py;
 			}
-			return Numerical.isZero(vx)
-					? vy >= 0 ? px - x : x - px
-					: Numerical.isZero(vy)
-						? vx >= 0 ? y - py : py - y
-						: (vx * (y - py) - vy * (x - px)) / Math.sqrt(vx * vx + vy * vy);
+			return vx === 0 ? vy > 0 ? x - px : px - x
+				 : vy === 0 ? vx < 0 ? y - py : py - y
+				 : ((x-px) * vy - (y-py) * vx) / Math.sqrt(vx * vx + vy * vy);
+		},
+
+		getDistance: function(px, py, vx, vy, x, y, asVector) {
+			return Math.abs(
+					Line.getSignedDistance(px, py, vx, vy, x, y, asVector));
 		}
 	}
 });
@@ -2490,32 +2683,55 @@
 	_class: 'Project',
 	_list: 'projects',
 	_reference: 'project',
+	_compactSerialize: true,
 
 	initialize: function Project(element) {
 		PaperScopeItem.call(this, true);
-		this.layers = [];
+		this._children = [];
+		this._namedChildren = {};
 		this._activeLayer = null;
-		this.symbols = [];
 		this._currentStyle = new Style(null, null, this);
 		this._view = View.create(this,
 				element || CanvasProvider.getCanvas(1, 1));
-		this._selectedItems = {};
-		this._selectedItemCount = 0;
+		this._selectionItems = {};
+		this._selectionCount = 0;
 		this._updateVersion = 0;
 	},
 
 	_serialize: function(options, dictionary) {
-		return Base.serialize(this.layers, options, true, dictionary);
+		return Base.serialize(this._children, options, true, dictionary);
+	},
+
+	_changed: function(flags, item) {
+		if (flags & 1) {
+			var view = this._view;
+			if (view) {
+				view._needsUpdate = true;
+				if (!view._requested && view._autoUpdate)
+					view.requestUpdate();
+			}
+		}
+		var changes = this._changes;
+		if (changes && item) {
+			var changesById = this._changesById,
+				id = item._id,
+				entry = changesById[id];
+			if (entry) {
+				entry.flags |= flags;
+			} else {
+				changes.push(changesById[id] = { item: item, flags: flags });
+			}
+		}
 	},
 
 	clear: function() {
-		for (var i = this.layers.length - 1; i >= 0; i--)
-			this.layers[i].remove();
-		this.symbols = [];
+		var children = this._children;
+		for (var i = children.length - 1; i >= 0; i--)
+			children[i].remove();
 	},
 
 	isEmpty: function() {
-		return this.layers.length === 0;
+		return !this._children.length;
 	},
 
 	remove: function remove() {
@@ -2535,7 +2751,7 @@
 	},
 
 	setCurrentStyle: function(style) {
-		this._currentStyle.initialize(style);
+		this._currentStyle.set(style);
 	},
 
 	getIndex: function() {
@@ -2546,85 +2762,113 @@
 		return this._scope.settings;
 	},
 
+	getLayers: function() {
+		return this._children;
+	},
+
 	getActiveLayer: function() {
-		return this._activeLayer || new Layer({ project: this });
-	},
+		return this._activeLayer || new Layer({ project: this, insert: true });
+	},
+
+	getSymbolDefinitions: function() {
+		var definitions = [],
+			ids = {};
+		this.getItems({
+			class: SymbolItem,
+			match: function(item) {
+				var definition = item._definition,
+					id = definition._id;
+				if (!ids[id]) {
+					ids[id] = true;
+					definitions.push(definition);
+				}
+				return false;
+			}
+		});
+		return definitions;
+	},
+
+	getSymbols: 'getSymbolDefinitions',
 
 	getSelectedItems: function() {
-		var items = [];
-		for (var id in this._selectedItems) {
-			var item = this._selectedItems[id];
-			if (item.isInserted())
+		var selectionItems = this._selectionItems,
+			items = [];
+		for (var id in selectionItems) {
+			var item = selectionItems[id],
+				selection = item._selection;
+			if ((selection & 1) && item.isInserted()) {
 				items.push(item);
+			} else if (!selection) {
+				this._updateSelection(item);
+			}
 		}
 		return items;
 	},
 
-	insertChild: function(index, item, _preserve) {
-		if (item instanceof Layer) {
-			item._remove(false, true);
-			Base.splice(this.layers, [item], index, 0);
-			item._setProject(this, true);
-			if (this._changes)
-				item._changed(5);
-			if (!this._activeLayer)
-				this._activeLayer = item;
-		} else if (item instanceof Item) {
-			(this._activeLayer
-				|| this.insertChild(index, new Layer(Item.NO_INSERT)))
-					.insertChild(index, item, _preserve);
-		} else {
-			item = null;
-		}
-		return item;
-	},
-
-	addChild: function(item, _preserve) {
-		return this.insertChild(undefined, item, _preserve);
-	},
-
 	_updateSelection: function(item) {
 		var id = item._id,
-			selectedItems = this._selectedItems;
-		if (item._selected) {
-			if (selectedItems[id] !== item) {
-				this._selectedItemCount++;
-				selectedItems[id] = item;
-			}
-		} else if (selectedItems[id] === item) {
-			this._selectedItemCount--;
-			delete selectedItems[id];
+			selectionItems = this._selectionItems;
+		if (item._selection) {
+			if (selectionItems[id] !== item) {
+				this._selectionCount++;
+				selectionItems[id] = item;
+			}
+		} else if (selectionItems[id] === item) {
+			this._selectionCount--;
+			delete selectionItems[id];
 		}
 	},
 
 	selectAll: function() {
-		var layers = this.layers;
-		for (var i = 0, l = layers.length; i < l; i++)
-			layers[i].setFullySelected(true);
+		var children = this._children;
+		for (var i = 0, l = children.length; i < l; i++)
+			children[i].setFullySelected(true);
 	},
 
 	deselectAll: function() {
-		var selectedItems = this._selectedItems;
-		for (var i in selectedItems)
-			selectedItems[i].setFullySelected(false);
-	},
-
-	hitTest: function() {
-		var point = Point.read(arguments),
-			options = HitResult.getOptions(Base.read(arguments));
-		for (var i = this.layers.length - 1; i >= 0; i--) {
-			var res = this.layers[i]._hitTest(point, options);
-			if (res) return res;
-		}
-		return null;
-	},
-
-	getItems: function(match) {
-		return Item._getItems(this.layers, match);
-	},
-
-	getItem: function(match) {
-		return Item._getItems(this.layers, match, null, null, true)[0] || null;
+		var selectionItems = this._selectionItems;
+		for (var i in selectionItems)
+			selectionItems[i].setFullySelected(false);
+	},
+
+	addLayer: function(layer) {
+		return this.insertLayer(undefined, layer);
+	},
+
+	insertLayer: function(index, layer) {
+		if (layer instanceof Layer) {
+			layer._remove(false, true);
+			Base.splice(this._children, [layer], index, 0);
+			layer._setProject(this, true);
+			var name = layer._name;
+			if (name)
+				layer.setName(name);
+			if (this._changes)
+				layer._changed(5);
+			if (!this._activeLayer)
+				this._activeLayer = layer;
+		} else {
+			layer = null;
+		}
+		return layer;
+	},
+
+	_insertItem: function(index, item, _created) {
+		item = this.insertLayer(index, item)
+				|| (this._activeLayer || this._insertItem(undefined,
+						new Layer(Item.NO_INSERT), true))
+						.insertChild(index, item);
+		if (_created && item.activate)
+			item.activate();
+		return item;
+	},
+
+	getItems: function(options) {
+		return Item._getItems(this, options);
+	},
+
+	getItem: function(options) {
+		return Item._getItems(this, options, null, null, true)[0] || null;
 	},
 
 	importJSON: function(json) {
@@ -2633,100 +2877,64 @@
 		return Base.importJSON(json, layer && layer.isEmpty() && layer);
 	},
 
+	removeOn: function(type) {
+		var sets = this._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)
+							delete other[item._id];
+					}
+					item.remove();
+				}
+				sets[type] = null;
+			}
+		}
+	},
+
 	draw: function(ctx, matrix, pixelRatio) {
 		this._updateVersion++;
 		ctx.save();
 		matrix.applyToContext(ctx);
-		var param = new Base({
-			offset: new Point(0, 0),
-			pixelRatio: pixelRatio,
-			viewMatrix: matrix.isIdentity() ? null : matrix,
-			matrices: [new Matrix()],
-			updateMatrix: true
-		});
-		for (var i = 0, layers = this.layers, l = layers.length; i < l; i++)
-			layers[i].draw(ctx, param);
+		var children = this._children,
+			param = new Base({
+				offset: new Point(0, 0),
+				pixelRatio: pixelRatio,
+				viewMatrix: matrix.isIdentity() ? null : matrix,
+				matrices: [new Matrix()],
+				updateMatrix: true
+			});
+		for (var i = 0, l = children.length; i < l; i++) {
+			children[i].draw(ctx, param);
+		}
 		ctx.restore();
 
-		if (this._selectedItemCount > 0) {
+		if (this._selectionCount > 0) {
 			ctx.save();
 			ctx.strokeWidth = 1;
-			var items = this._selectedItems,
+			var items = this._selectionItems,
 				size = this._scope.settings.handleSize,
 				version = this._updateVersion;
-			for (var id in items)
+			for (var id in items) {
 				items[id]._drawSelection(ctx, matrix, size, items, version);
+			}
 			ctx.restore();
 		}
 	}
 });
 
-var Symbol = Base.extend({
-	_class: 'Symbol',
-
-	initialize: function Symbol(item, dontCenter) {
-		this._id = UID.get();
-		this.project = paper.project;
-		this.project.symbols.push(this);
-		if (item)
-			this.setDefinition(item, dontCenter);
-	},
-
-	_serialize: function(options, dictionary) {
-		return dictionary.add(this, function() {
-			return Base.serialize([this._class, this._definition],
-					options, false, dictionary);
-		});
-	},
-
-	_changed: function(flags) {
-		if (flags & 8) {
-			Item._clearBoundsCache(this);
-		}
-		if (flags & 1) {
-			this.project._needsUpdate = true;
-		}
-	},
-
-	getDefinition: function() {
-		return this._definition;
-	},
-
-	setDefinition: function(item, _dontCenter) {
-		if (item._parentSymbol)
-			item = item.clone();
-		if (this._definition)
-			this._definition._parentSymbol = null;
-		this._definition = item;
-		item.remove();
-		item.setSelected(false);
-		if (!_dontCenter)
-			item.setPosition(new Point());
-		item._parentSymbol = this;
-		this._changed(9);
-	},
-
-	place: function(position) {
-		return new PlacedSymbol(this, position);
-	},
-
-	clone: function() {
-		return new Symbol(this._definition.clone(false));
-	},
-
-	equals: function(symbol) {
-		return symbol === this
-				|| symbol && this.definition.equals(symbol.definition)
-				|| false;
-	}
-});
-
 var Item = Base.extend(Emitter, {
 	statics: {
 		extend: function extend(src) {
 			if (src._serializeFields)
-				src._serializeFields = new Base(
-						this.prototype._serializeFields, src._serializeFields);
+				src._serializeFields = Base.set({},
+					this.prototype._serializeFields, src._serializeFields);
 			return extend.base.apply(this, arguments);
 		},
 
@@ -2734,25 +2942,71 @@
 	},
 
 	_class: 'Item',
+	_name: null,
 	_applyMatrix: true,
 	_canApplyMatrix: true,
-	_boundsSelected: false,
+	_canScaleStroke: false,
+	_pivot: null,
+	_visible: true,
+	_blendMode: 'normal',
+	_opacity: 1,
+	_locked: false,
+	_guide: false,
+	_clipMask: false,
+	_selection: 0,
+	_selectBounds: true,
 	_selectChildren: false,
 	_serializeFields: {
 		name: null,
 		applyMatrix: null,
 		matrix: new Matrix(),
 		pivot: null,
-		locked: false,
 		visible: true,
 		blendMode: 'normal',
 		opacity: 1,
+		locked: false,
 		guide: false,
-		selected: false,
 		clipMask: false,
+		selected: false,
 		data: {}
 	},
-
+	_prioritize: ['applyMatrix']
+},
+new function() {
+	var handlers = ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
+			'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'];
+	return Base.each(handlers,
+		function(name) {
+			this._events[name] = {
+				install: function(type) {
+					this.getView()._countItemEvent(type, 1);
+				},
+
+				uninstall: function(type) {
+					this.getView()._countItemEvent(type, -1);
+				}
+			};
+		}, {
+			_events: {
+				onFrame: {
+					install: function() {
+						this.getView()._animateItem(this, true);
+					},
+
+					uninstall: function() {
+						this.getView()._animateItem(this, false);
+					}
+				},
+
+				onLoad: {},
+				onError: {}
+			},
+			statics: {
+				_itemHandlers: handlers
+			}
+		}
+	);
+}, {
 	initialize: function Item() {
 	},
 
@@ -2760,94 +3014,30 @@
 		var hasProps = props && Base.isPlainObject(props),
 			internal = hasProps && props.internal === true,
 			matrix = this._matrix = new Matrix(),
-			project = hasProps && props.project || paper.project;
-		if (!internal)
-			this._id = UID.get();
-		this._applyMatrix = this._canApplyMatrix && paper.settings.applyMatrix;
+			project = hasProps && props.project || paper.project,
+			settings = paper.settings;
+		this._id = internal ? null : UID.get();
+		this._parent = this._index = null;
+		this._applyMatrix = this._canApplyMatrix && settings.applyMatrix;
 		if (point)
 			matrix.translate(point);
 		matrix._owner = this;
 		this._style = new Style(project._currentStyle, this, project);
-		if (!this._project) {
-			if (internal || hasProps && props.insert === false) {
-				this._setProject(project);
-			} else if (hasProps && props.parent) {
-				this.setParent(props.parent);
-			} else {
-				(project._activeLayer || new Layer()).addChild(this);
-			}
-		}
-		if (hasProps && props !== Item.NO_INSERT)
-			this._set(props, { insert: true, project: true, parent: true },
-					true);
+		if (internal || hasProps && props.insert == false
+			|| !settings.insertItems && !(hasProps && props.insert === true)) {
+			this._setProject(project);
+		} else {
+			(hasProps && props.parent || project)
+					._insertItem(undefined, this, true);
+		}
+		if (hasProps && props !== Item.NO_INSERT) {
+			this.set(props, {
+				internal: true, insert: true, project: true, parent: true
+			});
+		}
 		return hasProps;
 	},
 
-	_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.getView()._eventCounters;
-				if (counters) {
-					for (var key in mouseFlags) {
-						counters[key] = (counters[key] || 0)
-								+ (mouseFlags[key][type] || 0);
-					}
-				}
-			},
-			uninstall: function(type) {
-				var counters = this.getView()._eventCounters;
-				if (counters) {
-					for (var key in mouseFlags)
-						counters[key] -= mouseFlags[key][type] || 0;
-				}
-			}
-		};
-
-		return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
-			'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
-			function(name) {
-				this[name] = mouseEvent;
-			}, {
-				onFrame: {
-					install: function() {
-						this._animateItem(true);
-					},
-					uninstall: function() {
-						this._animateItem(false);
-					}
-				},
-
-				onLoad: {}
-			}
-		);
-	},
-
-	_animateItem: function(animate) {
-		this.getView()._animateItem(this, animate);
-	},
-
 	_serialize: function(options, dictionary) {
 		var props = {},
 			that = this;
@@ -2870,45 +3060,28 @@
 	},
 
 	_changed: function(flags) {
-		var symbol = this._parentSymbol,
+		var symbol = this._symbol,
 			cacheParent = this._parent || symbol,
 			project = this._project;
 		if (flags & 8) {
-			this._bounds = this._position = this._decomposed =
-					this._globalMatrix = this._currentPath = undefined;
+			this._bounds = this._position = this._decomposed = undefined;
+		}
+		if (flags & 16) {
+			this._globalMatrix = undefined;
 		}
 		if (cacheParent
-				&& (flags & 40)) {
+				&& (flags & 72)) {
 			Item._clearBoundsCache(cacheParent);
 		}
 		if (flags & 2) {
 			Item._clearBoundsCache(this);
 		}
-		if (project) {
-			if (flags & 1) {
-				project._needsUpdate = true;
-			}
-			if (project._changes) {
-				var entry = project._changesById[this._id];
-				if (entry) {
-					entry.flags |= flags;
-				} else {
-					entry = { item: this, flags: flags };
-					project._changesById[this._id] = entry;
-					project._changes.push(entry);
-				}
-			}
-		}
+		if (project)
+			project._changed(flags, this);
 		if (symbol)
 			symbol._changed(flags);
 	},
 
-	set: function(props) {
-		if (props)
-			this._set(props);
-		return this;
-	},
-
 	getId: function() {
 		return this._id;
 	},
@@ -2917,26 +3090,23 @@
 		return this._name;
 	},
 
-	setName: function(name, unique) {
+	setName: function(name) {
 
 		if (this._name)
 			this._removeNamed();
 		if (name === (+name) + '')
 			throw new Error(
 					'Names consisting only of numbers are not supported.');
-		var parent = this._parent;
-		if (name && parent) {
-			var children = parent._children,
-				namedChildren = parent._namedChildren,
-				orig = name,
-				i = 1;
-			while (unique && children[name])
-				name = orig + ' ' + (i++);
+		var owner = this._getOwner();
+		if (name && owner) {
+			var children = owner._children,
+				namedChildren = owner._namedChildren;
 			(namedChildren[name] = namedChildren[name] || []).push(this);
-			children[name] = this;
+			if (!(name in children))
+				children[name] = this;
 		}
 		this._name = name || undefined;
-		this._changed(128);
+		this._changed(256);
 	},
 
 	getStyle: function() {
@@ -2949,30 +3119,43 @@
 }, Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'],
 	function(name) {
 		var part = Base.capitalize(name),
-			name = '_' + name;
+			key = '_' + name,
+			flags = {
+				locked: 256,
+				visible: 265
+			};
 		this['get' + part] = function() {
-			return this[name];
+			return this[key];
 		};
 		this['set' + part] = function(value) {
-			if (value != this[name]) {
-				this[name] = value;
-				this._changed(name === '_locked'
-						? 128 : 129);
+			if (value != this[key]) {
+				this[key] = value;
+				this._changed(flags[name] || 257);
 			}
 		};
 	},
 {}), {
 	beans: true,
 
-	_locked: false,
-
-	_visible: true,
-
-	_blendMode: 'normal',
-
-	_opacity: 1,
-
-	_guide: false,
+	getSelection: function() {
+		return this._selection;
+	},
+
+	setSelection: function(selection) {
+		if (selection !== this._selection) {
+			this._selection = selection;
+			var project = this._project;
+			if (project) {
+				project._updateSelection(this);
+				this._changed(257);
+			}
+		}
+	},
+
+	_changeSelection: function(flag, selected) {
+		var selection = this._selection;
+		this.setSelection(selected ? selection | flag : selection & ~flag);
+	},
 
 	isSelected: function() {
 		if (this._selectChildren) {
@@ -2981,33 +3164,28 @@
 				if (children[i].isSelected())
 					return true;
 		}
-		return this._selected;
-	},
-
-	setSelected: function(selected, noChildren) {
-		if (!noChildren && this._selectChildren) {
+		return !!(this._selection & 1);
+	},
+
+	setSelected: function(selected) {
+		if (this._selectChildren) {
 			var children = this._children;
 			for (var i = 0, l = children.length; i < l; i++)
 				children[i].setSelected(selected);
 		}
-		if ((selected = !!selected) ^ this._selected) {
-			this._selected = selected;
-			this._project._updateSelection(this);
-			this._changed(129);
-		}
-	},
-
-	_selected: false,
+		this._changeSelection(1, selected);
+	},
 
 	isFullySelected: function() {
-		var children = this._children;
-		if (children && this._selected) {
+		var children = this._children,
+			selected = !!(this._selection & 1);
+		if (children && selected) {
 			for (var i = 0, l = children.length; i < l; i++)
 				if (!children[i].isFullySelected())
 					return false;
 			return true;
 		}
-		return this._selected;
+		return selected;
 	},
 
 	setFullySelected: function(selected) {
@@ -3016,7 +3194,7 @@
 			for (var i = 0, l = children.length; i < l; i++)
 				children[i].setFullySelected(selected);
 		}
-		this.setSelected(selected, true);
+		this._changeSelection(1, selected);
 	},
 
 	isClipMask: function() {
@@ -3030,13 +3208,11 @@
 				this.setFillColor(null);
 				this.setStrokeColor(null);
 			}
-			this._changed(129);
+			this._changed(257);
 			if (this._parent)
-				this._parent._changed(1024);
-		}
-	},
-
-	_clipMask: false,
+				this._parent._changed(2048);
+		}
+	},
 
 	getData: function() {
 		if (!this._data)
@@ -3049,14 +3225,9 @@
 	},
 
 	getPosition: function(_dontLink) {
-		var position = this._position,
-			ctor = _dontLink ? Point : LinkedPoint;
-		if (!position) {
-			var pivot = this._pivot;
-			position = this._position = pivot
-					? this._matrix._transformPoint(pivot)
-					: this.getBounds().getCenter(true);
-		}
+		var ctor = _dontLink ? Point : LinkedPoint;
+		var position = this._position ||
+			(this._position = this._getPositionFromBounds());
 		return new ctor(position.x, position.y, this, 'setPosition');
 	},
 
@@ -3064,105 +3235,135 @@
 		this.translate(Point.read(arguments).subtract(this.getPosition(true)));
 	},
 
-	getPivot: function(_dontLink) {
+	_getPositionFromBounds: function(bounds) {
+		return this._pivot
+				? this._matrix._transformPoint(this._pivot)
+				: (bounds || this.getBounds()).getCenter(true);
+	},
+
+	getPivot: function() {
 		var pivot = this._pivot;
-		if (pivot) {
-			var ctor = _dontLink ? Point : LinkedPoint;
-			pivot = new ctor(pivot.x, pivot.y, this, 'setPivot');
-		}
-		return pivot;
+		return pivot
+				? new LinkedPoint(pivot.x, pivot.y, this, 'setPivot')
+				: null;
 	},
 
 	setPivot: function() {
 		this._pivot = Point.read(arguments, 0, { clone: true, readNull: true });
 		this._position = undefined;
-	},
-
-	_pivot: null,
-}, Base.each(['bounds', 'strokeBounds', 'handleBounds', 'roughBounds',
-		'internalBounds', 'internalRoughBounds'],
-	function(key) {
-		var getter = 'get' + Base.capitalize(key),
-			match = key.match(/^internal(.*)$/),
-			internalGetter = match ? 'get' + match[1] : null;
-		this[getter] = function(_matrix) {
-			var boundsGetter = this._boundsGetter,
-				name = !internalGetter && (typeof boundsGetter === 'string'
-						? boundsGetter : boundsGetter && boundsGetter[getter])
-						|| getter,
-				bounds = this._getCachedBounds(name, _matrix, this,
-						internalGetter);
-			return key === 'bounds'
-					? new LinkedRectangle(bounds.x, bounds.y, bounds.width,
-							bounds.height, this, 'setBounds')
-					: bounds;
+	}
+}, Base.each({
+		getStrokeBounds: { stroke: true },
+		getHandleBounds: { handle: true },
+		getInternalBounds: { internal: true }
+	},
+	function(options, key) {
+		this[key] = function(matrix) {
+			return this.getBounds(matrix, options);
 		};
 	},
 {
 	beans: true,
 
-	_getBounds: function(getter, matrix, cacheItem) {
-		var children = this._children;
-		if (!children || children.length == 0)
-			return new Rectangle();
-		Item._updateBoundsCache(this, cacheItem);
-		var x1 = Infinity,
-			x2 = -x1,
-			y1 = x1,
-			y2 = x2;
-		for (var i = 0, l = children.length; i < l; i++) {
-			var child = children[i];
-			if (child._visible && !child.isEmpty()) {
-				var rect = child._getCachedBounds(getter,
-						matrix && matrix.chain(child._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 isFinite(x1)
-				? new Rectangle(x1, y1, x2 - x1, y2 - y1)
-				: new Rectangle();
+	getBounds: function(matrix, options) {
+		var hasMatrix = options || matrix instanceof Matrix,
+			opts = Base.set({}, hasMatrix ? options : matrix,
+					this._boundsOptions);
+		if (!opts.stroke || this.getStrokeScaling())
+			opts.cacheItem = this;
+		var rect = this._getCachedBounds(hasMatrix && matrix, opts).rect;
+		return !arguments.length
+				? new LinkedRectangle(rect.x, rect.y, rect.width, rect.height,
+					this, 'setBounds')
+				: rect;
 	},
 
 	setBounds: function() {
 		var rect = Rectangle.read(arguments),
 			bounds = this.getBounds(),
+			_matrix = this._matrix,
 			matrix = new Matrix(),
 			center = rect.getCenter();
 		matrix.translate(center);
 		if (rect.width != bounds.width || rect.height != bounds.height) {
+			if (!_matrix.isInvertible()) {
+				_matrix.set(_matrix._backup
+						|| new Matrix().translate(_matrix.getTranslation()));
+				bounds = this.getBounds();
+			}
 			matrix.scale(
-					bounds.width != 0 ? rect.width / bounds.width : 1,
-					bounds.height != 0 ? rect.height / bounds.height : 1);
+					bounds.width !== 0 ? rect.width / bounds.width : 0,
+					bounds.height !== 0 ? rect.height / bounds.height : 0);
 		}
 		center = bounds.getCenter();
 		matrix.translate(-center.x, -center.y);
 		this.transform(matrix);
 	},
 
-	_getCachedBounds: function(getter, matrix, cacheItem, internalGetter) {
-		matrix = matrix && matrix.orNullIfIdentity();
-		var _matrix = internalGetter ? null : this._matrix.orNullIfIdentity(),
-			cache = (!matrix || matrix.equals(_matrix)) && getter;
-		Item._updateBoundsCache(this._parent || this._parentSymbol, cacheItem);
-		if (cache && this._bounds && this._bounds[cache])
-			return this._bounds[cache].clone();
-		var bounds = this._getBounds(internalGetter || getter,
-				matrix || _matrix, cacheItem);
-		if (cache) {
-			if (!this._bounds)
-				this._bounds = {};
-			var cached = this._bounds[cache] = bounds.clone();
-			cached._internal = !!internalGetter;
-		}
-		return bounds;
+	_getBounds: function(matrix, options) {
+		var children = this._children;
+		if (!children || !children.length)
+			return new Rectangle();
+		Item._updateBoundsCache(this, options.cacheItem);
+		return Item._getBounds(children, matrix, options);
+	},
+
+	_getBoundsCacheKey: function(options, internal) {
+		return [
+			options.stroke ? 1 : 0,
+			options.handle ? 1 : 0,
+			internal ? 1 : 0
+		].join('');
+	},
+
+	_getCachedBounds: function(matrix, options, noInternal) {
+		matrix = matrix && matrix._orNullIfIdentity();
+		var internal = options.internal && !noInternal,
+			cacheItem = options.cacheItem,
+			_matrix = internal ? null : this._matrix._orNullIfIdentity(),
+			cacheKey = cacheItem && (!matrix || matrix.equals(_matrix))
+				&& this._getBoundsCacheKey(options, internal),
+			bounds = this._bounds;
+		Item._updateBoundsCache(this._parent || this._symbol, cacheItem);
+		if (cacheKey && bounds && cacheKey in bounds) {
+			var cached = bounds[cacheKey];
+			return {
+				rect: cached.rect.clone(),
+				nonscaling: cached.nonscaling
+			};
+		}
+		var res = this._getBounds(matrix || _matrix, options),
+			rect = res.rect || res,
+			style = this._style,
+			nonscaling = res.nonscaling || style.hasStroke()
+				&& !style.getStrokeScaling();
+		if (cacheKey) {
+			if (!bounds) {
+				this._bounds = bounds = {};
+			}
+			var cached = bounds[cacheKey] = {
+				rect: rect.clone(),
+				nonscaling: nonscaling,
+				internal: internal
+			};
+		}
+		return {
+			rect: rect,
+			nonscaling: nonscaling
+		};
+	},
+
+	_getStrokeMatrix: function(matrix, options) {
+		var parent = this.getStrokeScaling() ? null
+				: options && options.internal ? this
+					: this._parent || this._symbol && this._symbol._item,
+			mx = parent ? parent.getViewMatrix().invert() : matrix;
+		return mx && mx._shiftless();
 	},
 
 	statics: {
 		_updateBoundsCache: function(parent, item) {
-			if (parent) {
+			if (parent && item) {
 				var id = item._id,
 					ref = parent._boundsCache = parent._boundsCache || {
 						ids: {},
@@ -3188,6 +3389,35 @@
 					}
 				}
 			}
+		},
+
+		_getBounds: function(items, matrix, options) {
+			var x1 = Infinity,
+				x2 = -x1,
+				y1 = x1,
+				y2 = x2,
+				nonscaling = false;
+			options = options || {};
+			for (var i = 0, l = items.length; i < l; i++) {
+				var item = items[i];
+				if (item._visible && !item.isEmpty(true)) {
+					var bounds = item._getCachedBounds(
+						matrix && matrix.appended(item._matrix), options, true),
+						rect = bounds.rect;
+					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);
+					if (bounds.nonscaling)
+						nonscaling = true;
+				}
+			}
+			return {
+				rect: isFinite(x1)
+					? new Rectangle(x1, y1, x2 - x1, y2 - y1)
+					: new Rectangle(),
+				nonscaling: nonscaling
+			};
 		}
 	}
 
@@ -3195,12 +3425,14 @@
 	beans: true,
 
 	_decompose: function() {
-		return this._decomposed = this._matrix.decompose();
+		return this._applyMatrix
+			? null
+			: this._decomposed || (this._decomposed = this._matrix.decompose());
 	},
 
 	getRotation: function() {
-		var decomposed = this._decomposed || this._decompose();
-		return decomposed && decomposed.rotation;
+		var decomposed = this._decompose();
+		return decomposed ? decomposed.rotation : 0;
 	},
 
 	setRotation: function(rotation) {
@@ -3208,26 +3440,39 @@
 		if (current != null && rotation != null) {
 			var decomposed = this._decomposed;
 			this.rotate(rotation - current);
-			decomposed.rotation = rotation;
-			this._decomposed = decomposed;
-		}
-	},
-
-	getScaling: function(_dontLink) {
-		var decomposed = this._decomposed || this._decompose(),
-			scaling = decomposed && decomposed.scaling,
-			ctor = _dontLink ? Point : LinkedPoint;
-		return scaling && new ctor(scaling.x, scaling.y, this, 'setScaling');
+			if (decomposed) {
+				decomposed.rotation = rotation;
+				this._decomposed = decomposed;
+			}
+		}
+	},
+
+	getScaling: function() {
+		var decomposed = this._decompose(),
+			s = decomposed && decomposed.scaling;
+		return new LinkedPoint(s ? s.x : 1, s ? s.y : 1, this, 'setScaling');
 	},
 
 	setScaling: function() {
-		var current = this.getScaling();
-		if (current) {
-			var scaling = Point.read(arguments, 0, { clone: true }),
-				decomposed = this._decomposed;
-			this.scale(scaling.x / current.x, scaling.y / current.y);
-			decomposed.scaling = scaling;
-			this._decomposed = decomposed;
+		var current = this.getScaling(),
+			scaling = Point.read(arguments, 0, { clone: true, readNull: true });
+		if (current && scaling && !current.equals(scaling)) {
+			var rotation = this.getRotation(),
+				decomposed = this._decomposed,
+				matrix = new Matrix(),
+				center = this.getPosition(true);
+			matrix.translate(center);
+			if (rotation)
+				matrix.rotate(rotation);
+			matrix.scale(scaling.x / current.x, scaling.y / current.y);
+			if (rotation)
+				matrix.rotate(-rotation);
+			matrix.translate(center.negate());
+			this.transform(matrix);
+			if (decomposed) {
+				decomposed.scaling = scaling;
+				this._decomposed = decomposed;
+			}
 		}
 	},
 
@@ -3238,28 +3483,38 @@
 	setMatrix: function() {
 		var matrix = this._matrix;
 		matrix.initialize.apply(matrix, arguments);
-		if (this._applyMatrix) {
-			this.transform(null, true);
-		} else {
-			this._changed(9);
-		}
 	},
 
 	getGlobalMatrix: function(_dontClone) {
-		var matrix = this._globalMatrix,
-			updateVersion = this._project._updateVersion;
-		if (matrix && matrix._updateVersion !== updateVersion)
-			matrix = null;
+		var matrix = this._globalMatrix;
+		if (matrix) {
+			var parent = this._parent;
+			var parents = [];
+			while (parent) {
+				if (!parent._globalMatrix) {
+					matrix = null;
+					for (var i = 0, l = parents.length; i < l; i++) {
+						parents[i]._globalMatrix = null;
+					}
+					break;
+				}
+				parents.push(parent);
+				parent = parent._parent;
+			}
+		}
 		if (!matrix) {
 			matrix = this._globalMatrix = this._matrix.clone();
 			var parent = this._parent;
 			if (parent)
-				matrix.preConcatenate(parent.getGlobalMatrix(true));
-			matrix._updateVersion = updateVersion;
+				matrix.prepend(parent.getGlobalMatrix(true));
 		}
 		return _dontClone ? matrix : matrix.clone();
 	},
 
+	getViewMatrix: function() {
+		return this.getGlobalMatrix().prepend(this.getView()._matrix);
+	},
+
 	getApplyMatrix: function() {
 		return this._applyMatrix;
 	},
@@ -3291,7 +3546,7 @@
 	},
 
 	getView: function() {
-		return this._project.getView();
+		return this._project._view;
 	},
 
 	_installEvents: function _installEvents(install) {
@@ -3318,6 +3573,8 @@
 		return item.addChild(this);
 	},
 
+	_getOwner: '#getParent',
+
 	getChildren: function() {
 		return this._children;
 	},
@@ -3337,11 +3594,13 @@
 	},
 
 	getNextSibling: function() {
-		return this._parent && this._parent._children[this._index + 1] || null;
+		var owner = this._getOwner();
+		return owner && owner._children[this._index + 1] || null;
 	},
 
 	getPreviousSibling: function() {
-		return this._parent && this._parent._children[this._index - 1] || null;
+		var owner = this._getOwner();
+		return owner && owner._children[this._index - 1] || null;
 	},
 
 	getIndex: function() {
@@ -3366,70 +3625,99 @@
 		return Base.equals(this._children, item._children);
 	},
 
-	clone: function(insert) {
-		return this._clone(new this.constructor(Item.NO_INSERT), insert);
-	},
-
-	_clone: function(copy, insert, includeMatrix) {
+	clone: function(options) {
+		var copy = new this.constructor(Item.NO_INSERT),
+			children = this._children,
+			insert = Base.pick(options ? options.insert : undefined,
+					options === undefined || options === true),
+			deep = Base.pick(options ? options.deep : undefined, true);
+		if (children)
+			copy.copyAttributes(this);
+		if (!children || deep)
+			copy.copyContent(this);
+		if (!children)
+			copy.copyAttributes(this);
+		if (insert)
+			copy.insertAbove(this);
+		var name = this._name,
+			parent = this._parent;
+		if (name && parent) {
+			var children = parent._children,
+				orig = name,
+				i = 1;
+			while (children[name])
+				name = orig + ' ' + (i++);
+			if (name !== orig)
+				copy.setName(name);
+		}
+		return copy;
+	},
+
+	copyContent: function(source) {
+		var children = source._children;
+		for (var i = 0, l = children && children.length; i < l; i++) {
+			this.addChild(children[i].clone(false), true);
+		}
+	},
+
+	copyAttributes: function(source, excludeMatrix) {
+		this.setStyle(source._style);
 		var keys = ['_locked', '_visible', '_blendMode', '_opacity',
-				'_clipMask', '_guide'],
-			children = this._children;
-		copy.setStyle(this._style);
-		for (var i = 0, l = children && children.length; i < l; i++) {
-			copy.addChild(children[i].clone(false), true);
-		}
+				'_clipMask', '_guide'];
 		for (var i = 0, l = keys.length; i < l; i++) {
 			var key = keys[i];
-			if (this.hasOwnProperty(key))
-				copy[key] = this[key];
-		}
-		if (includeMatrix !== false)
-			copy._matrix.initialize(this._matrix);
-		copy.setApplyMatrix(this._applyMatrix);
-		copy.setPivot(this._pivot);
-		copy.setSelected(this._selected);
-		copy._data = this._data ? Base.clone(this._data) : null;
-		if (insert || insert === undefined)
-			copy.insertAbove(this);
-		if (this._name)
-			copy.setName(this._name, true);
-		return copy;
-	},
-
-	copyTo: function(itemOrProject) {
-		return itemOrProject.addChild(this.clone(false));
-	},
-
-	rasterize: function(resolution) {
+			if (source.hasOwnProperty(key))
+				this[key] = source[key];
+		}
+		if (!excludeMatrix)
+			this._matrix.set(source._matrix, true);
+		this.setApplyMatrix(source._applyMatrix);
+		this.setPivot(source._pivot);
+		this.setSelection(source._selection);
+		var data = source._data,
+			name = source._name;
+		this._data = data ? Base.clone(data) : null;
+		if (name)
+			this.setName(name);
+	},
+
+	rasterize: function(resolution, insert) {
 		var bounds = this.getStrokeBounds(),
 			scale = (resolution || this.getView().getResolution()) / 72,
 			topLeft = bounds.getTopLeft().floor(),
 			bottomRight = bounds.getBottomRight().ceil(),
 			size = new Size(bottomRight.subtract(topLeft)),
-			canvas = CanvasProvider.getCanvas(size.multiply(scale)),
-			ctx = canvas.getContext('2d'),
-			matrix = new Matrix().scale(scale).translate(topLeft.negate());
-		ctx.save();
-		matrix.applyToContext(ctx);
-		this.draw(ctx, new Base({ matrices: [matrix] }));
-		ctx.restore();
-		var raster = new Raster(Item.NO_INSERT);
-		raster.setCanvas(canvas);
+			raster = new Raster(Item.NO_INSERT);
+		if (!size.isZero()) {
+			var canvas = CanvasProvider.getCanvas(size.multiply(scale)),
+				ctx = canvas.getContext('2d'),
+				matrix = new Matrix().scale(scale).translate(topLeft.negate());
+			ctx.save();
+			matrix.applyToContext(ctx);
+			this.draw(ctx, new Base({ matrices: [matrix] }));
+			ctx.restore();
+			raster.setCanvas(canvas);
+		}
 		raster.transform(new Matrix().translate(topLeft.add(size.divide(2)))
 				.scale(1 / scale));
-		raster.insertAbove(this);
+		if (insert === undefined || insert)
+			raster.insertAbove(this);
 		return raster;
 	},
 
 	contains: function() {
-		return !!this._contains(
-				this._matrix._inverseTransform(Point.read(arguments)));
+		var matrix = this._matrix;
+		return (
+			matrix.isInvertible() &&
+			!!this._contains(matrix._inverseTransform(Point.read(arguments)))
+		);
 	},
 
 	_contains: function(point) {
-		if (this._children) {
-			for (var i = this._children.length - 1; i >= 0; i--) {
-				if (this._children[i].contains(point))
+		var children = this._children;
+		if (children) {
+			for (var i = children.length - 1; i >= 0; i--) {
+				if (children[i].contains(point))
 					return true;
 			}
 			return false;
@@ -3452,83 +3740,136 @@
 	intersects: function(item, _matrix) {
 		if (!(item instanceof Item))
 			return false;
-		return this._asPathItem().getIntersections(item._asPathItem(),
-				_matrix || item._matrix).length > 0;
-	},
-
-	hitTest: function() {
+		return this._asPathItem().getIntersections(item._asPathItem(), null,
+				_matrix, true).length > 0;
+	}
+},
+new function() {
+	function hitTest() {
 		return this._hitTest(
 				Point.read(arguments),
-				HitResult.getOptions(Base.read(arguments)));
-	},
-
-	_hitTest: function(point, options) {
+				HitResult.getOptions(arguments));
+	}
+
+	function hitTestAll() {
+		var point = Point.read(arguments),
+			options = HitResult.getOptions(arguments),
+			all = [];
+		this._hitTest(point, Base.set({ all: all }, options));
+		return all;
+	}
+
+	function hitTestChildren(point, options, viewMatrix, _exclude) {
+		var children = this._children;
+		if (children) {
+			for (var i = children.length - 1; i >= 0; i--) {
+				var child = children[i];
+				var res = child !== _exclude && child._hitTest(point, options,
+						viewMatrix);
+				if (res && !options.all)
+					return res;
+			}
+		}
+		return null;
+	}
+
+	Project.inject({
+		hitTest: hitTest,
+		hitTestAll: hitTestAll,
+		_hitTest: hitTestChildren
+	});
+
+	return {
+		hitTest: hitTest,
+		hitTestAll: hitTestAll,
+		_hitTestChildren: hitTestChildren,
+	};
+}, {
+
+	_hitTest: function(point, options, parentViewMatrix) {
 		if (this._locked || !this._visible || this._guide && !options.guides
-				|| this.isEmpty())
+				|| this.isEmpty()) {
 			return null;
+		}
 
 		var matrix = this._matrix,
-			parentTotalMatrix = options._totalMatrix,
-			view = this.getView(),
-			totalMatrix = options._totalMatrix = parentTotalMatrix
-					? parentTotalMatrix.chain(matrix)
-					: this.getGlobalMatrix().preConcatenate(view._matrix),
+			viewMatrix = parentViewMatrix
+					? parentViewMatrix.appended(matrix)
+					: this.getGlobalMatrix().prepend(this.getView()._matrix),
+			tolerance = Math.max(options.tolerance, 1e-12),
 			tolerancePadding = options._tolerancePadding = new Size(
-						Path._getPenPadding(1, totalMatrix.inverted())
-					).multiply(
-						Math.max(options.tolerance, 0.000001)
-					);
+					Path._getStrokePadding(tolerance,
+						matrix._shiftless().invert()));
 		point = matrix._inverseTransform(point);
-
-		if (!this._children && !this.getInternalRoughBounds()
-				.expand(tolerancePadding.multiply(2))._containsPoint(point))
+		if (!point || !this._children &&
+			!this.getBounds({ internal: true, stroke: true, handle: true })
+				.expand(tolerancePadding.multiply(2))._containsPoint(point)) {
 			return null;
+		}
+
 		var checkSelf = !(options.guides && !this._guide
-				|| options.selected && !this._selected
+				|| options.selected && !this.isSelected()
 				|| options.type && options.type !== Base.hyphenate(this._class)
 				|| options.class && !(this instanceof options.class)),
+			match = options.match,
 			that = this,
+			bounds,
 			res;
 
-		function checkBounds(type, part) {
-			var pt = bounds['get' + part]();
-			if (point.subtract(pt).divide(tolerancePadding).length <= 1)
-				return new HitResult(type, that,
-						{ name: Base.hyphenate(part), point: pt });
-		}
-
-		if (checkSelf && (options.center || options.bounds) && this._parent) {
-			var bounds = this.getInternalBounds();
-			if (options.center)
-				res = checkBounds('center', 'Center');
-			if (!res && options.bounds) {
+		function filter(hit) {
+			if (hit && match && !match(hit))
+				hit = null;
+			if (hit && options.all)
+				options.all.push(hit);
+			return hit;
+		}
+
+		function checkPoint(type, part) {
+			var pt = part ? bounds['get' + part]() : that.getPosition();
+			if (point.subtract(pt).divide(tolerancePadding).length <= 1) {
+				return new HitResult(type, that, {
+					name: part ? Base.hyphenate(part) : type,
+					point: pt
+				});
+			}
+		}
+
+		var checkPosition = options.position,
+			checkCenter = options.center,
+			checkBounds = options.bounds;
+		if (checkSelf && this._parent
+				&& (checkPosition || checkCenter || checkBounds)) {
+			if (checkCenter || checkBounds) {
+				bounds = this.getInternalBounds();
+			}
+			res = checkPosition && checkPoint('position') ||
+					checkCenter && checkPoint('center', 'Center');
+			if (!res && checkBounds) {
 				var points = [
 					'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
 					'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'
 				];
-				for (var i = 0; i < 8 && !res; i++)
-					res = checkBounds('bounds', points[i]);
-			}
-		}
-
-		var children = !res && this._children;
-		if (children) {
-			var opts = this._getChildHitTestOptions(options);
-			for (var i = children.length - 1; i >= 0 && !res; i--)
-				res = children[i]._hitTest(point, opts);
-		}
-		if (!res && checkSelf)
-			res = this._hitTestSelf(point, options);
-		if (res && res.point)
+				for (var i = 0; i < 8 && !res; i++) {
+					res = checkPoint('bounds', points[i]);
+				}
+			}
+			res = filter(res);
+		}
+
+		if (!res) {
+			res = this._hitTestChildren(point, options, viewMatrix)
+				|| checkSelf
+					&& filter(this._hitTestSelf(point, options, viewMatrix,
+						this.getStrokeScaling() ? null
+							: viewMatrix._shiftless().invert()))
+				|| null;
+		}
+		if (res && res.point) {
 			res.point = matrix.transform(res.point);
-		options._totalMatrix = parentTotalMatrix;
+		}
 		return res;
 	},
 
-	_getChildHitTestOptions: function(options) {
-		return options;
-	},
-
 	_hitTestSelf: function(point, options) {
 		if (options.fill && this.hasFill() && this._contains(point))
 			return new HitResult('fill', this);
@@ -3556,52 +3897,55 @@
 				if (name.hasOwnProperty(key) && !this.matches(key, name[key]))
 					return false;
 			}
+			return true;
 		} else if (type === 'function') {
 			return name(this);
+		} else if (name === 'match') {
+			return compare(this);
 		} else {
 			var value = /^(empty|editable)$/.test(name)
 					? this['is' + Base.capitalize(name)]()
 					: name === 'type'
 						? Base.hyphenate(this._class)
 						: this[name];
-			if (/^(constructor|class)$/.test(name)) {
-				if (!(this instanceof compare))
-					return false;
-			} else if (compare instanceof RegExp) {
-				if (!compare.test(value))
-					return false;
-			} else if (typeof compare === 'function') {
-				if (!compare(value))
-					return false;
-			} else if (Base.isPlainObject(compare)) {
-				if (!matchObject(compare, value))
-					return false;
-			} else if (!Base.equals(value, compare)) {
-				return false;
-			}
-		}
-		return true;
-	},
-
-	getItems: function(match) {
-		return Item._getItems(this._children, match, this._matrix);
-	},
-
-	getItem: function(match) {
-		return Item._getItems(this._children, match, this._matrix, null, true)
-				[0] || null;
+			if (name === 'class') {
+				if (typeof compare === 'function')
+					return this instanceof compare;
+				value = this._class;
+			}
+			if (typeof compare === 'function') {
+				return !!compare(value);
+			} else if (compare) {
+				if (compare.test) {
+					return compare.test(value);
+				} else if (Base.isPlainObject(compare)) {
+					return matchObject(compare, value);
+				}
+			}
+			return Base.equals(value, compare);
+		}
+	},
+
+	getItems: function(options) {
+		return Item._getItems(this, options, this._matrix);
+	},
+
+	getItem: function(options) {
+		return Item._getItems(this, options, this._matrix, null, true)[0]
+				|| null;
 	},
 
 	statics: {
-		_getItems: function _getItems(children, match, matrix, param,
-				firstOnly) {
-			if (!param && typeof match === 'object') {
-				var overlapping = match.overlapping,
-					inside = match.inside,
+		_getItems: function _getItems(item, options, matrix, param, firstOnly) {
+			if (!param) {
+				var obj = typeof options === 'object' && options,
+					overlapping = obj && obj.overlapping,
+					inside = obj && obj.inside,
 					bounds = overlapping || inside,
-					rect =	bounds && Rectangle.read([bounds]);
+					rect = bounds && Rectangle.read([bounds]);
 				param = {
 					items: [],
+					recursive: obj && obj.recursive !== false,
 					inside: !!inside,
 					overlapping: !!overlapping,
 					rect: rect,
@@ -3610,34 +3954,37 @@
 						insert: false
 					})
 				};
-				if (bounds)
-					match = Base.set({}, match,
-							{ inside: true, overlapping: true });
-			}
-			var items = param && param.items,
-				rect = param && param.rect;
+				if (obj) {
+					options = Base.filter({}, options, {
+						recursive: true, inside: true, overlapping: true
+					});
+				}
+			}
+			var children = item._children,
+				items = param.items,
+				rect = param.rect;
 			matrix = rect && (matrix || new Matrix());
 			for (var i = 0, l = children && children.length; i < l; i++) {
 				var child = children[i],
-					childMatrix = matrix && matrix.chain(child._matrix),
+					childMatrix = matrix && matrix.appended(child._matrix),
 					add = true;
 				if (rect) {
 					var bounds = child.getBounds(childMatrix);
 					if (!rect.intersects(bounds))
 						continue;
-					if (!(param.inside && rect.contains(bounds))
-							&& !(param.overlapping && (bounds.contains(rect)
+					if (!(rect.contains(bounds)
+							|| param.overlapping && (bounds.contains(rect)
 								|| param.path.intersects(child, childMatrix))))
 						add = false;
 				}
-				if (add && child.matches(match)) {
+				if (add && child.matches(options)) {
 					items.push(child);
 					if (firstOnly)
 						break;
 				}
-				_getItems(child._children, match,
-						childMatrix, param,
-						firstOnly);
+				if (param.recursive !== false) {
+					_getItems(child, options, childMatrix, param, firstOnly);
+				}
 				if (firstOnly && items.length > 0)
 					break;
 			}
@@ -3648,49 +3995,49 @@
 
 	importJSON: function(json) {
 		var res = Base.importJSON(json, this);
-		return res !== this
-				? this.addChild(res)
-				: res;
-	},
-
-	addChild: function(item, _preserve) {
-		return this.insertChild(undefined, item, _preserve);
-	},
-
-	insertChild: function(index, item, _preserve) {
-		var res = item ? this.insertChildren(index, [item], _preserve) : null;
+		return res !== this ? this.addChild(res) : res;
+	},
+
+	addChild: function(item) {
+		return this.insertChild(undefined, item);
+	},
+
+	insertChild: function(index, item) {
+		var res = item ? this.insertChildren(index, [item]) : null;
 		return res && res[0];
 	},
 
-	addChildren: function(items, _preserve) {
-		return this.insertChildren(this._children.length, items, _preserve);
-	},
-
-	insertChildren: function(index, items, _preserve, _proto) {
+	addChildren: function(items) {
+		return this.insertChildren(this._children.length, items);
+	},
+
+	insertChildren: function(index, items) {
 		var children = this._children;
 		if (children && items && items.length > 0) {
-			items = Array.prototype.slice.apply(items);
+			items = Base.slice(items);
+			var inserted = {};
 			for (var i = items.length - 1; i >= 0; i--) {
-				var item = items[i];
-				if (_proto && !(item instanceof _proto)) {
+				var item = items[i],
+					id = item && item._id;
+				if (!item || inserted[id]) {
 					items.splice(i, 1);
 				} else {
-					var shift = item._parent === this && item._index < index;
-					if (item._remove(false, true) && shift)
-						index--;
+					item._remove(false, true);
+					inserted[id] = true;
 				}
 			}
 			Base.splice(children, items, index, 0);
 			var project = this._project,
-				notifySelf = project && project._changes;
+				notifySelf = project._changes;
 			for (var i = 0, l = items.length; i < l; i++) {
-				var item = items[i];
+				var item = items[i],
+					name = item._name;
 				item._parent = this;
-				item._setProject(this._project, true);
-				if (item._name)
-					item.setName(item._name);
+				item._setProject(project, true);
+				if (name)
+					item.setName(name);
 				if (notifySelf)
-					this._changed(5);
+					item._changed(5);
 			}
 			this._changed(11);
 		} else {
@@ -3699,28 +4046,34 @@
 		return items;
 	},
 
-	_insertSibling: function(index, item, _preserve) {
-		return this._parent
-				? this._parent.insertChild(index, item, _preserve)
-				: null;
-	},
-
-	insertAbove: function(item, _preserve) {
-		return item._insertSibling(item._index + 1, this, _preserve);
-	},
-
-	insertBelow: function(item, _preserve) {
-		return item._insertSibling(item._index, this, _preserve);
+	_insertItem: '#insertChild',
+
+	_insertAt: function(item, offset) {
+		var owner = item && item._getOwner(),
+			res = item !== this && owner ? this : null;
+		if (res) {
+			res._remove(false, true);
+			owner._insertItem(item._index + offset, res);
+		}
+		return res;
+	},
+
+	insertAbove: function(item) {
+		return this._insertAt(item, 1);
+	},
+
+	insertBelow: function(item) {
+		return this._insertAt(item, 0);
 	},
 
 	sendToBack: function() {
-		return (this._parent || this instanceof Layer && this._project)
-				.insertChild(0, this);
+		var owner = this._getOwner();
+		return owner ? owner._insertItem(0, this) : null;
 	},
 
 	bringToFront: function() {
-		return (this._parent || this instanceof Layer && this._project)
-				.addChild(this);
+		var owner = this._getOwner();
+		return owner ? owner._insertItem(undefined, this) : null;
 	},
 
 	appendTop: '#addChild',
@@ -3733,22 +4086,34 @@
 
 	moveBelow: '#insertBelow',
 
-	reduce: function() {
-		if (this._children && this._children.length === 1) {
-			var child = this._children[0].reduce();
-			child.insertAbove(this);
-			child.setStyle(this._style);
-			this.remove();
+	addTo: function(owner) {
+		return owner._insertItem(undefined, this);
+	},
+
+	copyTo: function(owner) {
+		return this.clone(false).addTo(owner);
+	},
+
+	reduce: function(options) {
+		var children = this._children;
+		if (children && children.length === 1) {
+			var child = children[0].reduce(options);
+			if (this._parent) {
+				child.insertAbove(this);
+				this.remove();
+			} else {
+				child.remove();
+			}
 			return child;
 		}
 		return this;
 	},
 
 	_removeNamed: function() {
-		var parent = this._parent;
-		if (parent) {
-			var children = parent._children,
-				namedChildren = parent._namedChildren,
+		var owner = this._getOwner();
+		if (owner) {
+			var children = owner._children,
+				namedChildren = owner._namedChildren,
 				name = this._name,
 				namedArray = namedChildren[name],
 				index = namedArray ? namedArray.indexOf(this) : -1;
@@ -3757,7 +4122,7 @@
 					delete children[name];
 				namedArray.splice(index, 1);
 				if (namedArray.length) {
-					children[name] = namedArray[namedArray.length - 1];
+					children[name] = namedArray[0];
 				} else {
 					delete namedChildren[name];
 				}
@@ -3766,20 +4131,25 @@
 	},
 
 	_remove: function(notifySelf, notifyParent) {
-		var parent = this._parent;
-		if (parent) {
+		var owner = this._getOwner(),
+			project = this._project,
+			index = this._index;
+		if (this._style)
+			this._style._dispose();
+		if (owner) {
 			if (this._name)
 				this._removeNamed();
-			if (this._index != null)
-				Base.splice(parent._children, null, this._index, 1);
+			if (index != null) {
+				if (project._activeLayer === this)
+					project._activeLayer = this.getNextSibling()
+							|| this.getPreviousSibling();
+				Base.splice(owner._children, null, index, 1);
+			}
 			this._installEvents(false);
-			if (notifySelf) {
-				var project = this._project;
-				if (project && project._changes)
-					this._changed(5);
-			}
+			if (notifySelf && project._changes)
+				this._changed(5);
 			if (notifyParent)
-				parent._changed(11);
+				owner._changed(11, this);
 			this._parent = null;
 			return true;
 		}
@@ -3797,12 +4167,12 @@
 		return ok;
 	},
 
-	removeChildren: function(from, to) {
+	removeChildren: function(start, end) {
 		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);
+		start = start || 0;
+		end = Base.pick(end, this._children.length);
+		var removed = Base.splice(this._children, null, start, end - start);
 		for (var i = removed.length - 1; i >= 0; i--) {
 			removed[i]._remove(true, false);
 		}
@@ -3822,8 +4192,18 @@
 		}
 	},
 
-	isEmpty: function() {
-		return !this._children || this._children.length === 0;
+	isEmpty: function(recursively) {
+		var children = this._children;
+		var numChildren = children ? children.length : 0;
+		if (recursively) {
+			for (var i = 0; i < numChildren; i++) {
+				if (!children[i].isEmpty(recursively)) {
+					return false;
+				}
+			}
+			return true;
+		}
+		return !numChildren;
 	},
 
 	isEditable: function() {
@@ -3893,7 +4273,7 @@
 	isDescendant: function(item) {
 		var parent = this;
 		while (parent = parent._parent) {
-			if (parent == item)
+			if (parent === item)
 				return true;
 		}
 		return false;
@@ -3903,6 +4283,10 @@
 		return item ? item.isDescendant(this) : false;
 	},
 
+	isSibling: function(item) {
+		return this._parent === item._parent;
+	},
+
 	isGroupedWith: function(item) {
 		var parent = this._parent;
 		while (parent) {
@@ -3915,70 +4299,73 @@
 		return false;
 	},
 
-	translate: function() {
-		var mx = new Matrix();
-		return this.transform(mx.translate.apply(mx, arguments));
-	},
-
-	rotate: function(angle ) {
-		return this.transform(new Matrix().rotate(angle,
-				Point.read(arguments, 1, { readNull: true })
-					|| this.getPosition(true)));
-	}
-}, Base.each(['scale', 'shear', 'skew'], function(name) {
-	this[name] = function() {
-		var point = Point.read(arguments),
+}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) {
+	var rotate = key === 'rotate';
+	this[key] = function() {
+		var value = (rotate ? Base : Point).read(arguments),
 			center = Point.read(arguments, 0, { readNull: true });
-		return this.transform(new Matrix()[name](point,
+		return this.transform(new Matrix()[key](value,
 				center || this.getPosition(true)));
 	};
 }, {
-
-}), {
+	translate: function() {
+		var mx = new Matrix();
+		return this.transform(mx.translate.apply(mx, arguments));
+	},
+
 	transform: function(matrix, _applyMatrix, _applyRecursively,
 			_setApplyMatrix) {
-		if (matrix && matrix.isIdentity())
-			matrix = null;
 		var _matrix = this._matrix,
+			transformMatrix = matrix && !matrix.isIdentity(),
 			applyMatrix = (_applyMatrix || this._applyMatrix)
-					&& ((!_matrix.isIdentity() || matrix)
+					&& ((!_matrix.isIdentity() || transformMatrix)
 						|| _applyMatrix && _applyRecursively && this._children);
-		if (!matrix && !applyMatrix)
+		if (!transformMatrix && !applyMatrix)
 			return this;
-		if (matrix)
-			_matrix.preConcatenate(matrix);
-		if (applyMatrix = applyMatrix && this._transformContent(_matrix,
-					_applyRecursively, _setApplyMatrix)) {
-			var pivot = this._pivot,
-				style = this._style,
+		if (transformMatrix) {
+			if (!matrix.isInvertible() && _matrix.isInvertible())
+				_matrix._backup = _matrix.getValues();
+			_matrix.prepend(matrix, true);
+			var style = this._style,
 				fillColor = style.getFillColor(true),
 				strokeColor = style.getStrokeColor(true);
+			if (fillColor)
+				fillColor.transform(matrix);
+			if (strokeColor)
+				strokeColor.transform(matrix);
+		}
+		if (applyMatrix && (applyMatrix = this._transformContent(_matrix,
+				_applyRecursively, _setApplyMatrix))) {
+			var pivot = this._pivot;
 			if (pivot)
 				_matrix._transformPoint(pivot, pivot, true);
-			if (fillColor)
-				fillColor.transform(_matrix);
-			if (strokeColor)
-				strokeColor.transform(_matrix);
 			_matrix.reset(true);
 			if (_setApplyMatrix && this._canApplyMatrix)
 				this._applyMatrix = true;
 		}
 		var bounds = this._bounds,
 			position = this._position;
-		this._changed(9);
-		var decomp = bounds && matrix && matrix.decompose();
-		if (decomp && !decomp.shearing && decomp.rotation % 90 === 0) {
+		if (transformMatrix || applyMatrix) {
+			this._changed(25);
+		}
+		var decomp = transformMatrix && bounds && matrix.decompose();
+		if (decomp && decomp.skewing.isZero() && decomp.rotation % 90 === 0) {
 			for (var key in bounds) {
-				var rect = bounds[key];
-				if (applyMatrix || !rect._internal)
+				var cache = bounds[key];
+				if (cache.nonscaling) {
+					delete bounds[key];
+				} else if (applyMatrix || !cache.internal) {
+					var rect = cache.rect;
 					matrix._transformBounds(rect, rect);
-			}
-			var getter = this._boundsGetter,
-				rect = bounds[getter && getter.getBounds || getter || 'getBounds'];
-			if (rect)
-				this._position = rect.getCenter(true);
+				}
+			}
 			this._bounds = bounds;
-		} else if (matrix && position) {
+			var cached = bounds[this._getBoundsCacheKey(
+				this._boundsOptions || {})];
+			if (cached) {
+				this._position = this._getPositionFromBounds(cached.rect);
+			}
+		} else if (transformMatrix && position && this._pivot) {
 			this._position = matrix._transformPoint(position, position);
 		}
 		return this;
@@ -4024,53 +4411,51 @@
 					new Size(bounds.width * scale, bounds.height * scale));
 		newBounds.setCenter(rectangle.getCenter());
 		this.setBounds(newBounds);
-	},
-
-	_setStyles: function(ctx) {
+	}
+}), {
+
+	_setStyles: function(ctx, param, viewMatrix) {
 		var style = this._style,
-			fillColor = style.getFillColor(),
-			strokeColor = style.getStrokeColor(),
-			shadowColor = style.getShadowColor();
-		if (fillColor)
-			ctx.fillStyle = fillColor.toCanvasStyle(ctx);
-		if (strokeColor) {
-			var strokeWidth = style.getStrokeWidth();
-			if (strokeWidth > 0) {
-				ctx.strokeStyle = strokeColor.toCanvasStyle(ctx);
-				ctx.lineWidth = strokeWidth;
-				var strokeJoin = style.getStrokeJoin(),
-					strokeCap = style.getStrokeCap(),
-					miterLimit = style.getMiterLimit();
-				if (strokeJoin)
-					ctx.lineJoin = strokeJoin;
-				if (strokeCap)
-					ctx.lineCap = strokeCap;
-				if (miterLimit)
-					ctx.miterLimit = miterLimit;
-				if (paper.support.nativeDash) {
-					var dashArray = style.getDashArray(),
-						dashOffset = style.getDashOffset();
-					if (dashArray && dashArray.length) {
-						if ('setLineDash' in ctx) {
-							ctx.setLineDash(dashArray);
-							ctx.lineDashOffset = dashOffset;
-						} else {
-							ctx.mozDash = dashArray;
-							ctx.mozDashOffset = dashOffset;
-						}
-					}
-				}
-			}
-		}
-		if (shadowColor) {
-			var shadowBlur = style.getShadowBlur();
-			if (shadowBlur > 0) {
-				ctx.shadowColor = shadowColor.toCanvasStyle(ctx);
-				ctx.shadowBlur = shadowBlur;
-				var offset = this.getShadowOffset();
-				ctx.shadowOffsetX = offset.x;
-				ctx.shadowOffsetY = offset.y;
-			}
+			matrix = this._matrix;
+		if (style.hasFill()) {
+			ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix);
+		}
+		if (style.hasStroke()) {
+			ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx, matrix);
+			ctx.lineWidth = style.getStrokeWidth();
+			var strokeJoin = style.getStrokeJoin(),
+				strokeCap = style.getStrokeCap(),
+				miterLimit = style.getMiterLimit();
+			if (strokeJoin)
+				ctx.lineJoin = strokeJoin;
+			if (strokeCap)
+				ctx.lineCap = strokeCap;
+			if (miterLimit)
+				ctx.miterLimit = miterLimit;
+			if (paper.support.nativeDash) {
+				var dashArray = style.getDashArray(),
+					dashOffset = style.getDashOffset();
+				if (dashArray && dashArray.length) {
+					if ('setLineDash' in ctx) {
+						ctx.setLineDash(dashArray);
+						ctx.lineDashOffset = dashOffset;
+					} else {
+						ctx.mozDash = dashArray;
+						ctx.mozDashOffset = dashOffset;
+					}
+				}
+			}
+		}
+		if (style.hasShadow()) {
+			var pixelRatio = param.pixelRatio || 1,
+				mx = viewMatrix._shiftless().prepend(
+					new Matrix().scale(pixelRatio, pixelRatio)),
+				blur = mx.transform(new Point(style.getShadowBlur(), 0)),
+				offset = mx.transform(this.getShadowOffset());
+			ctx.shadowColor = style.getShadowColor().toCanvasStyle(ctx);
+			ctx.shadowBlur = blur.getLength();
+			ctx.shadowOffsetX = offset.x;
+			ctx.shadowOffsetY = offset.y;
 		}
 	},
 
@@ -4081,17 +4466,15 @@
 		var matrices = param.matrices,
 			viewMatrix = param.viewMatrix,
 			matrix = this._matrix,
-			globalMatrix = matrices[matrices.length - 1].chain(matrix);
+			globalMatrix = matrices[matrices.length - 1].appended(matrix);
 		if (!globalMatrix.isInvertible())
 			return;
 
-		function getViewMatrix(matrix) {
-			return viewMatrix ? viewMatrix.chain(matrix) : matrix;
-		}
+		viewMatrix = viewMatrix ? viewMatrix.appended(globalMatrix)
+				: globalMatrix;
 
 		matrices.push(globalMatrix);
 		if (param.updateMatrix) {
-			globalMatrix._updateVersion = updateVersion;
 			this._globalMatrix = globalMatrix;
 		}
 
@@ -4107,9 +4490,11 @@
 			pixelRatio = param.pixelRatio || 1,
 			mainCtx, itemOffset, prevOffset;
 		if (!direct) {
-			var bounds = this.getStrokeBounds(getViewMatrix(globalMatrix));
-			if (!bounds.width || !bounds.height)
+			var bounds = this.getStrokeBounds(viewMatrix);
+			if (!bounds.width || !bounds.height) {
+				matrices.pop();
 				return;
+			}
 			prevOffset = param.offset;
 			itemOffset = param.offset = bounds.getTopLeft().floor();
 			mainCtx = ctx;
@@ -4120,8 +4505,9 @@
 		}
 		ctx.save();
 		var strokeMatrix = parentStrokeMatrix
-				? parentStrokeMatrix.chain(matrix)
-				: !this.getStrokeScaling(true) && getViewMatrix(globalMatrix),
+				? parentStrokeMatrix.appended(matrix)
+				: this._canScaleStroke && !this.getStrokeScaling(true)
+					&& viewMatrix,
 			clip = !direct && param.clipItem,
 			transform = !strokeMatrix || clip;
 		if (direct) {
@@ -4131,21 +4517,24 @@
 		} else if (transform) {
 			ctx.translate(-itemOffset.x, -itemOffset.y);
 		}
-		if (transform)
-			(direct ? matrix : getViewMatrix(globalMatrix)).applyToContext(ctx);
-		if (clip)
+		if (transform) {
+			(direct ? matrix : viewMatrix).applyToContext(ctx);
+		}
+		if (clip) {
 			param.clipItem.draw(ctx, param.extend({ clip: true }));
+		}
 		if (strokeMatrix) {
 			ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
 			var offset = param.offset;
 			if (offset)
 				ctx.translate(-offset.x, -offset.y);
 		}
-		this._draw(ctx, param, strokeMatrix);
+		this._draw(ctx, param, viewMatrix, strokeMatrix);
 		ctx.restore();
 		matrices.pop();
-		if (param.clip && !param.dontFinish)
-			ctx.clip();
+		if (param.clip && !param.dontFinish) {
+			ctx.clip(this.getFillRule());
+		}
 		if (!direct) {
 			BlendMode.process(blendMode, ctx, mainCtx, opacity,
 					itemOffset.subtract(prevOffset).multiply(pixelRatio));
@@ -4167,27 +4556,58 @@
 		return updated;
 	},
 
-	_drawSelection: function(ctx, matrix, size, selectedItems, updateVersion) {
-		if ((this._drawSelected || this._boundsSelected)
+	_drawSelection: function(ctx, matrix, size, selectionItems, updateVersion) {
+		var selection = this._selection,
+			itemSelected = selection & 1,
+			boundsSelected = selection & 2
+					|| itemSelected && this._selectBounds,
+			positionSelected = selection & 4;
+		if (!this._drawSelected)
+			itemSelected = false;
+		if ((itemSelected || boundsSelected || positionSelected)
 				&& this._isUpdated(updateVersion)) {
-			var color = this.getSelectedColor(true)
-					|| this.getLayer().getSelectedColor(true),
-				mx = matrix.chain(this.getGlobalMatrix(true));
+			var layer,
+				color = this.getSelectedColor(true) || (layer = this.getLayer())
+					&& layer.getSelectedColor(true),
+				mx = matrix.appended(this.getGlobalMatrix(true)),
+				half = size / 2;
 			ctx.strokeStyle = ctx.fillStyle = color
 					? color.toCanvasStyle(ctx) : '#009dec';
-			if (this._drawSelected)
-				this._drawSelected(ctx, mx, selectedItems);
-			if (this._boundsSelected) {
-				var half = size / 2;
-					coords = mx._transformCorners(this.getInternalBounds());
+			if (itemSelected)
+				this._drawSelected(ctx, mx, selectionItems);
+			if (positionSelected) {
+				var pos = this.getPosition(true),
+					parent = this._parent,
+					point = parent ? parent.localToGlobal(pos) : pos,
+					x = point.x,
+					y = point.y;
 				ctx.beginPath();
-				for (var i = 0; i < 8; i++)
-					ctx[i === 0 ? 'moveTo' : 'lineTo'](coords[i], coords[++i]);
+				ctx.arc(x, y, half, 0, Math.PI * 2, true);
+				ctx.stroke();
+				var deltas = [[0, -1], [1, 0], [0, 1], [-1, 0]],
+					start = half,
+					end = size + 1;
+				for (var i = 0; i < 4; i++) {
+					var delta = deltas[i],
+						dx = delta[0],
+						dy = delta[1];
+					ctx.moveTo(x + dx * start, y + dy * start);
+					ctx.lineTo(x + dx * end, y + dy * end);
+					ctx.stroke();
+				}
+			}
+			if (boundsSelected) {
+				var coords = mx._transformCorners(this.getInternalBounds());
+				ctx.beginPath();
+				for (var i = 0; i < 8; i++) {
+					ctx[!i ? 'moveTo' : 'lineTo'](coords[i], coords[++i]);
+				}
 				ctx.closePath();
 				ctx.stroke();
-				for (var i = 0; i < 8; i++)
+				for (var i = 0; i < 8; i++) {
 					ctx.fillRect(coords[i] - half, coords[++i] - half,
 							size, size);
+				}
 			}
 		}
 	},
@@ -4195,10 +4615,10 @@
 	_canComposite: function() {
 		return false;
 	}
-}, Base.each(['down', 'drag', 'up', 'move'], function(name) {
-	this['removeOn' + Base.capitalize(name)] = function() {
+}, Base.each(['down', 'drag', 'up', 'move'], function(key) {
+	this['removeOn' + Base.capitalize(key)] = function() {
 		var hash = {};
-		hash[name] = true;
+		hash[key] = true;
 		return this.removeOn(hash);
 	};
 }, {
@@ -4215,10 +4635,47 @@
 		}
 		return this;
 	}
-}));
+}), {
+	tween: function(from, to, options) {
+		if (!options) {
+			options = to;
+			to = from;
+			from = null;
+			if (!options) {
+				options = to;
+				to = null;
+			}
+		}
+		var easing = options && options.easing,
+			start = options && options.start,
+			duration = options != null && (
+				typeof options === 'number' ? options : options.duration
+			),
+			tween = new Tween(this, from, to, duration, easing, start);
+		function onFrame(event) {
+			tween._handleFrame(event.time * 1000);
+			if (!tween.running) {
+				this.off('frame', onFrame);
+			}
+		}
+		if (duration) {
+			this.on('frame', onFrame);
+		}
+		return tween;
+	},
+
+	tweenTo: function(to, options) {
+		return this.tween(null, to, options);
+	},
+
+	tweenFrom: function(from, options) {
+		return this.tween(from, null, options);
+	}
+});
 
 var Group = Item.extend({
 	_class: 'Group',
+	_selectBounds: false,
 	_selectChildren: true,
 	_serializeFields: {
 		children: []
@@ -4233,7 +4690,7 @@
 
 	_changed: function _changed(flags) {
 		_changed.base.call(this, flags);
-		if (flags & 1026) {
+		if (flags & 2050) {
 			this._clipItem = undefined;
 		}
 	},
@@ -4242,10 +4699,10 @@
 		var clipItem = this._clipItem;
 		if (clipItem === undefined) {
 			clipItem = null;
-			for (var i = 0, l = this._children.length; i < l; i++) {
-				var child = this._children[i];
-				if (child._clipMask) {
-					clipItem = child;
+			var children = this._children;
+			for (var i = 0, l = children.length; i < l; i++) {
+				if (children[i]._clipMask) {
+					clipItem = children[i];
 					break;
 				}
 			}
@@ -4264,31 +4721,36 @@
 			child.setClipMask(clipped);
 	},
 
+	_getBounds: function _getBounds(matrix, options) {
+		var clipItem = this._getClipItem();
+		return clipItem
+			? clipItem._getCachedBounds(clipItem._matrix.prepended(matrix),
+				Base.set({}, options, { stroke: false }))
+			: _getBounds.base.call(this, matrix, options);
+	},
+
+	_hitTestChildren: function _hitTestChildren(point, options, viewMatrix) {
+		var clipItem = this._getClipItem();
+		return (!clipItem || clipItem.contains(point))
+				&& _hitTestChildren.base.call(this, point, options, viewMatrix,
+					clipItem);
+	},
+
 	_draw: function(ctx, param) {
 		var clip = param.clip,
-			clipItem = !clip && this._getClipItem(),
-			draw = true;
+			clipItem = !clip && this._getClipItem();
 		param = param.extend({ clipItem: clipItem, clip: false });
 		if (clip) {
-			if (this._currentPath) {
-				ctx.currentPath = this._currentPath;
-				draw = false;
-			} else {
-				ctx.beginPath();
-				param.dontStart = param.dontFinish = true;
-			}
+			ctx.beginPath();
+			param.dontStart = param.dontFinish = true;
 		} else if (clipItem) {
 			clipItem.draw(ctx, param.extend({ clip: true }));
 		}
-		if (draw) {
-			for (var i = 0, l = this._children.length; i < l; i++) {
-				var item = this._children[i];
-				if (item !== clipItem)
-					item.draw(ctx, param);
-			}
-		}
-		if (clip) {
-			this._currentPath = ctx.currentPath;
+		var children = this._children;
+		for (var i = 0, l = children.length; i < l; i++) {
+			var item = children[i];
+			if (item !== clipItem)
+				item.draw(ctx, param);
 		}
 	}
 });
@@ -4296,47 +4758,12 @@
 var Layer = Group.extend({
 	_class: 'Layer',
 
-	initialize: function Layer(arg) {
-		var props = Base.isPlainObject(arg)
-				? new Base(arg)
-				: { children: Array.isArray(arg) ? arg : arguments },
-			insert = props.insert;
-		props.insert = false;
-		Group.call(this, props);
-		if (insert || insert === undefined) {
-			this._project.addChild(this);
-			this.activate();
-		}
-	},
-
-	_remove: function _remove(notifySelf, notifyParent) {
-		if (this._parent)
-			return _remove.base.call(this, notifySelf, notifyParent);
-		if (this._index != null) {
-			var project = this._project;
-			if (project._activeLayer === this)
-				project._activeLayer = this.getNextSibling()
-						|| this.getPreviousSibling();
-			Base.splice(project.layers, null, this._index, 1);
-			this._installEvents(false);
-			if (notifySelf && project._changes)
-				this._changed(5);
-			if (notifyParent) {
-				project._needsUpdate = true;
-			}
-			return true;
-		}
-		return false;
-	},
-
-	getNextSibling: function getNextSibling() {
-		return this._parent ? getNextSibling.base.call(this)
-				: this._project.layers[this._index + 1] || null;
-	},
-
-	getPreviousSibling: function getPreviousSibling() {
-		return this._parent ? getPreviousSibling.base.call(this)
-				: this._project.layers[this._index - 1] || null;
+	initialize: function Layer() {
+		Group.apply(this, arguments);
+	},
+
+	_getOwner: function() {
+		return this._parent || this._index != null && this._project;
 	},
 
 	isInserted: function isInserted() {
@@ -4347,10 +4774,7 @@
 		this._project._activeLayer = this;
 	},
 
-	_insertSibling: function _insertSibling(index, item, _preserve) {
-		return !this._parent
-				? this._project.insertChild(index, item, _preserve)
-				: _insertSibling.base.call(this, index, item, _preserve);
+	_hitTestSelf: function() {
 	}
 });
 
@@ -4358,15 +4782,15 @@
 	_class: 'Shape',
 	_applyMatrix: false,
 	_canApplyMatrix: false,
-	_boundsSelected: true,
+	_canScaleStroke: true,
 	_serializeFields: {
 		type: null,
 		size: null,
 		radius: null
 	},
 
-	initialize: function Shape(props) {
-		this._initialize(props);
+	initialize: function Shape(props, point) {
+		this._initialize(props, point);
 	},
 
 	_equals: function(item) {
@@ -4375,12 +4799,10 @@
 			&& Base.equals(this._radius, item._radius);
 	},
 
-	clone: function(insert) {
-		var copy = new Shape(Item.NO_INSERT);
-		copy.setType(this._type);
-		copy.setSize(this._size);
-		copy.setRadius(this._radius);
-		return this._clone(copy, insert);
+	copyContent: function(source) {
+		this.setType(source._type);
+		this.setSize(source._size);
+		this.setRadius(source._radius);
 	},
 
 	getType: function() {
@@ -4408,15 +4830,14 @@
 				width = size.width,
 				height = size.height;
 			if (type === 'rectangle') {
-				var radius = Size.min(this._radius, size.divide(2));
-				this._radius.set(radius.width, radius.height);
+				this._radius.set(Size.min(this._radius, size.divide(2)));
 			} else if (type === 'circle') {
 				width = height = (width + height) / 2;
 				this._radius = width / 2;
 			} else if (type === 'ellipse') {
-				this._radius.set(width / 2, height / 2);
-			}
-			this._size.set(width, height);
+				this._radius._set(width / 2, height / 2);
+			}
+			this._size._set(width, height);
 			this._changed(9);
 		}
 	},
@@ -4435,7 +4856,7 @@
 				return;
 			var size = radius * 2;
 			this._radius = radius;
-			this._size.set(size, size);
+			this._size._set(size, size);
 		} else {
 			radius = Size.read(arguments);
 			if (!this._radius) {
@@ -4443,12 +4864,12 @@
 			} else {
 				if (this._radius.equals(radius))
 					return;
-				this._radius.set(radius.width, radius.height);
+				this._radius.set(radius);
 				if (type === 'rectangle') {
 					var size = Size.max(this._size, radius.multiply(2));
-					this._size.set(size.width, size.height);
+					this._size.set(size);
 				} else if (type === 'ellipse') {
-					this._size.set(radius.width * 2, radius.height * 2);
+					this._size._set(radius.width * 2, radius.height * 2);
 				}
 			}
 		}
@@ -4460,18 +4881,27 @@
 	},
 
 	toPath: function(insert) {
-		var path = this._clone(new Path[Base.capitalize(this._type)]({
+		var path = new Path[Base.capitalize(this._type)]({
 			center: new Point(),
 			size: this._size,
 			radius: this._radius,
 			insert: false
-		}), insert);
+		});
+		path.copyAttributes(this);
 		if (paper.settings.applyMatrix)
 			path.setApplyMatrix(true);
+		if (insert === undefined || insert)
+			path.insertAbove(this);
 		return path;
 	},
 
-	_draw: function(ctx, param, strokeMatrix) {
+	toShape: '#clone',
+
+	_asPathItem: function() {
+		return this.toPath(false);
+	},
+
+	_draw: function(ctx, param, viewMatrix, strokeMatrix) {
 		var style = this._style,
 			hasFill = style.hasFill(),
 			hasStroke = style.hasStroke(),
@@ -4535,9 +4965,9 @@
 			ctx.closePath();
 		}
 		if (!dontPaint && (hasFill || hasStroke)) {
-			this._setStyles(ctx);
+			this._setStyles(ctx, param, viewMatrix);
 			if (hasFill) {
-				ctx.fill(style.getWindingRule());
+				ctx.fill(style.getFillRule());
 				ctx.shadowColor = 'rgba(0,0,0,0)';
 			}
 			if (hasStroke)
@@ -4549,37 +4979,42 @@
 		return !(this.hasFill() && this.hasStroke());
 	},
 
-	_getBounds: function(getter, matrix) {
-		var rect = new Rectangle(this._size).setCenter(0, 0);
-		if (getter !== 'getBounds' && this.hasStroke())
-			rect = rect.expand(this.getStrokeWidth());
-		return matrix ? matrix._transformBounds(rect) : rect;
+	_getBounds: function(matrix, options) {
+		var rect = new Rectangle(this._size).setCenter(0, 0),
+			style = this._style,
+			strokeWidth = options.stroke && style.hasStroke()
+					&& style.getStrokeWidth();
+		if (matrix)
+			rect = matrix._transformBounds(rect);
+		return strokeWidth
+				? rect.expand(Path._getStrokePadding(strokeWidth,
+					this._getStrokeMatrix(matrix, options)))
+				: rect;
 	}
 },
 new function() {
-
 	function getCornerCenter(that, point, expand) {
 		var radius = that._radius;
 		if (!radius.isZero()) {
 			var halfSize = that._size.divide(2);
-			for (var i = 0; i < 4; i++) {
-				var dir = new Point(i & 1 ? 1 : -1, i > 1 ? 1 : -1),
+			for (var q = 1; q <= 4; q++) {
+				var dir = new Point(q > 1 && q < 4 ? -1 : 1, q > 2 ? -1 : 1),
 					corner = dir.multiply(halfSize),
 					center = corner.subtract(dir.multiply(radius)),
-					rect = new Rectangle(corner, center);
-				if ((expand ? rect.expand(expand) : rect).contains(point))
-					return center;
-			}
-		}
-	}
-
-	function getEllipseRadius(point, radius) {
-		var angle = point.getAngleInRadians(),
-			width = radius.width * 2,
-			height = radius.height * 2,
-			x = width * Math.sin(angle),
-			y = height * Math.cos(angle);
-		return width * height / (2 * Math.sqrt(x * x + y * y));
+					rect = new Rectangle(
+							expand ? corner.add(dir.multiply(expand)) : corner,
+							center);
+				if (rect.contains(point))
+					return { point: center, quadrant: q };
+			}
+		}
+	}
+
+	function isOnEllipseStroke(point, radius, padding, quadrant) {
+		var vector = point.divide(radius);
+		return (!quadrant || vector.isInQuadrant(quadrant)) &&
+				vector.subtract(vector.normalize()).multiply(radius)
+					.divide(padding).length <= 1;
 	}
 
 	return {
@@ -4587,7 +5022,7 @@
 			if (this._type === 'rectangle') {
 				var center = getCornerCenter(this, point);
 				return center
-						? point.subtract(center).divide(this._radius)
+						? point.subtract(center.point).divide(this._radius)
 							.getLength() <= 1
 						: _contains.base.call(this, point);
 			} else {
@@ -4595,34 +5030,37 @@
 			}
 		},
 
-		_hitTestSelf: function _hitTestSelf(point, options) {
-			var hit = false;
-			if (this.hasStroke()) {
+		_hitTestSelf: function _hitTestSelf(point, options, viewMatrix,
+				strokeMatrix) {
+			var hit = false,
+				style = this._style,
+				hitStroke = options.stroke && style.hasStroke(),
+				hitFill = options.fill && style.hasFill();
+			if (hitStroke || hitFill) {
 				var type = this._type,
 					radius = this._radius,
-					strokeWidth = this.getStrokeWidth() + 2 * options.tolerance;
+					strokeRadius = hitStroke ? style.getStrokeWidth() / 2 : 0,
+					strokePadding = options._tolerancePadding.add(
+						Path._getStrokePadding(strokeRadius,
+							!style.getStrokeScaling() && strokeMatrix));
 				if (type === 'rectangle') {
-					var center = getCornerCenter(this, point, strokeWidth);
+					var padding = strokePadding.multiply(2),
+						center = getCornerCenter(this, point, padding);
 					if (center) {
-						var pt = point.subtract(center);
-						hit = 2 * Math.abs(pt.getLength()
-								- getEllipseRadius(pt, radius)) <= strokeWidth;
+						hit = isOnEllipseStroke(point.subtract(center.point),
+								radius, strokePadding, center.quadrant);
 					} else {
 						var rect = new Rectangle(this._size).setCenter(0, 0),
-							outer = rect.expand(strokeWidth),
-							inner = rect.expand(-strokeWidth);
+							outer = rect.expand(padding),
+							inner = rect.expand(padding.negate());
 						hit = outer._containsPoint(point)
 								&& !inner._containsPoint(point);
 					}
 				} else {
-					if (type === 'ellipse')
-						radius = getEllipseRadius(point, radius);
-					hit = 2 * Math.abs(point.getLength() - radius)
-							<= strokeWidth;
-				}
-			}
-			return hit
-					? new HitResult('stroke', this)
+					hit = isOnEllipseStroke(point, radius, strokePadding);
+				}
+			}
+			return hit ? new HitResult(hitStroke ? 'stroke' : 'fill', this)
 					: _hitTestSelf.base.apply(this, arguments);
 		}
 	};
@@ -4630,11 +5068,11 @@
 
 statics: new function() {
 	function createShape(type, point, size, radius, args) {
-		var item = new Shape(Base.getNamed(args));
+		var item = new Shape(Base.getNamed(args), point);
 		item._type = type;
 		item._size = size;
 		item._radius = radius;
-		return item.translate(point);
+		return item;
 	}
 
 	return {
@@ -4677,23 +5115,43 @@
 }});
 
 var Raster = Item.extend({
+}, {
 	_class: 'Raster',
 	_applyMatrix: false,
 	_canApplyMatrix: false,
-	_boundsGetter: 'getBounds',
-	_boundsSelected: true,
+	_boundsOptions: { stroke: false, handle: false },
 	_serializeFields: {
 		crossOrigin: null,
 		source: null
 	},
-
-	initialize: function Raster(object, position) {
-		if (!this._initialize(object,
-				position !== undefined && Point.read(arguments, 1))) {
-			if (typeof object === 'string') {
-				this.setSource(object);
-			} else {
-				this.setImage(object);
+	_prioritize: ['crossOrigin'],
+	_smoothing: true,
+	beans: true,
+
+	initialize: function Raster(source, position) {
+		if (!this._initialize(source,
+				position !== undefined && Point.read(arguments))) {
+			var image,
+				type = typeof source,
+				object = type === 'string'
+					? document.getElementById(source)
+					: type  === 'object'
+						? source
+						: null;
+			if (object && object !== Item.NO_INSERT) {
+				if (object.getContent || object.naturalHeight != null) {
+					image = object;
+				} else if (object) {
+					var size = Size.read(arguments);
+					if (!size.isZero()) {
+						image = CanvasProvider.getCanvas(size);
+					}
+				}
+			}
+			if (image) {
+				this.setImage(image);
+			} else {
+				this.setSource(source);
 			}
 		}
 		if (!this._size) {
@@ -4706,19 +5164,17 @@
 		return this.getSource() === item.getSource();
 	},
 
-	clone: function(insert) {
-		var copy = new Raster(Item.NO_INSERT),
-			image = this._image,
-			canvas = this._canvas;
+	copyContent: function(source) {
+		var image = source._image,
+			canvas = source._canvas;
 		if (image) {
-			copy.setImage(image);
+			this._setImage(image);
 		} else if (canvas) {
-			var copyCanvas = CanvasProvider.getCanvas(this._size);
+			var copyCanvas = CanvasProvider.getCanvas(source._size);
 			copyCanvas.getContext('2d').drawImage(canvas, 0, 0);
-			copy.setImage(copyCanvas);
-		}
-		copy._crossOrigin = this._crossOrigin;
-		return this._clone(copy, insert);
+			this._setImage(copyCanvas);
+		}
+		this._crossOrigin = source._crossOrigin;
 	},
 
 	getSize: function() {
@@ -4732,7 +5188,7 @@
 		if (!size.equals(this._size)) {
 			if (size.width > 0 && size.height > 0) {
 				var element = this.getElement();
-				this.setImage(CanvasProvider.getCanvas(size));
+				this._setImage(CanvasProvider.getCanvas(size));
 				if (element)
 					this.getContext(true).drawImage(element, 0, 0,
 							size.width, size.height);
@@ -4760,6 +5216,10 @@
 		this.setSize(this.getWidth(), height);
 	},
 
+	getLoaded: function() {
+		return this._loaded;
+	},
+
 	isEmpty: function() {
 		var size = this._size;
 		return !size || size.width === 0 && size.height === 0;
@@ -4783,6 +5243,32 @@
 	},
 
 	setImage: function(image) {
+		var that = this;
+
+		function emit(event) {
+			var view = that.getView(),
+				type = event && event.type || 'load';
+			if (view && that.responds(type)) {
+				paper = view._scope;
+				that.emit(type, new Event(event));
+			}
+		}
+
+		this._setImage(image);
+		if (this._loaded) {
+			setTimeout(emit, 0);
+		} else if (image) {
+			DomEvent.add(image, {
+				load: function(event) {
+					that._setImage(image);
+					emit(event);
+				},
+				error: emit
+			});
+		}
+	},
+
+	_setImage: function(image) {
 		if (this._canvas)
 			CanvasProvider.release(this._canvas);
 		if (image && image.getContext) {
@@ -4792,13 +5278,13 @@
 		} else {
 			this._image = image;
 			this._canvas = null;
-			this._loaded = image && image.complete;
+			this._loaded = !!(image && image.src && image.complete);
 		}
 		this._size = new Size(
 				image ? image.naturalWidth || image.width : 0,
 				image ? image.naturalHeight || image.height : 0);
 		this._context = null;
-		this._changed(521);
+		this._changed(1033);
 	},
 
 	getCanvas: function() {
@@ -4817,12 +5303,12 @@
 
 	setCanvas: '#setImage',
 
-	getContext: function(modify) {
+	getContext: function(_change) {
 		if (!this._context)
 			this._context = this.getCanvas().getContext('2d');
-		if (modify) {
+		if (_change) {
 			this._image = null;
-			this._changed(513);
+			this._changed(1025);
 		}
 		return this._context;
 	},
@@ -4832,45 +5318,39 @@
 	},
 
 	getSource: function() {
-		return this._image && this._image.src || this.toDataURL();
+		var image = this._image;
+		return image && image.src || this.toDataURL();
 	},
 
 	setSource: function(src) {
-		var that = this,
-			crossOrigin = this._crossOrigin,
-			image;
-
-		function loaded() {
-			var view = that.getView();
-			if (view) {
-				paper = view._scope;
-				that.setImage(image);
-				that.emit('load');
-				view.update();
-			}
-		}
-
-		image = document.getElementById(src) || new Image();
+		var image = new self.Image(),
+			crossOrigin = this._crossOrigin;
 		if (crossOrigin)
 			image.crossOrigin = crossOrigin;
-		if (image.naturalWidth && image.naturalHeight) {
-			setTimeout(loaded, 0);
-		} else {
-			DomEvent.add(image, { load: loaded });
-			if (!image.src)
-				image.src = src;
-		}
+		if (src)
+			image.src = src;
 		this.setImage(image);
 	},
 
 	getCrossOrigin: function() {
-		return this._image && this._image.crossOrigin || this._crossOrigin || '';
+		var image = this._image;
+		return image && image.crossOrigin || this._crossOrigin || '';
 	},
 
 	setCrossOrigin: function(crossOrigin) {
 		this._crossOrigin = crossOrigin;
-		if (this._image)
-			this._image.crossOrigin = crossOrigin;
+		var image = this._image;
+		if (image)
+			image.crossOrigin = crossOrigin;
+	},
+
+	getSmoothing: function() {
+		return this._smoothing;
+	},
+
+	setSmoothing: function(smoothing) {
+		this._smoothing = smoothing;
+		this._changed(257);
 	},
 
 	getElement: function() {
@@ -4890,19 +5370,20 @@
 	getSubRaster: function() {
 		var rect = Rectangle.read(arguments),
 			raster = new Raster(Item.NO_INSERT);
-		raster.setImage(this.getSubCanvas(rect));
+		raster._setImage(this.getSubCanvas(rect));
 		raster.translate(rect.getCenter().subtract(this.getSize().divide(2)));
-		raster._matrix.preConcatenate(this._matrix);
+		raster._matrix.prepend(this._matrix);
 		raster.insertAbove(this);
 		return raster;
 	},
 
 	toDataURL: function() {
-		var src = this._image && this._image.src;
+		var image = this._image,
+			src = image && image.src;
 		if (/^data:/.test(src))
 			return src;
 		var canvas = this.getCanvas();
-		return canvas ? canvas.toDataURL() : null;
+		return canvas ? canvas.toDataURL.apply(canvas, arguments) : null;
 	},
 
 	drawImage: function(image ) {
@@ -4917,11 +5398,15 @@
 		} else if (object instanceof PathItem) {
 			path = object;
 			bounds = object.getBounds();
-		} else if (object.width) {
-			bounds = new Rectangle(object);
-		} else if (object.x) {
-			bounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1);
-		}
+		} else if (typeof object === 'object') {
+			if ('width' in object) {
+				bounds = new Rectangle(object);
+			} else if ('x' in object) {
+				bounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1);
+			}
+		}
+		if (!bounds)
+			return null;
 		var sampleSize = 32,
 			width = Math.min(bounds.width, sampleSize),
 			height = Math.min(bounds.height, sampleSize);
@@ -4984,6 +5469,11 @@
 		ctx.putImageData(imageData, point.x, point.y);
 	},
 
+	clear: function() {
+		var size = this._size;
+		this.getContext(true).clearRect(0, 0, size.width + 1, size.height + 1);
+	},
+
 	createImageData: function() {
 		var size = Size.read(arguments);
 		return this.getContext().createImageData(size.width, size.height);
@@ -5002,7 +5492,7 @@
 		this.getContext(true).putImageData(data, point.x, point.y);
 	},
 
-	_getBounds: function(getter, matrix) {
+	_getBounds: function(matrix, options) {
 		var rect = new Rectangle(this._size).setCenter(0, 0);
 		return matrix ? matrix._transformBounds(rect) : rect;
 	},
@@ -5021,10 +5511,17 @@
 		}
 	},
 
-	_draw: function(ctx) {
+	_draw: function(ctx, param, viewMatrix) {
 		var element = this.getElement();
-		if (element) {
+		if (element && element.width > 0 && element.height > 0) {
 			ctx.globalAlpha = this._opacity;
+
+			this._setStyles(ctx, param, viewMatrix);
+
+			DomElement.setPrefixed(
+				ctx, 'imageSmoothingEnabled', this._smoothing
+			);
+
 			ctx.drawImage(element,
 					-this._size.width / 2, -this._size.height / 2);
 		}
@@ -5035,62 +5532,122 @@
 	}
 });
 
-var PlacedSymbol = Item.extend({
-	_class: 'PlacedSymbol',
+var SymbolItem = Item.extend({
+	_class: 'SymbolItem',
 	_applyMatrix: false,
 	_canApplyMatrix: false,
-	_boundsGetter: { getBounds: 'getStrokeBounds' },
-	_boundsSelected: true,
+	_boundsOptions: { stroke: true },
 	_serializeFields: {
 		symbol: null
 	},
 
-	initialize: function PlacedSymbol(arg0, arg1) {
+	initialize: function SymbolItem(arg0, arg1) {
 		if (!this._initialize(arg0,
 				arg1 !== undefined && Point.read(arguments, 1)))
-			this.setSymbol(arg0 instanceof Symbol ? arg0 : new Symbol(arg0));
+			this.setDefinition(arg0 instanceof SymbolDefinition ?
+					arg0 : new SymbolDefinition(arg0));
 	},
 
 	_equals: function(item) {
-		return this._symbol === item._symbol;
-	},
-
-	getSymbol: function() {
-		return this._symbol;
-	},
-
-	setSymbol: function(symbol) {
-		this._symbol = symbol;
+		return this._definition === item._definition;
+	},
+
+	copyContent: function(source) {
+		this.setDefinition(source._definition);
+	},
+
+	getDefinition: function() {
+		return this._definition;
+	},
+
+	setDefinition: function(definition) {
+		this._definition = definition;
 		this._changed(9);
 	},
 
-	clone: function(insert) {
-		var copy = new PlacedSymbol(Item.NO_INSERT);
-		copy.setSymbol(this._symbol);
-		return this._clone(copy, insert);
-	},
+	getSymbol: '#getDefinition',
+	setSymbol: '#setDefinition',
 
 	isEmpty: function() {
-		return this._symbol._definition.isEmpty();
-	},
-
-	_getBounds: function(getter, matrix, cacheItem) {
-		var definition = this.symbol._definition;
-		return definition._getCachedBounds(getter,
-				matrix && matrix.chain(definition._matrix), cacheItem);
-	},
-
-	_hitTestSelf: function(point, options) {
-		var res = this._symbol._definition._hitTest(point, options);
+		return this._definition._item.isEmpty();
+	},
+
+	_getBounds: function(matrix, options) {
+		var item = this._definition._item;
+		return item._getCachedBounds(item._matrix.prepended(matrix), options);
+	},
+
+	_hitTestSelf: function(point, options, viewMatrix) {
+		var res = this._definition._item._hitTest(point, options, viewMatrix);
 		if (res)
 			res.item = this;
 		return res;
 	},
 
 	_draw: function(ctx, param) {
-		this.symbol._definition.draw(ctx, param);
-	}
-
+		this._definition._item.draw(ctx, param);
+	}
+
+});
+
+var SymbolDefinition = Base.extend({
+	_class: 'SymbolDefinition',
+
+	initialize: function SymbolDefinition(item, dontCenter) {
+		this._id = UID.get();
+		this.project = paper.project;
+		if (item)
+			this.setItem(item, dontCenter);
+	},
+
+	_serialize: function(options, dictionary) {
+		return dictionary.add(this, function() {
+			return Base.serialize([this._class, this._item],
+					options, false, dictionary);
+		});
+	},
+
+	_changed: function(flags) {
+		if (flags & 8)
+			Item._clearBoundsCache(this);
+		if (flags & 1)
+			this.project._changed(flags);
+	},
+
+	getItem: function() {
+		return this._item;
+	},
+
+	setItem: function(item, _dontCenter) {
+		if (item._symbol)
+			item = item.clone();
+		if (this._item)
+			this._item._symbol = null;
+		this._item = item;
+		item.remove();
+		item.setSelected(false);
+		if (!_dontCenter)
+			item.setPosition(new Point());
+		item._symbol = this;
+		this._changed(9);
+	},
+
+	getDefinition: '#getItem',
+	setDefinition: '#setItem',
+
+	place: function(position) {
+		return new SymbolItem(this, position);
+	},
+
+	clone: function() {
+		return new SymbolDefinition(this._item.clone(false));
+	},
+
+	equals: function(symbol) {
+		return symbol === this
+				|| symbol && this._item.equals(symbol._item)
+				|| false;
+	}
 });
 
 var HitResult = Base.extend({
@@ -5099,15 +5656,14 @@
 	initialize: function HitResult(type, item, values) {
 		this.type = type;
 		this.item = item;
-		if (values) {
-			values.enumerable = true;
+		if (values)
 			this.inject(values);
-		}
 	},
 
 	statics: {
-		getOptions: function(options) {
-			return new Base({
+		getOptions: function(args) {
+			var options = args && Base.read(args);
+			return Base.set({
 				type: null,
 				tolerance: paper.settings.hitTolerance,
 				fill: !options,
@@ -5115,6 +5671,7 @@
 				segments: !options,
 				handles: false,
 				ends: false,
+				position: false,
 				center: false,
 				bounds: false,
 				guides: false,
@@ -5127,39 +5684,46 @@
 var Segment = Base.extend({
 	_class: 'Segment',
 	beans: true,
+	_selection: 0,
 
 	initialize: function Segment(arg0, arg1, arg2, arg3, arg4, arg5) {
 		var count = arguments.length,
-			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 === 2 && typeof arg0 === 'number') {
-			point = arguments;
-		} else if (count <= 3) {
-			point = arg0;
-			handleIn = arg1;
-			handleOut = arg2;
-		} else {
-			point = arg0 !== undefined ? [ arg0, arg1 ] : null;
-			handleIn = arg2 !== undefined ? [ arg2, arg3 ] : null;
-			handleOut = arg4 !== undefined ? [ arg4, arg5 ] : null;
+			point, handleIn, handleOut, selection;
+		if (count > 0) {
+			if (arg0 == null || typeof arg0 === 'object') {
+				if (count === 1 && arg0 && 'point' in arg0) {
+					point = arg0.point;
+					handleIn = arg0.handleIn;
+					handleOut = arg0.handleOut;
+					selection = arg0.selection;
+				} else {
+					point = arg0;
+					handleIn = arg1;
+					handleOut = arg2;
+					selection = arg3;
+				}
+			} else {
+				point = [ arg0, arg1 ];
+				handleIn = arg2 !== undefined ? [ arg2, arg3 ] : null;
+				handleOut = arg4 !== undefined ? [ arg4, arg5 ] : null;
+			}
 		}
 		new SegmentPoint(point, this, '_point');
 		new SegmentPoint(handleIn, this, '_handleIn');
 		new SegmentPoint(handleOut, this, '_handleOut');
-	},
-
-	_serialize: function(options) {
-		return Base.serialize(this.isStraight() ? this._point
-				: [this._point, this._handleIn, this._handleOut],
-				options, true);
+		if (selection)
+			this.setSelection(selection);
+	},
+
+	_serialize: function(options, dictionary) {
+		var point = this._point,
+			selection = this._selection,
+			obj = selection || this.hasHandles()
+					? [point, this._handleIn, this._handleOut]
+					: point;
+		if (selection)
+			obj.push(selection);
+		return Base.serialize(obj, options, true, dictionary);
 	},
 
 	_changed: function(point) {
@@ -5178,7 +5742,7 @@
 					&& (curve = curves[index]))
 				curve._changed();
 		}
-		path._changed(25);
+		path._changed(41);
 	},
 
 	getPoint: function() {
@@ -5186,8 +5750,7 @@
 	},
 
 	setPoint: function() {
-		var point = Point.read(arguments);
-		this._point.set(point.x, point.y);
+		this._point.set(Point.read(arguments));
 	},
 
 	getHandleIn: function() {
@@ -5195,8 +5758,7 @@
 	},
 
 	setHandleIn: function() {
-		var point = Point.read(arguments);
-		this._handleIn.set(point.x, point.y);
+		this._handleIn.set(Point.read(arguments));
 	},
 
 	getHandleOut: function() {
@@ -5204,70 +5766,50 @@
 	},
 
 	setHandleOut: function() {
-		var point = Point.read(arguments);
-		this._handleOut.set(point.x, point.y);
+		this._handleOut.set(Point.read(arguments));
 	},
 
 	hasHandles: function() {
-		return !this.isStraight();
-	},
-
-	isStraight: function() {
-		return this._handleIn.isZero() && this._handleOut.isZero();
-	},
-
-	isLinear: function() {
-		return Segment.isLinear(this, this.getNext());
-	},
-
-	isCollinear: function(segment) {
-		return Segment.isCollinear(this, this.getNext(),
-				segment, segment.getNext());
-	},
-
-	isColinear: '#isCollinear',
-
-	isOrthogonal: function() {
-		return Segment.isOrthogonal(this.getPrevious(), this, this.getNext());
-	},
-
-	isOrthogonalArc: function() {
-		return Segment.isOrthogonalArc(this, this.getNext());
-	},
-
-	isArc: '#isOrthogonalArc',
-
-	_selectionState: 0,
-
-	isSelected: function(_point) {
-		var state = this._selectionState;
-		return !_point ? !!(state & 7)
-			: _point === this._point ? !!(state & 4)
-			: _point === this._handleIn ? !!(state & 1)
-			: _point === this._handleOut ? !!(state & 2)
-			: false;
-	},
-
-	setSelected: function(selected, _point) {
-		var path = this._path,
-			selected = !!selected,
-			state = this._selectionState,
-			oldState = state,
-			flag = !_point ? 7
-					: _point === this._point ? 4
-					: _point === this._handleIn ? 1
-					: _point === this._handleOut ? 2
-					: 0;
-		if (selected) {
-			state |= flag;
-		} else {
-			state &= ~flag;
-		}
-		this._selectionState = state;
-		if (path && state !== oldState) {
-			path._updateSelection(this, oldState, state);
-			path._changed(129);
-		}
+		return !this._handleIn.isZero() || !this._handleOut.isZero();
+	},
+
+	isSmooth: function() {
+		var handleIn = this._handleIn,
+			handleOut = this._handleOut;
+		return !handleIn.isZero() && !handleOut.isZero()
+				&& handleIn.isCollinear(handleOut);
+	},
+
+	clearHandles: function() {
+		this._handleIn._set(0, 0);
+		this._handleOut._set(0, 0);
+	},
+
+	getSelection: function() {
+		return this._selection;
+	},
+
+	setSelection: function(selection) {
+		var oldSelection = this._selection,
+			path = this._path;
+		this._selection = selection = selection || 0;
+		if (path && selection !== oldSelection) {
+			path._updateSelection(this, oldSelection, selection);
+			path._changed(257);
+		}
+	},
+
+	_changeSelection: function(flag, selected) {
+		var selection = this._selection;
+		this.setSelection(selected ? selection | flag : selection & ~flag);
+	},
+
+	isSelected: function() {
+		return !!(this._selection & 7);
+	},
+
+	setSelected: function(selected) {
+		this._changeSelection(7, selected);
 	},
 
 	getIndex: function() {
@@ -5303,13 +5845,80 @@
 				|| this._path._closed && segments[0]) || null;
 	},
 
+	smooth: function(options, _first, _last) {
+		var opts = options || {},
+			type = opts.type,
+			factor = opts.factor,
+			prev = this.getPrevious(),
+			next = this.getNext(),
+			p0 = (prev || this)._point,
+			p1 = this._point,
+			p2 = (next || this)._point,
+			d1 = p0.getDistance(p1),
+			d2 = p1.getDistance(p2);
+		if (!type || type === 'catmull-rom') {
+			var a = factor === undefined ? 0.5 : factor,
+				d1_a = Math.pow(d1, a),
+				d1_2a = d1_a * d1_a,
+				d2_a = Math.pow(d2, a),
+				d2_2a = d2_a * d2_a;
+			if (!_first && prev) {
+				var A = 2 * d2_2a + 3 * d2_a * d1_a + d1_2a,
+					N = 3 * d2_a * (d2_a + d1_a);
+				this.setHandleIn(N !== 0
+					? new Point(
+						(d2_2a * p0._x + A * p1._x - d1_2a * p2._x) / N - p1._x,
+						(d2_2a * p0._y + A * p1._y - d1_2a * p2._y) / N - p1._y)
+					: new Point());
+			}
+			if (!_last && next) {
+				var A = 2 * d1_2a + 3 * d1_a * d2_a + d2_2a,
+					N = 3 * d1_a * (d1_a + d2_a);
+				this.setHandleOut(N !== 0
+					? new Point(
+						(d1_2a * p2._x + A * p1._x - d2_2a * p0._x) / N - p1._x,
+						(d1_2a * p2._y + A * p1._y - d2_2a * p0._y) / N - p1._y)
+					: new Point());
+			}
+		} else if (type === 'geometric') {
+			if (prev && next) {
+				var vector = p0.subtract(p2),
+					t = factor === undefined ? 0.4 : factor,
+					k = t * d1 / (d1 + d2);
+				if (!_first)
+					this.setHandleIn(vector.multiply(k));
+				if (!_last)
+					this.setHandleOut(vector.multiply(k - t));
+			}
+		} else {
+			throw new Error('Smoothing method \'' + type + '\' not supported.');
+		}
+	},
+
 	getPrevious: function() {
 		var segments = this._path && this._path._segments;
 		return segments && (segments[this._index - 1]
 				|| this._path._closed && segments[segments.length - 1]) || null;
 	},
 
+	isFirst: function() {
+		return !this._index;
+	},
+
+	isLast: function() {
+		var path = this._path;
+		return path && this._index === path._segments.length - 1 || false;
+	},
+
 	reverse: function() {
+		var handleIn = this._handleIn,
+			handleOut = this._handleOut,
+			tmp = handleIn.clone();
+		handleIn.set(handleOut);
+		handleOut.set(tmp);
+	},
+
+	reversed: function() {
 		return new Segment(this._point, this._handleOut, this._handleIn);
 	},
 
@@ -5343,6 +5952,27 @@
 		this._changed();
 	},
 
+	interpolate: function(from, to, factor) {
+		var u = 1 - factor,
+			v = factor,
+			point1 = from._point,
+			point2 = to._point,
+			handleIn1 = from._handleIn,
+			handleIn2 = to._handleIn,
+			handleOut2 = to._handleOut,
+			handleOut1 = from._handleOut;
+		this._point._set(
+				u * point1._x + v * point2._x,
+				u * point1._y + v * point2._y, true);
+		this._handleIn._set(
+				u * handleIn1._x + v * handleIn2._x,
+				u * handleIn1._y + v * handleIn2._y, true);
+		this._handleOut._set(
+				u * handleOut1._x + v * handleOut2._x,
+				u * handleOut1._y + v * handleOut2._y, true);
+		this._changed();
+	},
+
 	_transformCoordinates: function(matrix, coords, change) {
 		var point = this._point,
 			handleIn = !change || !this._handleIn.isZero()
@@ -5369,7 +5999,7 @@
 			if (change) {
 				point._x = x;
 				point._y = y;
-				i  = 2;
+				i = 2;
 				if (handleIn) {
 					handleIn._x = coords[i++] - x;
 					handleIn._y = coords[i++] - y;
@@ -5390,52 +6020,13 @@
 			}
 		}
 		return coords;
-	},
-
-   statics: {
-
-		isLinear: function(seg1, seg2) {
-			var l = seg2._point.subtract(seg1._point);
-			return l.isCollinear(seg1._handleOut)
-					&& l.isCollinear(seg2._handleIn);
-		},
-
-		isCollinear: function(seg1, seg2, seg3, seg4) {
-			return seg1._handleOut.isZero() && seg2._handleIn.isZero()
-					&& seg3._handleOut.isZero() && seg4._handleIn.isZero()
-					&& seg2._point.subtract(seg1._point).isCollinear(
-						seg4._point.subtract(seg3._point));
-		},
-
-		isOrthogonal: function(seg1, seg2, seg3) {
-			return seg1._handleOut.isZero() && seg2._handleIn.isZero()
-				&& seg2._handleOut.isZero() && seg3._handleIn.isZero()
-				&& seg2._point.subtract(seg1._point).isOrthogonal(
-						seg3._point.subtract(seg2._point));
-		},
-
-		isOrthogonalArc: function(seg1, seg2) {
-			var handle1 = seg1._handleOut,
-				handle2 = seg2._handleIn,
-				kappa = 0.5522847498307936;
-			if (handle1.isOrthogonal(handle2)) {
-				var pt1 = seg1._point,
-					pt2 = seg2._point,
-					corner = new Line(pt1, handle1, true).intersect(
-							new Line(pt2, handle2, true), true);
-				return corner && Numerical.isZero(handle1.getLength() /
-						corner.subtract(pt1).getLength() - kappa)
-					&& Numerical.isZero(handle2.getLength() /
-						corner.subtract(pt2).getLength() - kappa);
-			}
-			return false;
-		},
 	}
 });
 
 var SegmentPoint = Point.extend({
 	initialize: function SegmentPoint(point, owner, key) {
-		var x, y, selected;
+		var x, y,
+			selected;
 		if (!point) {
 			x = y = 0;
 		} else if ((x = point[0]) !== undefined) {
@@ -5457,22 +6048,13 @@
 			this.setSelected(true);
 	},
 
-	set: function(x, y) {
+	_set: function(x, y) {
 		this._x = x;
 		this._y = y;
 		this._owner._changed(this);
 		return this;
 	},
 
-	_serialize: function(options) {
-		var f = options.formatter,
-			x = f.number(this._x),
-			y = f.number(this._y);
-		return this.isSelected()
-				? { x: x, y: y, selected: true }
-				: [x, y];
-	},
-
 	getX: function() {
 		return this._x;
 	},
@@ -5492,65 +6074,124 @@
 	},
 
 	isZero: function() {
-		return Numerical.isZero(this._x) && Numerical.isZero(this._y);
+		var isZero = Numerical.isZero;
+		return isZero(this._x) && isZero(this._y);
+	},
+
+	isSelected: function() {
+		return !!(this._owner._selection & this._getSelection());
 	},
 
 	setSelected: function(selected) {
-		this._owner.setSelected(selected, this);
-	},
-
-	isSelected: function() {
-		return this._owner.isSelected(this);
+		this._owner._changeSelection(this._getSelection(), selected);
+	},
+
+	_getSelection: function() {
+		var owner = this._owner;
+		return this === owner._point ? 1
+			: this === owner._handleIn ? 2
+			: this === owner._handleOut ? 4
+			: 0;
 	}
 });
 
 var Curve = Base.extend({
 	_class: 'Curve',
+	beans: true,
 
 	initialize: function Curve(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
-		var count = arguments.length;
+		var count = arguments.length,
+			seg1, seg2,
+			point1, point2,
+			handle1, handle2;
 		if (count === 3) {
 			this._path = arg0;
-			this._segment1 = arg1;
-			this._segment2 = arg2;
-		} else if (count === 0) {
-			this._segment1 = new Segment();
-			this._segment2 = new Segment();
+			seg1 = arg1;
+			seg2 = arg2;
+		} else if (!count) {
+			seg1 = new Segment();
+			seg2 = new Segment();
 		} else if (count === 1) {
-			this._segment1 = new Segment(arg0.segment1);
-			this._segment2 = new Segment(arg0.segment2);
+			if ('segment1' in arg0) {
+				seg1 = new Segment(arg0.segment1);
+				seg2 = new Segment(arg0.segment2);
+			} else if ('point1' in arg0) {
+				point1 = arg0.point1;
+				handle1 = arg0.handle1;
+				handle2 = arg0.handle2;
+				point2 = arg0.point2;
+			} else if (Array.isArray(arg0)) {
+				point1 = [arg0[0], arg0[1]];
+				point2 = [arg0[6], arg0[7]];
+				handle1 = [arg0[2] - arg0[0], arg0[3] - arg0[1]];
+				handle2 = [arg0[4] - arg0[6], arg0[5] - arg0[7]];
+			}
 		} else if (count === 2) {
-			this._segment1 = new Segment(arg0);
-			this._segment2 = new Segment(arg1);
-		} else {
-			var point1, handle1, handle2, point2;
-			if (count === 4) {
-				point1 = arg0;
-				handle1 = arg1;
-				handle2 = arg2;
-				point2 = arg3;
-			} else if (count === 8) {
-				point1 = [arg0, arg1];
-				point2 = [arg6, arg7];
-				handle1 = [arg2 - arg0, arg3 - arg1];
-				handle2 = [arg4 - arg6, arg5 - arg7];
-			}
-			this._segment1 = new Segment(point1, null, handle1);
-			this._segment2 = new Segment(point2, handle2, null);
-		}
+			seg1 = new Segment(arg0);
+			seg2 = new Segment(arg1);
+		} else if (count === 4) {
+			point1 = arg0;
+			handle1 = arg1;
+			handle2 = arg2;
+			point2 = arg3;
+		} else if (count === 8) {
+			point1 = [arg0, arg1];
+			point2 = [arg6, arg7];
+			handle1 = [arg2 - arg0, arg3 - arg1];
+			handle2 = [arg4 - arg6, arg5 - arg7];
+		}
+		this._segment1 = seg1 || new Segment(point1, null, handle1);
+		this._segment2 = seg2 || new Segment(point2, handle2, null);
+	},
+
+	_serialize: function(options, dictionary) {
+		return Base.serialize(this.hasHandles()
+				? [this.getPoint1(), this.getHandle1(), this.getHandle2(),
+					this.getPoint2()]
+				: [this.getPoint1(), this.getPoint2()],
+				options, true, dictionary);
 	},
 
 	_changed: function() {
 		this._length = this._bounds = undefined;
 	},
 
+	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(', ') + ' }';
+	},
+
+	classify: function() {
+		return Curve.classify(this.getValues());
+	},
+
+	remove: function() {
+		var removed = false;
+		if (this._path) {
+			var segment2 = this._segment2,
+				handleOut = segment2._handleOut;
+			removed = segment2.remove();
+			if (removed)
+				this._segment1._handleOut.set(handleOut);
+		}
+		return removed;
+	},
+
 	getPoint1: function() {
 		return this._segment1._point;
 	},
 
 	setPoint1: function() {
-		var point = Point.read(arguments);
-		this._segment1._point.set(point.x, point.y);
+		this._segment1._point.set(Point.read(arguments));
 	},
 
 	getPoint2: function() {
@@ -5558,8 +6199,7 @@
 	},
 
 	setPoint2: function() {
-		var point = Point.read(arguments);
-		this._segment2._point.set(point.x, point.y);
+		this._segment2._point.set(Point.read(arguments));
 	},
 
 	getHandle1: function() {
@@ -5567,8 +6207,7 @@
 	},
 
 	setHandle1: function() {
-		var point = Point.read(arguments);
-		this._segment1._handleOut.set(point.x, point.y);
+		this._segment1._handleOut.set(Point.read(arguments));
 	},
 
 	getHandle2: function() {
@@ -5576,8 +6215,7 @@
 	},
 
 	setHandle2: function() {
-		var point = Point.read(arguments);
-		this._segment2._handleIn.set(point.x, point.y);
+		this._segment2._handleIn.set(Point.read(arguments));
 	},
 
 	getSegment1: function() {
@@ -5608,9 +6246,19 @@
 				|| this._path._closed && curves[curves.length - 1]) || null;
 	},
 
+	isFirst: function() {
+		return !this._segment1._index;
+	},
+
+	isLast: function() {
+		var path = this._path;
+		return path && this._segment1._index === path._curves.length - 1
+				|| false;
+	},
+
 	isSelected: function() {
 		return this.getPoint1().isSelected()
-				&& this.getHandle2().isSelected()
+				&& this.getHandle1().isSelected()
 				&& this.getHandle2().isSelected()
 				&& this.getPoint2().isSelected();
 	},
@@ -5632,14 +6280,11 @@
 		for (var i = 0; i < 8; i += 2)
 			points.push(new Point(coords[i], coords[i + 1]));
 		return points;
-	},
-
+	}
+}, {
 	getLength: function() {
-		if (this._length == null) {
-			this._length = this.isLinear()
-				? this._segment2._point.getDistance(this._segment1._point)
-				: Curve.getLength(this.getValues(), 0, 1);
-		}
+		if (this._length == null)
+			this._length = Curve.getLength(this.getValues(), 0, 1);
 		return this._length;
 	},
 
@@ -5647,6 +6292,10 @@
 		return Curve.getArea(this.getValues());
 	},
 
+	getLine: function() {
+		return new Line(this._segment1._point, this._segment2._point);
+	},
+
 	getPart: function(from, to) {
 		return new Curve(Curve.getPart(this.getValues(), from, to));
 	},
@@ -5655,245 +6304,267 @@
 		return Curve.getLength(this.getValues(), from, to);
 	},
 
-	hasHandles: function() {
-		return !this._segment1._handleOut.isZero()
-				|| !this._segment2._handleIn.isZero();
-	},
-
-	isLinear: function() {
-		return Segment.isLinear(this._segment1, this._segment2);
-	},
-
-	isCollinear: function(curve) {
-		return Ssegment.isCollinear(this._segment1, this._segment2,
-				curve._segment1, curve._segment2);
-	},
-
-	isOrthogonalArc: function() {
-		return Segment.isOrthogonalArc(this._segment1, this._segment2);
-	},
-
-	getIntersections: function(curve) {
-		return Curve.filterIntersections(Curve.getIntersections(
-				this.getValues(), curve.getValues(), this, curve, []));
-	},
-
-	_getParameter: function(offset, isParameter) {
-		return isParameter
-				? offset
-				: offset && offset.curve === this
-					? offset.parameter
-					: offset === undefined && isParameter === undefined
-						? 0.5
-						: this.getParameterAt(offset, 0);
-	},
-
-	divide: function(offset, isParameter, ignoreLinear) {
-		var parameter = this._getParameter(offset, isParameter),
-			tolerance = 0.000001,
+	divideAt: function(location) {
+		return this.divideAtTime(location && location.curve === this
+				? location.time : this.getTimeAt(location));
+	},
+
+	divideAtTime: function(time, _setHandles) {
+		var tMin = 1e-8,
+			tMax = 1 - tMin,
 			res = null;
-		if (parameter > tolerance && parameter < 1 - tolerance) {
-			var parts = Curve.subdivide(this.getValues(), parameter),
-				isLinear = ignoreLinear ? false : this.isLinear(),
+		if (time >= tMin && time <= tMax) {
+			var parts = Curve.subdivide(this.getValues(), time),
 				left = parts[0],
-				right = parts[1];
-
-			if (!isLinear) {
-				this._segment1._handleOut.set(left[2] - left[0],
-						left[3] - left[1]);
-				this._segment2._handleIn.set(right[4] - right[6],
-						right[5] - right[7]);
-			}
-
+				right = parts[1],
+				setHandles = _setHandles || this.hasHandles(),
+				seg1 = this._segment1,
+				seg2 = this._segment2,
+				path = this._path;
+			if (setHandles) {
+				seg1._handleOut._set(left[2] - left[0], left[3] - left[1]);
+				seg2._handleIn._set(right[4] - right[6],right[5] - right[7]);
+			}
 			var x = left[6], y = left[7],
 				segment = new Segment(new Point(x, y),
-						!isLinear && new Point(left[4] - x, left[5] - y),
-						!isLinear && new Point(right[2] - x, right[3] - y));
-
-			if (this._path) {
-				if (this._segment1._index > 0 && this._segment2._index === 0) {
-					this._path.add(segment);
-				} else {
-					this._path.insert(this._segment2._index, segment);
-				}
-				res = this;
-			} else {
-				var end = this._segment2;
+						setHandles && new Point(left[4] - x, left[5] - y),
+						setHandles && new Point(right[2] - x, right[3] - y));
+			if (path) {
+				path.insert(seg1._index + 1, segment);
+				res = this.getNext();
+			} else {
 				this._segment2 = segment;
-				res = new Curve(segment, end);
+				this._changed();
+				res = new Curve(segment, seg2);
 			}
 		}
 		return res;
 	},
 
-	split: function(offset, isParameter) {
-		return this._path
-			? this._path.split(this._segment1._index,
-					this._getParameter(offset, isParameter))
-			: null;
-	},
-
-	reverse: function() {
-		return new Curve(this._segment2.reverse(), this._segment1.reverse());
-	},
-
-	remove: function() {
-		var removed = false;
-		if (this._path) {
-			var segment2 = this._segment2,
-				handleOut = segment2._handleOut;
-			removed = segment2.remove();
-			if (removed)
-				this._segment1._handleOut.set(handleOut.x, handleOut.y);
-		}
-		return removed;
-	},
-
-	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(', ') + ' }';
+	splitAt: function(location) {
+		var path = this._path;
+		return path ? path.splitAt(location) : null;
+	},
+
+	splitAtTime: function(time) {
+		return this.splitAt(this.getLocationAtTime(time));
+	},
+
+	divide: function(offset, isTime) {
+		return this.divideAtTime(offset === undefined ? 0.5 : isTime ? offset
+				: this.getTimeAt(offset));
+	},
+
+	split: function(offset, isTime) {
+		return this.splitAtTime(offset === undefined ? 0.5 : isTime ? offset
+				: this.getTimeAt(offset));
+	},
+
+	reversed: function() {
+		return new Curve(this._segment2.reversed(), this._segment1.reversed());
+	},
+
+	clearHandles: function() {
+		this._segment1._handleOut._set(0, 0);
+		this._segment2._handleIn._set(0, 0);
 	},
 
 statics: {
-	getValues: function(segment1, segment2, matrix) {
+	getValues: function(segment1, segment2, matrix, straight) {
 		var p1 = segment1._point,
 			h1 = segment1._handleOut,
 			h2 = segment2._handleIn,
 			p2 = segment2._point,
-			values = [
-				p1._x, p1._y,
-				p1._x + h1._x, p1._y + h1._y,
-				p2._x + h2._x, p2._y + h2._y,
-				p2._x, p2._y
-			];
+			x1 = p1.x, y1 = p1.y,
+			x2 = p2.x, y2 = p2.y,
+			values = straight
+				? [ x1, y1, x1, y1, x2, y2, x2, y2 ]
+				: [
+					x1, y1,
+					x1 + h1._x, y1 + h1._y,
+					x2 + h2._x, y2 + h2._y,
+					x2, y2
+				];
 		if (matrix)
 			matrix._transformCoordinates(values, values, 4);
 		return values;
 	},
 
 	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];
+		var x0 = v[0], y0 = v[1],
+			x1 = v[2], y1 = v[3],
+			x2 = v[4], y2 = v[5],
+			x3 = v[6], y3 = 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;
+			x4 = u * x0 + t * x1, y4 = u * y0 + t * y1,
+			x5 = u * x1 + t * x2, y5 = u * y1 + t * y2,
+			x6 = u * x2 + t * x3, y6 = u * y2 + t * y3,
+			x7 = u * x4 + t * x5, y7 = u * y4 + t * y5,
+			x8 = u * x5 + t * x6, y8 = u * y5 + t * y6,
+			x9 = u * x7 + t * x8, y9 = u * y7 + t * y8;
 		return [
-			[p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y],
-			[p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y]
+			[x0, y0, x4, y4, x7, y7, x9, y9],
+			[x9, y9, x8, y8, x6, y6, x3, y3]
 		];
 	},
 
+	getMonoCurves: function(v, dir) {
+		var curves = [],
+			io = dir ? 0 : 1,
+			o0 = v[io + 0],
+			o1 = v[io + 2],
+			o2 = v[io + 4],
+			o3 = v[io + 6];
+		if ((o0 >= o1) === (o1 >= o2) && (o1 >= o2) === (o2 >= o3)
+				|| Curve.isStraight(v)) {
+			curves.push(v);
+		} else {
+			var a = 3 * (o1 - o2) - o0 + o3,
+				b = 2 * (o0 + o2) - 4 * o1,
+				c = o1 - o0,
+				tMin = 1e-8,
+				tMax = 1 - tMin,
+				roots = [],
+				n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax);
+			if (!n) {
+				curves.push(v);
+			} else {
+				roots.sort();
+				var t = roots[0],
+					parts = Curve.subdivide(v, t);
+				curves.push(parts[0]);
+				if (n > 1) {
+					t = (roots[1] - t) / (1 - t);
+					parts = Curve.subdivide(parts[1], t);
+					curves.push(parts[0]);
+				}
+				curves.push(parts[1]);
+			}
+		}
+		return curves;
+	},
+
 	solveCubic: function (v, coord, val, roots, min, max) {
-		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, min, max);
-	},
-
-	getParameterOf: function(v, x, y) {
-		var tolerance = 0.000001;
-		if (Math.abs(v[0] - x) < tolerance && Math.abs(v[1] - y) < tolerance)
-			return 0;
-		if (Math.abs(v[6] - x) < tolerance && Math.abs(v[7] - y) < tolerance)
-			return 1;
-		var txs = [],
-			tys = [],
-			sx = Curve.solveCubic(v, 0, x, txs, 0, 1),
-			sy = Curve.solveCubic(v, 1, y, tys, 0, 1),
-			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) < tolerance)
-							return (tx + ty) * 0.5;
-					}
-				}
-				if (sx === -1)
-					break;
-			}
-		}
-		return null;
+		var v0 = v[coord],
+			v1 = v[coord + 2],
+			v2 = v[coord + 4],
+			v3 = v[coord + 6],
+			res = 0;
+		if (  !(v0 < val && v3 < val && v1 < val && v2 < val ||
+				v0 > val && v3 > val && v1 > val && v2 > val)) {
+			var c = 3 * (v1 - v0),
+				b = 3 * (v2 - v1) - c,
+				a = v3 - v0 - c - b;
+			res = Numerical.solveCubic(a, b, c, v0 - val, roots, min, max);
+		}
+		return res;
+	},
+
+	getTimeOf: function(v, point) {
+		var p0 = new Point(v[0], v[1]),
+			p3 = new Point(v[6], v[7]),
+			epsilon = 1e-12,
+			geomEpsilon = 1e-7,
+			t = point.isClose(p0, epsilon) ? 0
+			  : point.isClose(p3, epsilon) ? 1
+			  : null;
+		if (t === null) {
+			var coords = [point.x, point.y],
+				roots = [];
+			for (var c = 0; c < 2; c++) {
+				var count = Curve.solveCubic(v, c, coords[c], roots, 0, 1);
+				for (var i = 0; i < count; i++) {
+					var u = roots[i];
+					if (point.isClose(Curve.getPoint(v, u), geomEpsilon))
+						return u;
+				}
+			}
+		}
+		return point.isClose(p0, geomEpsilon) ? 0
+			 : point.isClose(p3, geomEpsilon) ? 1
+			 : null;
+	},
+
+	getNearestTime: function(v, point) {
+		if (Curve.isStraight(v)) {
+			var x0 = v[0], y0 = v[1],
+				x3 = v[6], y3 = v[7],
+				vx = x3 - x0, vy = y3 - y0,
+				det = vx * vx + vy * vy;
+			if (det === 0)
+				return 0;
+			var u = ((point.x - x0) * vx + (point.y - y0) * vy) / det;
+			return u < 1e-12 ? 0
+				 : u > 0.999999999999 ? 1
+				 : Curve.getTimeOf(v,
+					new Point(x0 + u * vx, y0 + u * vy));
+		}
+
+		var count = 100,
+			minDist = Infinity,
+			minT = 0;
+
+		function refine(t) {
+			if (t >= 0 && t <= 1) {
+				var dist = point.getDistance(Curve.getPoint(v, t), true);
+				if (dist < minDist) {
+					minDist = dist;
+					minT = t;
+					return true;
+				}
+			}
+		}
+
+		for (var i = 0; i <= count; i++)
+			refine(i / count);
+
+		var step = 1 / (count * 2);
+		while (step > 1e-8) {
+			if (!refine(minT - step) && !refine(minT + step))
+				step /= 2;
+		}
+		return minT;
 	},
 
 	getPart: function(v, from, to) {
+		var flip = from > to;
+		if (flip) {
+			var tmp = from;
+			from = to;
+			to = tmp;
+		}
 		if (from > 0)
 			v = Curve.subdivide(v, from)[1];
 		if (to < 1)
 			v = Curve.subdivide(v, (to - from) / (1 - from))[0];
-		return v;
-	},
-
-	hasHandles: function(v) {
-		var isZero = Numerical.isZero;
-		return !(isZero(v[0] - v[2]) && isZero(v[1] - v[3])
-				&& isZero(v[4] - v[6]) && isZero(v[5] - v[7]));
-	},
-
-	isLinear: function(v) {
-		var p1x = v[0], p1y = v[1],
-			p2x = v[6], p2y = v[7],
-			l = new Point(p2x - p1x, p2y - p1y);
-		return l.isCollinear(new Point(v[2] - p1x, v[3] - p1y))
-				&& l.isCollinear(new Point(v[4] - p2x, v[5] - p2y));
-	},
-
-	isFlatEnough: function(v, tolerance) {
-		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 flip
+				? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]]
+				: v;
+	},
+
+	isFlatEnough: function(v, flatness) {
+		var x0 = v[0], y0 = v[1],
+			x1 = v[2], y1 = v[3],
+			x2 = v[4], y2 = v[5],
+			x3 = v[6], y3 = v[7],
+			ux = 3 * x1 - 2 * x0 - x3,
+			uy = 3 * y1 - 2 * y0 - y3,
+			vx = 3 * x2 - 2 * x3 - x0,
+			vy = 3 * y2 - 2 * y3 - y0;
 		return Math.max(ux * ux, vx * vx) + Math.max(uy * uy, vy * vy)
-				< 10 * tolerance * tolerance;
+				<= 16 * flatness * flatness;
 	},
 
 	getArea: 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];
-		return (  3.0 * c1y * p1x - 1.5 * c1y * c2x
-				- 1.5 * c1y * p2x - 3.0 * p1y * c1x
-				- 1.5 * p1y * c2x - 0.5 * p1y * p2x
-				+ 1.5 * c2y * p1x + 1.5 * c2y * c1x
-				- 3.0 * c2y * p2x + 0.5 * p2y * p1x
-				+ 1.5 * p2y * c1x + 3.0 * p2y * c2x) / 10;
-	},
-
-	getEdgeSum: function(v) {
-		return	  (v[0] - v[2]) * (v[3] + v[1])
-				+ (v[2] - v[4]) * (v[5] + v[3])
-				+ (v[4] - v[6]) * (v[7] + v[5]);
+		var x0 = v[0], y0 = v[1],
+			x1 = v[2], y1 = v[3],
+			x2 = v[4], y2 = v[5],
+			x3 = v[6], y3 = v[7];
+		return 3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
+				+ y1 * (x0 - x2) - x1 * (y0 - y2)
+				+ y3 * (x2 + x0 / 3) - x3 * (y2 + y0 / 3)) / 20;
 	},
 
 	getBounds: function(v) {
@@ -5915,62 +6586,156 @@
 			if (right > max[coord])
 				max[coord] = right;
 		}
-		var a = 3 * (v1 - v2) - v0 + v3,
-			b = 2 * (v0 + v2) - 4 * v1,
-			c = v1 - v0,
-			count = Numerical.solveQuadratic(a, b, c, roots),
-			tMin = 0.000001,
-			tMax = 1 - tMin;
-		add(v3, 0);
-		for (var i = 0; i < count; i++) {
-			var t = roots[i],
-				u = 1 - t;
-			if (tMin < t && t < tMax)
-				add(u * u * u * v0
-					+ 3 * u * u * t * v1
-					+ 3 * u * t * t * v2
-					+ t * t * t * v3,
-					padding);
+
+		padding /= 2;
+		var minPad = min[coord] - padding,
+			maxPad = max[coord] + padding;
+		if (    v0 < minPad || v1 < minPad || v2 < minPad || v3 < minPad ||
+				v0 > maxPad || v1 > maxPad || v2 > maxPad || v3 > maxPad) {
+			if (v1 < v0 != v1 < v3 && v2 < v0 != v2 < v3) {
+				add(v0, padding);
+				add(v3, padding);
+			} else {
+				var a = 3 * (v1 - v2) - v0 + v3,
+					b = 2 * (v0 + v2) - 4 * v1,
+					c = v1 - v0,
+					count = Numerical.solveQuadratic(a, b, c, roots),
+					tMin = 1e-8,
+					tMax = 1 - tMin;
+				add(v3, 0);
+				for (var i = 0; i < count; i++) {
+					var t = roots[i],
+						u = 1 - t;
+					if (tMin <= t && t <= tMax)
+						add(u * u * u * v0
+							+ 3 * u * u * t * v1
+							+ 3 * u * t * t * v2
+							+ t * t * t * v3,
+							padding);
+				}
+			}
 		}
 	}
 }}, Base.each(
-	['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
+	['getBounds', 'getStrokeBounds', 'getHandleBounds'],
 	function(name) {
 		this[name] = function() {
 			if (!this._bounds)
 				this._bounds = {};
 			var bounds = this._bounds[name];
 			if (!bounds) {
-				bounds = this._bounds[name] = Path[name]([this._segment1,
-						this._segment2], false, this._path.getStyle());
+				bounds = this._bounds[name] = Path[name](
+						[this._segment1, this._segment2], false, this._path);
 			}
 			return bounds.clone();
 		};
 	},
 {
 
+}), Base.each({
+	isStraight: function(p1, h1, h2, p2) {
+		if (h1.isZero() && h2.isZero()) {
+			return true;
+		} else {
+			var v = p2.subtract(p1);
+			if (v.isZero()) {
+				return false;
+			} else if (v.isCollinear(h1) && v.isCollinear(h2)) {
+				var l = new Line(p1, p2),
+					epsilon = 1e-7;
+				if (l.getDistance(p1.add(h1)) < epsilon &&
+					l.getDistance(p2.add(h2)) < epsilon) {
+					var div = v.dot(v),
+						s1 = v.dot(h1) / div,
+						s2 = v.dot(h2) / div;
+					return s1 >= 0 && s1 <= 1 && s2 <= 0 && s2 >= -1;
+				}
+			}
+		}
+		return false;
+	},
+
+	isLinear: function(p1, h1, h2, p2) {
+		var third = p2.subtract(p1).divide(3);
+		return h1.equals(third) && h2.negate().equals(third);
+	}
+}, function(test, name) {
+	this[name] = function(epsilon) {
+		var seg1 = this._segment1,
+			seg2 = this._segment2;
+		return test(seg1._point, seg1._handleOut, seg2._handleIn, seg2._point,
+				epsilon);
+	};
+
+	this.statics[name] = function(v, epsilon) {
+		var x0 = v[0], y0 = v[1],
+			x3 = v[6], y3 = v[7];
+		return test(
+				new Point(x0, y0),
+				new Point(v[2] - x0, v[3] - y0),
+				new Point(v[4] - x3, v[5] - y3),
+				new Point(x3, y3), epsilon);
+	};
+}, {
+	statics: {},
+
+	hasHandles: function() {
+		return !this._segment1._handleOut.isZero()
+				|| !this._segment2._handleIn.isZero();
+	},
+
+	hasLength: function(epsilon) {
+		return (!this.getPoint1().equals(this.getPoint2()) || this.hasHandles())
+				&& this.getLength() > (epsilon || 0);
+	},
+
+	isCollinear: function(curve) {
+		return curve && this.isStraight() && curve.isStraight()
+				&& this.getLine().isCollinear(curve.getLine());
+	},
+
+	isHorizontal: function() {
+		return this.isStraight() && Math.abs(this.getTangentAtTime(0.5).y)
+				< 1e-8;
+	},
+
+	isVertical: function() {
+		return this.isStraight() && Math.abs(this.getTangentAtTime(0.5).x)
+				< 1e-8;
+	}
 }), {
 	beans: false,
 
-	getParameterAt: function(offset, start) {
-		return Curve.getParameterAt(this.getValues(), offset, start);
-	},
-
-	getParameterOf: function() {
-		var point = Point.read(arguments);
-		return Curve.getParameterOf(this.getValues(), point.x, point.y);
-	},
-
-	getLocationAt: function(offset, isParameter) {
-		var t = isParameter ? offset : this.getParameterAt(offset);
+	getLocationAt: function(offset, _isTime) {
+		return this.getLocationAtTime(
+				_isTime ? offset : this.getTimeAt(offset));
+	},
+
+	getLocationAtTime: function(t) {
 		return t != null && t >= 0 && t <= 1
 				? new CurveLocation(this, t)
 				: null;
 	},
 
+	getTimeAt: function(offset, start) {
+		return Curve.getTimeAt(this.getValues(), offset, start);
+	},
+
+	getParameterAt: '#getTimeAt',
+
+	getTimesWithTangent: function () {
+		var tangent = Point.read(arguments);
+		return tangent.isZero()
+				? []
+				: Curve.getTimesWithTangent(this.getValues(), tangent);
+	},
+
+	getOffsetAtTime: function(t) {
+		return this.getPartLength(0, t);
+	},
+
 	getLocationOf: function() {
-		return this.getLocationAt(this.getParameterOf(Point.read(arguments)),
-				true);
+		return this.getLocationAtTime(this.getTimeOf(Point.read(arguments)));
 	},
 
 	getOffsetOf: function() {
@@ -5978,39 +6743,23 @@
 		return loc ? loc.getOffset() : null;
 	},
 
+	getTimeOf: function() {
+		return Curve.getTimeOf(this.getValues(), Point.read(arguments));
+	},
+
+	getParameterOf: '#getTimeOf',
+
 	getNearestLocation: function() {
 		var point = Point.read(arguments),
 			values = this.getValues(),
-			count = 100,
-			minDist = Infinity,
-			minT = 0;
-
-		function refine(t) {
-			if (t >= 0 && t <= 1) {
-				var dist = point.getDistance(Curve.getPoint(values, t), true);
-				if (dist < minDist) {
-					minDist = dist;
-					minT = t;
-					return true;
-				}
-			}
-		}
-
-		for (var i = 0; i <= count; i++)
-			refine(i / count);
-
-		var step = 1 / (count * 2);
-		while (step > 0.000001) {
-			if (!refine(minT - step) && !refine(minT + step))
-				step /= 2;
-		}
-		var pt = Curve.getPoint(values, minT);
-		return new CurveLocation(this, minT, pt, null, null, null,
-				point.getDistance(pt));
+			t = Curve.getNearestTime(values, point),
+			pt = Curve.getPoint(values, t);
+		return new CurveLocation(this, t, pt, null, point.getDistance(pt));
 	},
 
 	getNearestPoint: function() {
-		return this.getNearestLocation.apply(this, arguments).getPoint();
+		var loc = this.getNearestLocation.apply(this, arguments);
+		return loc ? loc.getPoint() : loc;
 	}
 
 },
@@ -6018,33 +6767,38 @@
 	var methods = ['getPoint', 'getTangent', 'getNormal', 'getWeightedTangent',
 		'getWeightedNormal', 'getCurvature'];
 	return Base.each(methods,
-	function(name) {
-		this[name + 'At'] = function(offset, isParameter) {
-			var values = this.getValues();
-			return Curve[name](values, isParameter ? offset
-					: Curve.getParameterAt(values, offset, 0));
-		};
-	}, {
-		statics: {
-			evaluateMethods: methods
-		}
-	})
+		function(name) {
+			this[name + 'At'] = function(location, _isTime) {
+				var values = this.getValues();
+				return Curve[name](values, _isTime ? location
+						: Curve.getTimeAt(values, location));
+			};
+
+			this[name + 'AtTime'] = function(time) {
+				return Curve[name](this.getValues(), time);
+			};
+		}, {
+			statics: {
+				_evaluateMethods: methods
+			}
+		}
+	);
 },
 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);
+		var x0 = v[0], y0 = v[1],
+			x1 = v[2], y1 = v[3],
+			x2 = v[4], y2 = v[5],
+			x3 = v[6], y3 = v[7],
+
+			ax = 9 * (x1 - x2) + 3 * (x3 - x0),
+			bx = 6 * (x0 + x2) - 12 * x1,
+			cx = 3 * (x1 - x0),
+
+			ay = 9 * (y1 - y2) + 3 * (y3 - y0),
+			by = 6 * (y0 + y2) - 12 * y1,
+			cy = 3 * (y1 - y0);
 
 		return function(t) {
 			var dx = (ax * t + bx) * t + cx,
@@ -6060,97 +6814,161 @@
 	function evaluate(v, t, type, normalized) {
 		if (t == null || t < 0 || t > 1)
 			return null;
-		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],
-			tolerance = 0.000001,
+		var x0 = v[0], y0 = v[1],
+			x1 = v[2], y1 = v[3],
+			x2 = v[4], y2 = v[5],
+			x3 = v[6], y3 = v[7],
+			isZero = Numerical.isZero;
+		if (isZero(x1 - x0) && isZero(y1 - y0)) {
+			x1 = x0;
+			y1 = y0;
+		}
+		if (isZero(x2 - x3) && isZero(y2 - y3)) {
+			x2 = x3;
+			y2 = y3;
+		}
+		var cx = 3 * (x1 - x0),
+			bx = 3 * (x2 - x1) - cx,
+			ax = x3 - x0 - cx - bx,
+			cy = 3 * (y1 - y0),
+			by = 3 * (y2 - y1) - cy,
+			ay = y3 - y0 - cy - by,
 			x, y;
-
-		if (type === 0 && (t < tolerance || t > 1 - tolerance)) {
-			var isZero = t < tolerance;
-			x = isZero ? p1x : p2x;
-			y = isZero ? p1y : p2y;
+		if (type === 0) {
+			x = t === 0 ? x0 : t === 1 ? x3
+					: ((ax * t + bx) * t + cx) * t + x0;
+			y = t === 0 ? y0 : t === 1 ? y3
+					: ((ay * t + by) * t + cy) * t + y0;
 		} else {
-			var cx = 3 * (c1x - p1x),
-				bx = 3 * (c2x - c1x) - cx,
-				ax = p2x - p1x - cx - bx,
-
-				cy = 3 * (c1y - p1y),
-				by = 3 * (c2y - c1y) - cy,
-				ay = p2y - p1y - cy - by;
-			if (type === 0) {
-				x = ((ax * t + bx) * t + cx) * t + p1x;
-				y = ((ay * t + by) * t + cy) * t + p1y;
-			} else {
-				if (t < tolerance) {
-					x = cx;
-					y = cy;
-				} else if (t > 1 - tolerance) {
-					x = 3 * (p2x - c2x);
-					y = 3 * (p2y - c2y);
-				} else {
-					x = (3 * ax * t + 2 * bx) * t + cx;
-					y = (3 * ay * t + 2 * by) * t + cy;
-				}
-				if (normalized) {
-					if (x === 0 && y === 0
-							&& (t < tolerance || t > 1 - tolerance)) {
-						x = c2x - c1x;
-						y = c2y - c1y;
-					}
-					var len = Math.sqrt(x * x + y * y);
+			var tMin = 1e-8,
+				tMax = 1 - tMin;
+			if (t < tMin) {
+				x = cx;
+				y = cy;
+			} else if (t > tMax) {
+				x = 3 * (x3 - x2);
+				y = 3 * (y3 - y2);
+			} else {
+				x = (3 * ax * t + 2 * bx) * t + cx;
+				y = (3 * ay * t + 2 * by) * t + cy;
+			}
+			if (normalized) {
+				if (x === 0 && y === 0 && (t < tMin || t > tMax)) {
+					x = x2 - x1;
+					y = y2 - y1;
+				}
+				var len = Math.sqrt(x * x + y * y);
+				if (len) {
 					x /= len;
 					y /= len;
 				}
-				if (type === 3) {
-					var x2 = 6 * ax * t + 2 * bx,
-						y2 = 6 * ay * t + 2 * by,
-						d = Math.pow(x * x + y * y, 3 / 2);
-					x = d !== 0 ? (x * y2 - y * x2) / d : 0;
-					y = 0;
-				}
+			}
+			if (type === 3) {
+				var x2 = 6 * ax * t + 2 * bx,
+					y2 = 6 * ay * t + 2 * by,
+					d = Math.pow(x * x + y * y, 3 / 2);
+				x = d !== 0 ? (x * y2 - y * x2) / d : 0;
+				y = 0;
 			}
 		}
 		return type === 2 ? new Point(y, -x) : new Point(x, y);
 	}
 
-	return {
-		statics: true,
-
-		getLength: function(v, a, b) {
+	return { statics: {
+
+		classify: function(v) {
+
+			var x0 = v[0], y0 = v[1],
+				x1 = v[2], y1 = v[3],
+				x2 = v[4], y2 = v[5],
+				x3 = v[6], y3 = v[7],
+				a1 = x0 * (y3 - y2) + y0 * (x2 - x3) + x3 * y2 - y3 * x2,
+				a2 = x1 * (y0 - y3) + y1 * (x3 - x0) + x0 * y3 - y0 * x3,
+				a3 = x2 * (y1 - y0) + y2 * (x0 - x1) + x1 * y0 - y1 * x0,
+				d3 = 3 * a3,
+				d2 = d3 - a2,
+				d1 = d2 - a2 + a1,
+				l = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3),
+				s = l !== 0 ? 1 / l : 0,
+				isZero = Numerical.isZero,
+				serpentine = 'serpentine';
+			d1 *= s;
+			d2 *= s;
+			d3 *= s;
+
+			function type(type, t1, t2) {
+				var hasRoots = t1 !== undefined,
+					t1Ok = hasRoots && t1 > 0 && t1 < 1,
+					t2Ok = hasRoots && t2 > 0 && t2 < 1;
+				if (hasRoots && (!(t1Ok || t2Ok)
+						|| type === 'loop' && !(t1Ok && t2Ok))) {
+					type = 'arch';
+					t1Ok = t2Ok = false;
+				}
+				return {
+					type: type,
+					roots: t1Ok || t2Ok
+							? t1Ok && t2Ok
+								? t1 < t2 ? [t1, t2] : [t2, t1]
+								: [t1Ok ? t1 : t2]
+							: null
+				};
+			}
+
+			if (isZero(d1)) {
+				return isZero(d2)
+						? type(isZero(d3) ? 'line' : 'quadratic')
+						: type(serpentine, d3 / (3 * d2));
+			}
+			var d = 3 * d2 * d2 - 4 * d1 * d3;
+			if (isZero(d)) {
+				return type('cusp', d2 / (2 * d1));
+			}
+			var f1 = d > 0 ? Math.sqrt(d / 3) : Math.sqrt(-d),
+				f2 = 2 * d1;
+			return type(d > 0 ? serpentine : 'loop',
+					(d2 + f1) / f2,
+					(d2 - f1) / f2);
+		},
+
+		getLength: function(v, a, b, ds) {
 			if (a === undefined)
 				a = 0;
 			if (b === undefined)
 				b = 1;
-			var isZero = Numerical.isZero;
-			if (a === 0 && b === 1
-					&& isZero(v[0] - v[2]) && isZero(v[1] - v[3])
-					&& isZero(v[6] - v[4]) && isZero(v[7] - v[5])) {
-				var dx = v[6] - v[0],
-					dy = v[7] - v[1];
+			if (Curve.isStraight(v)) {
+				var c = v;
+				if (b < 1) {
+					c = Curve.subdivide(c, b)[0];
+					a /= b;
+				}
+				if (a > 0) {
+					c = Curve.subdivide(c, a)[1];
+				}
+				var dx = c[6] - c[0],
+					dy = c[7] - c[1];
 				return Math.sqrt(dx * dx + dy * dy);
 			}
-			var ds = getLengthIntegrand(v);
-			return Numerical.integrate(ds, a, b, getIterations(a, b));
-		},
-
-		getParameterAt: function(v, offset, start) {
+			return Numerical.integrate(ds || getLengthIntegrand(v), a, b,
+					getIterations(a, b));
+		},
+
+		getTimeAt: function(v, offset, start) {
 			if (start === undefined)
-				start = offset < 0 ? 1 : 0
+				start = offset < 0 ? 1 : 0;
 			if (offset === 0)
 				return start;
-			var tolerance = 0.000001,
-				abs = Math.abs,
+			var abs = Math.abs,
+				epsilon = 1e-12,
 				forward = offset > 0,
 				a = forward ? start : 0,
 				b = forward ? 1 : start,
 				ds = getLengthIntegrand(v),
-				rangeLength = Numerical.integrate(ds, a, b,
-						getIterations(a, b));
-			if (abs(offset - rangeLength) < tolerance) {
+				rangeLength = Curve.getLength(v, a, b, ds),
+				diff = abs(offset) - rangeLength;
+			if (abs(diff) < epsilon) {
 				return forward ? b : a;
-			} else if (abs(offset) > rangeLength) {
+			} else if (diff > epsilon) {
 				return null;
 			}
 			var guess = offset / rangeLength,
@@ -6161,8 +6979,8 @@
 				start = t;
 				return length - offset;
 			}
-			return Numerical.findRoot(f, ds, start + guess, a, b, 16,
-					tolerance);
+			return Numerical.findRoot(f, ds, start + guess, a, b, 32,
+					1e-12);
 		},
 
 		getPoint: function(v, t) {
@@ -6187,25 +7005,63 @@
 
 		getCurvature: function(v, t) {
 			return evaluate(v, t, 3, false).x;
-		}
-	};
-}, new function() {
-	function addLocation(locations, include, curve1, t1, point1, curve2, t2,
-			point2) {
-		var loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2);
-		if (!include || include(loc))
-			locations.push(loc);
-	}
-
-	function addCurveIntersections(v1, v2, curve1, curve2, locations, include,
-			tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) {
-		if (recursion > 32)
-			return;
-		var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7],
-			tolerance = 0.000001,
+		},
+
+		getPeaks: function(v) {
+			var x0 = v[0], y0 = v[1],
+				x1 = v[2], y1 = v[3],
+				x2 = v[4], y2 = v[5],
+				x3 = v[6], y3 = v[7],
+				ax =     -x0 + 3 * x1 - 3 * x2 + x3,
+				bx =  3 * x0 - 6 * x1 + 3 * x2,
+				cx = -3 * x0 + 3 * x1,
+				ay =     -y0 + 3 * y1 - 3 * y2 + y3,
+				by =  3 * y0 - 6 * y1 + 3 * y2,
+				cy = -3 * y0 + 3 * y1,
+				tMin = 1e-8,
+				tMax = 1 - tMin,
+				roots = [];
+			Numerical.solveCubic(
+					9 * (ax * ax + ay * ay),
+					9 * (ax * bx + by * ay),
+					2 * (bx * bx + by * by) + 3 * (cx * ax + cy * ay),
+					(cx * bx + by * cy),
+					roots, tMin, tMax);
+			return roots.sort();
+		}
+	}};
+},
+new function() {
+
+	function addLocation(locations, include, c1, t1, c2, t2, overlap) {
+		var excludeStart = !overlap && c1.getPrevious() === c2,
+			excludeEnd = !overlap && c1 !== c2 && c1.getNext() === c2,
+			tMin = 1e-8,
+			tMax = 1 - tMin;
+		if (t1 !== null && t1 >= (excludeStart ? tMin : 0) &&
+			t1 <= (excludeEnd ? tMax : 1)) {
+			if (t2 !== null && t2 >= (excludeEnd ? tMin : 0) &&
+				t2 <= (excludeStart ? tMax : 1)) {
+				var loc1 = new CurveLocation(c1, t1, null, overlap),
+					loc2 = new CurveLocation(c2, t2, null, overlap);
+				loc1._intersection = loc2;
+				loc2._intersection = loc1;
+				if (!include || include(loc1)) {
+					CurveLocation.insert(locations, loc1, true);
+				}
+			}
+		}
+	}
+
+	function addCurveIntersections(v1, v2, c1, c2, locations, include, flip,
+			recursion, calls, tMin, tMax, uMin, uMax) {
+		if (++calls >= 4096 || ++recursion >= 40)
+			return calls;
+		var fatLineEpsilon = 1e-9,
+			q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7],
 			getSignedDistance = Line.getSignedDistance,
-			d1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]) || 0,
-			d2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]) || 0,
+			d1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]),
+			d2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]),
 			factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9,
 			dMin = factor * Math.min(0, d1, d2),
 			dMax = factor * Math.max(0, d1, d2),
@@ -6213,62 +7069,60 @@
 			dp1 = getSignedDistance(q0x, q0y, q3x, q3y, v1[2], v1[3]),
 			dp2 = getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]),
 			dp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]),
-			tMinNew, tMaxNew, tDiff;
-		if (q0x === q3x && uMax - uMin < tolerance && recursion > 3) {
-			tMaxNew = tMinNew = (tMax + tMin) / 2;
-			tDiff = 0;
+			hull = getConvexHull(dp0, dp1, dp2, dp3),
+			top = hull[0],
+			bottom = hull[1],
+			tMinClip,
+			tMaxClip;
+		if (d1 === 0 && d2 === 0
+				&& dp0 === 0 && dp1 === 0 && dp2 === 0 && dp3 === 0
+			|| (tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null
+			|| (tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(),
+				dMin, dMax)) == null)
+			return calls;
+		var tMinNew = tMin + (tMax - tMin) * tMinClip,
+			tMaxNew = tMin + (tMax - tMin) * tMaxClip;
+		if (Math.max(uMax - uMin, tMaxNew - tMinNew) < fatLineEpsilon) {
+			var t = (tMinNew + tMaxNew) / 2,
+				u = (uMin + uMax) / 2;
+			addLocation(locations, include,
+					flip ? c2 : c1, flip ? u : t,
+					flip ? c1 : c2, flip ? t : u);
 		} else {
-			var hull = getConvexHull(dp0, dp1, dp2, dp3),
-				top = hull[0],
-				bottom = hull[1],
-				tMinClip, tMaxClip;
-			tMinClip = clipConvexHull(top, bottom, dMin, dMax);
-			top.reverse();
-			bottom.reverse();
-			tMaxClip = clipConvexHull(top, bottom, dMin, dMax);
-			if (tMinClip == null || tMaxClip == null)
-				return;
 			v1 = Curve.getPart(v1, tMinClip, tMaxClip);
-			tDiff = tMaxClip - tMinClip;
-			tMinNew = tMax * tMinClip + tMin * (1 - tMinClip);
-			tMaxNew = tMax * tMaxClip + tMin * (1 - tMaxClip);
-		}
-		if (oldTDiff > 0.5 && tDiff > 0.5) {
-			if (tMaxNew - tMinNew > uMax - uMin) {
-				var parts = Curve.subdivide(v1, 0.5),
-					t = tMinNew + (tMaxNew - tMinNew) / 2;
-				addCurveIntersections(
-					v2, parts[0], curve2, curve1, locations, include,
-					uMin, uMax, tMinNew, t, tDiff, !reverse, ++recursion);
-				addCurveIntersections(
-					v2, parts[1], curve2, curve1, locations, include,
-					uMin, uMax, t, tMaxNew, tDiff, !reverse, recursion);
-			} else {
-				var parts = Curve.subdivide(v2, 0.5),
-					t = uMin + (uMax - uMin) / 2;
-				addCurveIntersections(
-					parts[0], v1, curve2, curve1, locations, include,
-					uMin, t, tMinNew, tMaxNew, tDiff, !reverse, ++recursion);
-				addCurveIntersections(
-					parts[1], v1, curve2, curve1, locations, include,
-					t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
-			}
-		} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) {
-			var t1 = tMinNew + (tMaxNew - tMinNew) / 2,
-				t2 = uMin + (uMax - uMin) / 2;
-			if (reverse) {
-				addLocation(locations, include,
-						curve2, t2, Curve.getPoint(v2, t2),
-						curve1, t1, Curve.getPoint(v1, t1));
-			} else {
-				addLocation(locations, include,
-						curve1, t1, Curve.getPoint(v1, t1),
-						curve2, t2, Curve.getPoint(v2, t2));
-			}
-		} else if (tDiff > 0) {
-			addCurveIntersections(v2, v1, curve2, curve1, locations, include,
-					uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, ++recursion);
-		}
+			if (tMaxClip - tMinClip > 0.8) {
+				if (tMaxNew - tMinNew > uMax - uMin) {
+					var parts = Curve.subdivide(v1, 0.5),
+						t = (tMinNew + tMaxNew) / 2;
+					calls = addCurveIntersections(
+							v2, parts[0], c2, c1, locations, include, !flip,
+							recursion, calls, uMin, uMax, tMinNew, t);
+					calls = addCurveIntersections(
+							v2, parts[1], c2, c1, locations, include, !flip,
+							recursion, calls, uMin, uMax, t, tMaxNew);
+				} else {
+					var parts = Curve.subdivide(v2, 0.5),
+						u = (uMin + uMax) / 2;
+					calls = addCurveIntersections(
+							parts[0], v1, c2, c1, locations, include, !flip,
+							recursion, calls, uMin, u, tMinNew, tMaxNew);
+					calls = addCurveIntersections(
+							parts[1], v1, c2, c1, locations, include, !flip,
+							recursion, calls, u, uMax, tMinNew, tMaxNew);
+				}
+			} else {
+				if (uMax - uMin >= fatLineEpsilon) {
+					calls = addCurveIntersections(
+							v2, v1, c2, c1, locations, include, !flip,
+							recursion, calls, uMin, uMax, tMinNew, tMaxNew);
+				} else {
+					calls = addCurveIntersections(
+							v1, v2, c1, c2, locations, include, flip,
+							recursion, calls, tMinNew, tMaxNew, uMin, uMax);
+				}
+			}
+		}
+		return calls;
 	}
 
 	function getConvexHull(dq0, dq1, dq2, dq3) {
@@ -6276,32 +7130,21 @@
 			p1 = [ 1 / 3, dq1 ],
 			p2 = [ 2 / 3, dq2 ],
 			p3 = [ 1, dq3 ],
-			getSignedDistance = Line.getSignedDistance,
-			dist1 = getSignedDistance(0, dq0, 1, dq3, 1 / 3, dq1),
-			dist2 = getSignedDistance(0, dq0, 1, dq3, 2 / 3, dq2),
-			flip = false,
+			dist1 = dq1 - (2 * dq0 + dq3) / 3,
+			dist2 = dq2 - (dq0 + 2 * dq3) / 3,
 			hull;
 		if (dist1 * dist2 < 0) {
 			hull = [[p0, p1, p3], [p0, p2, p3]];
-			flip = dist1 < 0;
 		} else {
-			var pmax, cross = 0,
-				distZero = dist1 === 0 || dist2 === 0;
-			if (Math.abs(dist1) > Math.abs(dist2)) {
-				pmax = p1;
-				cross = (dq3 - dq2 - (dq3 - dq0) / 3)
-						* (2 * (dq3 - dq2) - dq3 + dq1) / 3;
-			} else {
-				pmax = p2;
-				cross = (dq1 - dq0 + (dq0 - dq3) / 3)
-						* (-2 * (dq0 - dq1) + dq0 - dq2) / 3;
-			}
-			hull = cross < 0 || distZero
-					? [[p0, pmax, p3], [p0, p3]]
-					: [[p0, p1, p2, p3], [p0, p3]];
-			flip = dist1 ? dist1 < 0 : dist2 < 0;
-		}
-		return flip ? hull.reverse() : hull;
+			var distRatio = dist1 / dist2;
+			hull = [
+				distRatio >= 2 ? [p0, p1, p3]
+				: distRatio <= 0.5 ? [p0, p2, p3]
+				: [p0, p1, p2, p3],
+				[p0, p3]
+			];
+		}
+		return (dist1 || dist2) < 0 ? hull.reverse() : hull;
 	}
 
 	function clipConvexHull(hullTop, hullBottom, dMin, dMax) {
@@ -6320,193 +7163,373 @@
 		for (var i = 1, l = part.length; i < l; i++) {
 			var qx = part[i][0],
 				qy = part[i][1];
-			if (top ? qy >= threshold : qy <= threshold)
-				return px + (threshold - py) * (qx - px) / (qy - py);
+			if (top ? qy >= threshold : qy <= threshold) {
+				return qy === threshold ? qx
+						: px + (threshold - py) * (qx - px) / (qy - py);
+			}
 			px = qx;
 			py = qy;
 		}
 		return null;
 	}
 
-	function addCurveLineIntersections(v1, v2, curve1, curve2, locations,
-			include) {
-		var flip = Curve.isLinear(v1),
-			vc = flip ? v2 : v1,
-			vl = flip ? v1 : v2,
-			lx1 = vl[0], ly1 = vl[1],
-			lx2 = vl[6], ly2 = vl[7],
-			ldx = lx2 - lx1,
-			ldy = ly2 - ly1,
-			angle = Math.atan2(-ldy, ldx),
+	function getCurveLineIntersections(v, px, py, vx, vy) {
+		var isZero = Numerical.isZero;
+		if (isZero(vx) && isZero(vy)) {
+			var t = Curve.getTimeOf(v, new Point(px, py));
+			return t === null ? [] : [t];
+		}
+		var angle = Math.atan2(-vy, vx),
 			sin = Math.sin(angle),
 			cos = Math.cos(angle),
-			rlx2 = ldx * cos - ldy * sin,
-			rvl = [0, 0, 0, 0, rlx2, 0, rlx2, 0],
-			rvc = [];
-		for(var i = 0; i < 8; i += 2) {
-			var x = vc[i] - lx1,
-				y = vc[i + 1] - ly1;
-			rvc.push(
+			rv = [],
+			roots = [];
+		for (var i = 0; i < 8; i += 2) {
+			var x = v[i] - px,
+				y = v[i + 1] - py;
+			rv.push(
 				x * cos - y * sin,
-				y * cos + x * sin);
-		}
-		var roots = [],
-			count = Curve.solveCubic(rvc, 1, 0, roots, 0, 1);
-		for (var i = 0; i < count; i++) {
-			var tc = roots[i],
-				x = Curve.getPoint(rvc, tc).x;
-			if (x >= 0 && x <= rlx2) {
-				var tl = Curve.getParameterOf(rvl, x, 0),
-					t1 = flip ? tl : tc,
-					t2 = flip ? tc : tl;
+				x * sin + y * cos);
+		}
+		Curve.solveCubic(rv, 1, 0, roots, 0, 1);
+		return roots;
+	}
+
+	function addCurveLineIntersections(v1, v2, c1, c2, locations, include,
+			flip) {
+		var x1 = v2[0], y1 = v2[1],
+			x2 = v2[6], y2 = v2[7],
+			roots = getCurveLineIntersections(v1, x1, y1, x2 - x1, y2 - y1);
+		for (var i = 0, l = roots.length; i < l; i++) {
+			var t1 = roots[i],
+				p1 = Curve.getPoint(v1, t1),
+				t2 = Curve.getTimeOf(v2, p1);
+			if (t2 !== null) {
 				addLocation(locations, include,
-						curve1, t1, Curve.getPoint(v1, t1),
-						curve2, t2, Curve.getPoint(v2, t2));
-			}
-		}
-	}
-
-	function addLineIntersection(v1, v2, curve1, curve2, locations, include) {
-		var point = Line.intersect(
+						flip ? c2 : c1, flip ? t2 : t1,
+						flip ? c1 : c2, flip ? t1 : t2);
+			}
+		}
+	}
+
+	function addLineIntersection(v1, v2, c1, c2, locations, include) {
+		var pt = Line.intersect(
 				v1[0], v1[1], v1[6], v1[7],
 				v2[0], v2[1], v2[6], v2[7]);
-		if (point) {
-			var x = point.x,
-				y = point.y;
+		if (pt) {
+			addLocation(locations, include,
+					c1, Curve.getTimeOf(v1, pt),
+					c2, Curve.getTimeOf(v2, pt));
+		}
+	}
+
+	function getCurveIntersections(v1, v2, c1, c2, locations, include) {
+		var epsilon = 1e-12,
+			min = Math.min,
+			max = Math.max;
+
+		if (max(v1[0], v1[2], v1[4], v1[6]) + epsilon >
+			min(v2[0], v2[2], v2[4], v2[6]) &&
+			min(v1[0], v1[2], v1[4], v1[6]) - epsilon <
+			max(v2[0], v2[2], v2[4], v2[6]) &&
+			max(v1[1], v1[3], v1[5], v1[7]) + epsilon >
+			min(v2[1], v2[3], v2[5], v2[7]) &&
+			min(v1[1], v1[3], v1[5], v1[7]) - epsilon <
+			max(v2[1], v2[3], v2[5], v2[7])) {
+			var overlaps = getOverlaps(v1, v2);
+			if (overlaps) {
+				for (var i = 0; i < 2; i++) {
+					var overlap = overlaps[i];
+					addLocation(locations, include,
+							c1, overlap[0],
+							c2, overlap[1], true);
+				}
+			} else {
+				var straight1 = Curve.isStraight(v1),
+					straight2 = Curve.isStraight(v2),
+					straight = straight1 && straight2,
+					flip = straight1 && !straight2,
+					before = locations.length;
+				(straight
+					? addLineIntersection
+					: straight1 || straight2
+						? addCurveLineIntersections
+						: addCurveIntersections)(
+							flip ? v2 : v1, flip ? v1 : v2,
+							flip ? c2 : c1, flip ? c1 : c2,
+							locations, include, flip,
+							0, 0, 0, 1, 0, 1);
+				if (!straight || locations.length === before) {
+					for (var i = 0; i < 4; i++) {
+						var t1 = i >> 1,
+							t2 = i & 1,
+							i1 = t1 * 6,
+							i2 = t2 * 6,
+							p1 = new Point(v1[i1], v1[i1 + 1]),
+							p2 = new Point(v2[i2], v2[i2 + 1]);
+						if (p1.isClose(p2, epsilon)) {
+							addLocation(locations, include,
+									c1, t1,
+									c2, t2);
+						}
+					}
+				}
+			}
+		}
+		return locations;
+	}
+
+	function getLoopIntersection(v1, c1, locations, include) {
+		var info = Curve.classify(v1);
+		if (info.type === 'loop') {
+			var roots = info.roots;
 			addLocation(locations, include,
-					curve1, Curve.getParameterOf(v1, x, y), point,
-					curve2, Curve.getParameterOf(v2, x, y), point);
-		}
-	}
-
-	return { statics: {
-		getIntersections: function(v1, v2, c1, c2, locations, include) {
-			var linear1 = Curve.isLinear(v1),
-				linear2 = Curve.isLinear(v2),
-				c1p1 = c1.getPoint1(),
-				c1p2 = c1.getPoint2(),
-				c2p1 = c2.getPoint1(),
-				c2p2 = c2.getPoint2(),
-				tolerance = 0.000001;
-			if (c1p1.isClose(c2p1, tolerance))
-				addLocation(locations, include, c1, 0, c1p1, c2, 0, c1p1);
-			if (c1p1.isClose(c2p2, tolerance))
-				addLocation(locations, include, c1, 0, c1p1, c2, 1, c1p1);
-			(linear1 && linear2
-				? addLineIntersection
-				: linear1 || linear2
-					? addCurveLineIntersections
-					: addCurveIntersections)(
-						v1, v2, c1, c2, locations, include,
-						0, 1, 0, 1, 0, false, 0);
-			if (c1p2.isClose(c2p1, tolerance))
-				addLocation(locations, include, c1, 1, c1p2, c2, 0, c1p2);
-			if (c1p2.isClose(c2p2, tolerance))
-				addLocation(locations, include, c1, 1, c1p2, c2, 1, c1p2);
-			return locations;
-		},
-
-		filterIntersections: function(locations, _expand) {
-			var last = locations.length - 1,
-				tMax = 1 - 0.000001;
-			for (var i = last; i >= 0; i--) {
-				var loc = locations[i],
-					next = loc._curve.getNext(),
-					next2 = loc._curve2.getNext();
-				if (next && loc._parameter >= tMax) {
-					loc._parameter = 0;
-					loc._curve = next;
-				}
-				if (next2 && loc._parameter2 >= tMax) {
-					loc._parameter2 = 0;
-					loc._curve2 = next2;
-				}
-			}
-
-			function compare(loc1, loc2) {
-				var path1 = loc1.getPath(),
-					path2 = loc2.getPath();
-				return path1 === path2
-						? (loc1.getIndex() + loc1.getParameter())
-								- (loc2.getIndex() + loc2.getParameter())
-						: path1._id - path2._id;
-			}
-
-			if (last > 0) {
-				locations.sort(compare);
-				for (var i = last; i > 0; i--) {
-					if (locations[i].equals(locations[i - 1])) {
-						locations.splice(i, 1);
-						last--;
-					}
-				}
-			}
-			if (_expand) {
-				for (var i = last; i >= 0; i--)
-					locations.push(locations[i].getIntersection());
-				locations.sort(compare);
-			}
-			return locations;
-		}
-	}};
+					c1, roots[0],
+					c1, roots[1]);
+		}
+	  return locations;
+	}
+
+	function getIntersections(curves1, curves2, include, matrix1, matrix2,
+			_returnFirst) {
+		var self = !curves2;
+		if (self)
+			curves2 = curves1;
+		var length1 = curves1.length,
+			length2 = curves2.length,
+			values2 = [],
+			arrays = [],
+			locations,
+			current;
+		for (var i = 0; i < length2; i++)
+			values2[i] = curves2[i].getValues(matrix2);
+		for (var i = 0; i < length1; i++) {
+			var curve1 = curves1[i],
+				values1 = self ? values2[i] : curve1.getValues(matrix1),
+				path1 = curve1.getPath();
+			if (path1 !== current) {
+				current = path1;
+				locations = [];
+				arrays.push(locations);
+			}
+			if (self) {
+				getLoopIntersection(values1, curve1, locations, include);
+			}
+			for (var j = self ? i + 1 : 0; j < length2; j++) {
+				if (_returnFirst && locations.length)
+					return locations;
+				getCurveIntersections(values1, values2[j], curve1, curves2[j],
+						locations, include);
+			}
+		}
+		locations = [];
+		for (var i = 0, l = arrays.length; i < l; i++) {
+			Base.push(locations, arrays[i]);
+		}
+		return locations;
+	}
+
+	function getOverlaps(v1, v2) {
+
+		function getSquaredLineLength(v) {
+			var x = v[6] - v[0],
+				y = v[7] - v[1];
+			return x * x + y * y;
+		}
+
+		var abs = Math.abs,
+			getDistance = Line.getDistance,
+			timeEpsilon = 1e-8,
+			geomEpsilon = 1e-7,
+			straight1 = Curve.isStraight(v1),
+			straight2 = Curve.isStraight(v2),
+			straightBoth = straight1 && straight2,
+			flip = getSquaredLineLength(v1) < getSquaredLineLength(v2),
+			l1 = flip ? v2 : v1,
+			l2 = flip ? v1 : v2,
+			px = l1[0], py = l1[1],
+			vx = l1[6] - px, vy = l1[7] - py;
+		if (getDistance(px, py, vx, vy, l2[0], l2[1], true) < geomEpsilon &&
+			getDistance(px, py, vx, vy, l2[6], l2[7], true) < geomEpsilon) {
+			if (!straightBoth &&
+				getDistance(px, py, vx, vy, l1[2], l1[3], true) < geomEpsilon &&
+				getDistance(px, py, vx, vy, l1[4], l1[5], true) < geomEpsilon &&
+				getDistance(px, py, vx, vy, l2[2], l2[3], true) < geomEpsilon &&
+				getDistance(px, py, vx, vy, l2[4], l2[5], true) < geomEpsilon) {
+				straight1 = straight2 = straightBoth = true;
+			}
+		} else if (straightBoth) {
+			return null;
+		}
+		if (straight1 ^ straight2) {
+			return null;
+		}
+
+		var v = [v1, v2],
+			pairs = [];
+		for (var i = 0; i < 4 && pairs.length < 2; i++) {
+			var i1 = i & 1,
+				i2 = i1 ^ 1,
+				t1 = i >> 1,
+				t2 = Curve.getTimeOf(v[i1], new Point(
+					v[i2][t1 ? 6 : 0],
+					v[i2][t1 ? 7 : 1]));
+			if (t2 != null) {
+				var pair = i1 ? [t1, t2] : [t2, t1];
+				if (!pairs.length ||
+					abs(pair[0] - pairs[0][0]) > timeEpsilon &&
+					abs(pair[1] - pairs[0][1]) > timeEpsilon) {
+					pairs.push(pair);
+				}
+			}
+			if (i > 2 && !pairs.length)
+				break;
+		}
+		if (pairs.length !== 2) {
+			pairs = null;
+		} else if (!straightBoth) {
+			var o1 = Curve.getPart(v1, pairs[0][0], pairs[1][0]),
+				o2 = Curve.getPart(v2, pairs[0][1], pairs[1][1]);
+			if (abs(o2[2] - o1[2]) > geomEpsilon ||
+				abs(o2[3] - o1[3]) > geomEpsilon ||
+				abs(o2[4] - o1[4]) > geomEpsilon ||
+				abs(o2[5] - o1[5]) > geomEpsilon)
+				pairs = null;
+		}
+		return pairs;
+	}
+
+	function getTimesWithTangent(v, tangent) {
+		var x0 = v[0], y0 = v[1],
+			x1 = v[2], y1 = v[3],
+			x2 = v[4], y2 = v[5],
+			x3 = v[6], y3 = v[7],
+			normalized = tangent.normalize(),
+			tx = normalized.x,
+			ty = normalized.y,
+			ax = 3 * x3 - 9 * x2 + 9 * x1 - 3 * x0,
+			ay = 3 * y3 - 9 * y2 + 9 * y1 - 3 * y0,
+			bx = 6 * x2 - 12 * x1 + 6 * x0,
+			by = 6 * y2 - 12 * y1 + 6 * y0,
+			cx = 3 * x1 - 3 * x0,
+			cy = 3 * y1 - 3 * y0,
+			den = 2 * ax * ty - 2 * ay * tx,
+			times = [];
+		if (Math.abs(den) < Numerical.CURVETIME_EPSILON) {
+			var num = ax * cy - ay * cx,
+				den = ax * by - ay * bx;
+			if (den != 0) {
+				var t = -num / den;
+				if (t >= 0 && t <= 1) times.push(t);
+			}
+		} else {
+			var delta = (bx * bx - 4 * ax * cx) * ty * ty +
+				(-2 * bx * by + 4 * ay * cx + 4 * ax * cy) * tx * ty +
+				(by * by - 4 * ay * cy) * tx * tx,
+				k = bx * ty - by * tx;
+			if (delta >= 0 && den != 0) {
+				var d = Math.sqrt(delta),
+					t0 = -(k + d) / den,
+					t1 = (-k + d) / den;
+				if (t0 >= 0 && t0 <= 1) times.push(t0);
+				if (t1 >= 0 && t1 <= 1) times.push(t1);
+			}
+		}
+		return times;
+	}
+
+	return {
+		getIntersections: function(curve) {
+			var v1 = this.getValues(),
+				v2 = curve && curve !== this && curve.getValues();
+			return v2 ? getCurveIntersections(v1, v2, this, curve, [])
+					  : getLoopIntersection(v1, this, []);
+		},
+
+		statics: {
+			getOverlaps: getOverlaps,
+			getIntersections: getIntersections,
+			getCurveLineIntersections: getCurveLineIntersections,
+			getTimesWithTangent: getTimesWithTangent
+		}
+	};
 });
 
 var CurveLocation = Base.extend({
 	_class: 'CurveLocation',
-	beans: true,
-
-	initialize: function CurveLocation(curve, parameter, point, _curve2,
-			_parameter2, _point2, _distance) {
-		this._id = UID.get(CurveLocation);
+
+	initialize: function CurveLocation(curve, time, point, _overlap, _distance) {
+		if (time >= 0.99999999) {
+			var next = curve.getNext();
+			if (next) {
+				time = 0;
+				curve = next;
+			}
+		}
+		this._setCurve(curve);
+		this._time = time;
+		this._point = point || curve.getPointAtTime(time);
+		this._overlap = _overlap;
+		this._distance = _distance;
+		this._intersection = this._next = this._previous = null;
+	},
+
+	_setCurve: function(curve) {
 		var path = curve._path;
+		this._path = path;
 		this._version = path ? path._version : 0;
 		this._curve = curve;
-		this._parameter = parameter;
-		this._point = point || curve.getPointAt(parameter, true);
-		this._curve2 = _curve2;
-		this._parameter2 = _parameter2;
-		this._point2 = _point2;
-		this._distance = _distance;
+		this._segment = null;
 		this._segment1 = curve._segment1;
 		this._segment2 = curve._segment2;
 	},
 
-	getSegment: function(_preferFirst) {
-		if (!this._segment) {
+	_setSegment: function(segment) {
+		this._setCurve(segment.getCurve());
+		this._segment = segment;
+		this._time = segment === this._segment1 ? 0 : 1;
+		this._point = segment._point.clone();
+	},
+
+	getSegment: function() {
+		var segment = this._segment;
+		if (!segment) {
 			var curve = this.getCurve(),
-				parameter = this.getParameter();
-			if (parameter === 1) {
-				this._segment = curve._segment2;
-			} else if (parameter === 0 || _preferFirst) {
-				this._segment = curve._segment1;
-			} else if (parameter == null) {
-				return null;
-			} else {
-				this._segment = curve.getPartLength(0, parameter)
-					< curve.getPartLength(parameter, 1)
+				time = this.getTime();
+			if (time === 0) {
+				segment = curve._segment1;
+			} else if (time === 1) {
+				segment = curve._segment2;
+			} else if (time != null) {
+				segment = curve.getPartLength(0, time)
+					< curve.getPartLength(time, 1)
 						? curve._segment1
 						: curve._segment2;
 			}
-		}
-		return this._segment;
+			this._segment = segment;
+		}
+		return segment;
 	},
 
 	getCurve: function() {
-		var curve = this._curve,
-			path = curve && curve._path;
+		var path = this._path,
+			that = this;
 		if (path && path._version !== this._version) {
-			curve = null;
-			this._parameter = null;
-		}
-		if (!curve) {
-			curve = this._segment1.getCurve();
-			if (curve.getParameterOf(this._point) == null)
-				curve = this._segment2.getPrevious().getCurve();
-			this._curve = curve;
-			path = curve._path;
-			this._version = path ? path._version : 0;
-		}
-		return curve;
+			this._time = this._offset = this._curveOffset = this._curve = null;
+		}
+
+		function trySegment(segment) {
+			var curve = segment && segment.getCurve();
+			if (curve && (that._time = curve.getTimeOf(that._point)) != null) {
+				that._setCurve(curve);
+				return curve;
+			}
+		}
+
+		return this._curve
+			|| trySegment(this._segment)
+			|| trySegment(this._segment1)
+			|| trySegment(this._segment2.getPrevious());
 	},
 
 	getPath: function() {
@@ -6519,37 +7542,49 @@
 		return curve && curve.getIndex();
 	},
 
-	getParameter: function() {
+	getTime: function() {
 		var curve = this.getCurve(),
-			parameter = this._parameter;
-		return curve && parameter == null
-			? this._parameter = curve.getParameterOf(this._point)
-			: parameter;
-	},
+			time = this._time;
+		return curve && time == null
+			? this._time = curve.getTimeOf(this._point)
+			: time;
+	},
+
+	getParameter: '#getTime',
 
 	getPoint: function() {
 		return this._point;
 	},
 
 	getOffset: function() {
-		var path = this.getPath();
-		return path ? path._getOffset(this) : this.getCurveOffset();
+		var offset = this._offset;
+		if (offset == null) {
+			offset = 0;
+			var path = this.getPath(),
+				index = this.getIndex();
+			if (path && index != null) {
+				var curves = path.getCurves();
+				for (var i = 0; i < index; i++)
+					offset += curves[i].getLength();
+			}
+			this._offset = offset += this.getCurveOffset();
+		}
+		return offset;
 	},
 
 	getCurveOffset: function() {
-		var curve = this.getCurve(),
-			parameter = this.getParameter();
-		return parameter != null && curve && curve.getPartLength(0, parameter);
+		var offset = this._curveOffset;
+		if (offset == null) {
+			var curve = this.getCurve(),
+				time = this.getTime();
+			this._curveOffset = offset = time != null && curve
+					&& curve.getPartLength(0, time);
+		}
+		return offset;
 	},
 
 	getIntersection: function() {
-		var intersection = this._intersection;
-		if (!intersection && this._curve2) {
-			this._intersection = intersection = new CurveLocation(this._curve2,
-					this._parameter2, this._point2 || this._point, this);
-			intersection._intersection = this;
-		}
-		return intersection;
+		return this._intersection;
 	},
 
 	getDistance: function() {
@@ -6557,25 +7592,43 @@
 	},
 
 	divide: function() {
-		var curve = this.getCurve();
-		return curve && curve.divide(this.getParameter(), true);
+		var curve = this.getCurve(),
+			res = curve && curve.divideAtTime(this.getTime());
+		if (res) {
+			this._setSegment(res._segment1);
+		}
+		return res;
 	},
 
 	split: function() {
-		var curve = this.getCurve();
-		return curve && curve.split(this.getParameter(), true);
-	},
-
-	equals: function(loc) {
-		var abs = Math.abs,
-			tolerance = 0.000001;
-		return this === loc
-				|| loc instanceof CurveLocation
-					&& this.getCurve() === loc.getCurve()
-					&& abs(this.getParameter() - loc.getParameter()) < tolerance
-					&& this._curve2 === loc._curve2
-					&& abs(this._parameter2 - loc._parameter2) < tolerance
-				|| false;
+		var curve = this.getCurve(),
+			path = curve._path,
+			res = curve && curve.splitAtTime(this.getTime());
+		if (res) {
+			this._setSegment(path.getLastSegment());
+		}
+		return  res;
+	},
+
+	equals: function(loc, _ignoreOther) {
+		var res = this === loc;
+		if (!res && loc instanceof CurveLocation) {
+			var c1 = this.getCurve(),
+				c2 = loc.getCurve(),
+				p1 = c1._path,
+				p2 = c2._path;
+			if (p1 === p2) {
+				var abs = Math.abs,
+					epsilon = 1e-7,
+					diff = abs(this.getOffset() - loc.getOffset()),
+					i1 = !_ignoreOther && this._intersection,
+					i2 = !_ignoreOther && loc._intersection;
+				res = (diff < epsilon
+						|| p1 && abs(p1.getLength() - diff) < epsilon)
+					&& (!i1 && !i2 || i1 && i2 && i1.equals(i2, true));
+			}
+		}
+		return res;
 	},
 
 	toString: function() {
@@ -6587,93 +7640,217 @@
 		var index = this.getIndex();
 		if (index != null)
 			parts.push('index: ' + index);
-		var parameter = this.getParameter();
-		if (parameter != null)
-			parts.push('parameter: ' + f.number(parameter));
+		var time = this.getTime();
+		if (time != null)
+			parts.push('time: ' + f.number(time));
 		if (this._distance != null)
 			parts.push('distance: ' + f.number(this._distance));
 		return '{ ' + parts.join(', ') + ' }';
-	}
-}, Base.each(Curve.evaluateMethods, function(name) {
-	if (name !== 'getPoint') {
-		var get = name + 'At';
-		this[name] = function() {
-			var parameter = this.getParameter(),
-				curve = this.getCurve();
-			return parameter != null && curve && curve[get](parameter, true);
-		};
-	}
-}, {}));
+	},
+
+	isTouching: function() {
+		var inter = this._intersection;
+		if (inter && this.getTangent().isCollinear(inter.getTangent())) {
+			var curve1 = this.getCurve(),
+				curve2 = inter.getCurve();
+			return !(curve1.isStraight() && curve2.isStraight()
+					&& curve1.getLine().intersect(curve2.getLine()));
+		}
+		return false;
+	},
+
+	isCrossing: function() {
+		var inter = this._intersection;
+		if (!inter)
+			return false;
+		var t1 = this.getTime(),
+			t2 = inter.getTime(),
+			tMin = 1e-8,
+			tMax = 1 - tMin,
+			t1Inside = t1 >= tMin && t1 <= tMax,
+			t2Inside = t2 >= tMin && t2 <= tMax;
+		if (t1Inside && t2Inside)
+			return !this.isTouching();
+		var c2 = this.getCurve(),
+			c1 = t1 < tMin ? c2.getPrevious() : c2,
+			c4 = inter.getCurve(),
+			c3 = t2 < tMin ? c4.getPrevious() : c4;
+		if (t1 > tMax)
+			c2 = c2.getNext();
+		if (t2 > tMax)
+			c4 = c4.getNext();
+		if (!c1 || !c2 || !c3 || !c4)
+			return false;
+
+		var offsets = [];
+
+		function addOffsets(curve, end) {
+			var v = curve.getValues(),
+				roots = Curve.classify(v).roots || Curve.getPeaks(v),
+				count = roots.length,
+				t = end && count > 1 ? roots[count - 1]
+						: count > 0 ? roots[0]
+						: 0.5;
+			offsets.push(Curve.getLength(v, end ? t : 0, end ? 1 : t) / 2);
+		}
+
+		function isInRange(angle, min, max) {
+			return min < max
+					? angle > min && angle < max
+					: angle > min || angle < max;
+		}
+
+		if (!t1Inside) {
+			addOffsets(c1, true);
+			addOffsets(c2, false);
+		}
+		if (!t2Inside) {
+			addOffsets(c3, true);
+			addOffsets(c4, false);
+		}
+		var pt = this.getPoint(),
+			offset = Math.min.apply(Math, offsets),
+			v2 = t1Inside ? c2.getTangentAtTime(t1)
+					: c2.getPointAt(offset).subtract(pt),
+			v1 = t1Inside ? v2.negate()
+					: c1.getPointAt(-offset).subtract(pt),
+			v4 = t2Inside ? c4.getTangentAtTime(t2)
+					: c4.getPointAt(offset).subtract(pt),
+			v3 = t2Inside ? v4.negate()
+					: c3.getPointAt(-offset).subtract(pt),
+			a1 = v1.getAngle(),
+			a2 = v2.getAngle(),
+			a3 = v3.getAngle(),
+			a4 = v4.getAngle();
+		return !!(t1Inside
+				? (isInRange(a1, a3, a4) ^ isInRange(a2, a3, a4)) &&
+				  (isInRange(a1, a4, a3) ^ isInRange(a2, a4, a3))
+				: (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2)) &&
+				  (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1)));
+	},
+
+	hasOverlap: function() {
+		return !!this._overlap;
+	}
+}, Base.each(Curve._evaluateMethods, function(name) {
+	var get = name + 'At';
+	this[name] = function() {
+		var curve = this.getCurve(),
+			time = this.getTime();
+		return time != null && curve && curve[get](time, true);
+	};
+}, {
+	preserve: true
+}),
+new function() {
+
+	function insert(locations, loc, merge) {
+		var length = locations.length,
+			l = 0,
+			r = length - 1;
+
+		function search(index, dir) {
+			for (var i = index + dir; i >= -1 && i <= length; i += dir) {
+				var loc2 = locations[((i % length) + length) % length];
+				if (!loc.getPoint().isClose(loc2.getPoint(),
+						1e-7))
+					break;
+				if (loc.equals(loc2))
+					return loc2;
+			}
+			return null;
+		}
+
+		while (l <= r) {
+			var m = (l + r) >>> 1,
+				loc2 = locations[m],
+				found;
+			if (merge && (found = loc.equals(loc2) ? loc2
+					: (search(m, -1) || search(m, 1)))) {
+				if (loc._overlap) {
+					found._overlap = found._intersection._overlap = true;
+				}
+				return found;
+			}
+		var path1 = loc.getPath(),
+			path2 = loc2.getPath(),
+			diff = path1 !== path2
+				? path1._id - path2._id
+				: (loc.getIndex() + loc.getTime())
+				- (loc2.getIndex() + loc2.getTime());
+			if (diff < 0) {
+				r = m - 1;
+			} else {
+				l = m + 1;
+			}
+		}
+		locations.splice(l, 0, loc);
+		return loc;
+	}
+
+	return { statics: {
+		insert: insert,
+
+		expand: function(locations) {
+			var expanded = locations.slice();
+			for (var i = locations.length - 1; i >= 0; i--) {
+				insert(expanded, locations[i]._intersection, false);
+			}
+			return expanded;
+		}
+	}};
+});
 
 var PathItem = Item.extend({
 	_class: 'PathItem',
+	_selectBounds: false,
+	_canScaleStroke: true,
+	beans: true,
 
 	initialize: function PathItem() {
 	},
 
-	getIntersections: function(path, _matrix, _expand) {
-		if (this === path)
-			path = null;
-		var locations = [],
-			curves1 = this.getCurves(),
-			curves2 = path ? path.getCurves() : curves1,
-			matrix1 = this._matrix.orNullIfIdentity(),
-			matrix2 = path ? (_matrix || path._matrix).orNullIfIdentity()
-				: matrix1,
-			length1 = curves1.length,
-			length2 = path ? curves2.length : length1,
-			values2 = [],
-			tMin = 0.000001,
-			tMax = 1 - tMin;
-		if (path && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
-			return [];
-		for (var i = 0; i < length2; i++)
-			values2[i] = curves2[i].getValues(matrix2);
-		for (var i = 0; i < length1; i++) {
-			var curve1 = curves1[i],
-				values1 = path ? curve1.getValues(matrix1) : values2[i];
-			if (!path) {
-				var seg1 = curve1.getSegment1(),
-					seg2 = curve1.getSegment2(),
-					h1 = seg1._handleOut,
-					h2 = seg2._handleIn;
-				if (new Line(seg1._point.subtract(h1), h1.multiply(2), true)
-						.intersect(new Line(seg2._point.subtract(h2),
-						h2.multiply(2), true), false)) {
-					var parts = Curve.subdivide(values1);
-					Curve.getIntersections(
-						parts[0], parts[1], curve1, curve1, locations,
-						function(loc) {
-							if (loc._parameter <= tMax) {
-								loc._parameter /= 2;
-								loc._parameter2 = 0.5 + loc._parameter2 / 2;
-								return true;
-							}
-						}
-					);
-				}
-			}
-			for (var j = path ? 0 : i + 1; j < length2; j++) {
-				Curve.getIntersections(
-					values1, values2[j], curve1, curves2[j], locations,
-					!path && (j === i + 1 || j === length2 - 1 && i === 0)
-						&& function(loc) {
-							var t = loc._parameter;
-							return t >= tMin && t <= tMax;
-						}
-				);
-			}
-		}
-		return Curve.filterIntersections(locations, _expand);
+	statics: {
+		create: function(arg) {
+			var data,
+				segments,
+				compound;
+			if (Base.isPlainObject(arg)) {
+				segments = arg.segments;
+				data = arg.pathData;
+			} else if (Array.isArray(arg)) {
+				segments = arg;
+			} else if (typeof arg === 'string') {
+				data = arg;
+			}
+			if (segments) {
+				var first = segments[0];
+				compound = first && Array.isArray(first[0]);
+			} else if (data) {
+				compound = (data.match(/m/gi) || []).length > 1
+						|| /z\s*\S+/i.test(data);
+			}
+			var ctor = compound ? CompoundPath : Path;
+			return new ctor(arg);
+		}
 	},
 
 	_asPathItem: function() {
 		return this;
 	},
 
+	isClockwise: function() {
+		return this.getArea() >= 0;
+	},
+
+	setClockwise: function(clockwise) {
+		if (this.isClockwise() != (clockwise = !!clockwise))
+			this.reverse();
+	},
+
 	setPathData: function(data) {
 
-		var parts = data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig),
+		var parts = data && data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig),
 			coords,
 			relative = false,
 			previous,
@@ -6705,21 +7882,24 @@
 			var length = coords && coords.length;
 			relative = command === lower;
 			if (previous === 'z' && !/[mz]/.test(lower))
-				this.moveTo(current = start);
+				this.moveTo(current);
 			switch (lower) {
 			case 'm':
 			case 'l':
 				var move = lower === 'm';
-				for (var j = 0; j < length; j += 2)
-					this[j === 0 && move ? 'moveTo' : 'lineTo'](
-							current = getPoint(j));
+				for (var j = 0; j < length; j += 2) {
+					this[move ? 'moveTo' : 'lineTo'](current = getPoint(j));
+					if (move) {
+						start = current;
+						move = false;
+					}
+				}
 				control = current;
-				if (move)
-					start = current;
 				break;
 			case 'h':
 			case 'v':
 				var coord = lower === 'h' ? 'x' : 'y';
+				current = current.clone();
 				for (var j = 0; j < length; j++) {
 					current[coord] = getCoord(j, coord);
 					this.lineTo(current);
@@ -6770,7 +7950,8 @@
 				}
 				break;
 			case 'z':
-				this.closePath(true);
+				this.closePath(1e-12);
+				current = start;
 				break;
 			}
 			previous = lower;
@@ -6782,9 +7963,110 @@
 	},
 
 	_contains: function(point) {
-		var winding = this._getWinding(point, false, true);
-		return !!(this.getWindingRule() === 'evenodd' ? winding & 1 : winding);
-	}
+		var winding = point.isInside(
+				this.getBounds({ internal: true, handle: true }))
+					? this._getWinding(point)
+					: {};
+		return winding.onPath || !!(this.getFillRule() === 'evenodd'
+				? winding.windingL & 1 || winding.windingR & 1
+				: winding.winding);
+	},
+
+	getIntersections: function(path, include, _matrix, _returnFirst) {
+		var self = this === path || !path,
+			matrix1 = this._matrix._orNullIfIdentity(),
+			matrix2 = self ? matrix1
+				: (_matrix || path._matrix)._orNullIfIdentity();
+		return self || this.getBounds(matrix1).intersects(
+				path.getBounds(matrix2), 1e-12)
+				? Curve.getIntersections(
+						this.getCurves(), !self && path.getCurves(), include,
+						matrix1, matrix2, _returnFirst)
+				: [];
+	},
+
+	getCrossings: function(path) {
+		return this.getIntersections(path, function(inter) {
+			return inter.hasOverlap() || inter.isCrossing();
+		});
+	},
+
+	getNearestLocation: function() {
+		var point = Point.read(arguments),
+			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() {
+		var loc = this.getNearestLocation.apply(this, arguments);
+		return loc ? loc.getPoint() : loc;
+	},
+
+	interpolate: function(from, to, factor) {
+		var isPath = !this._children,
+			name = isPath ? '_segments' : '_children',
+			itemsFrom = from[name],
+			itemsTo = to[name],
+			items = this[name];
+		if (!itemsFrom || !itemsTo || itemsFrom.length !== itemsTo.length) {
+			throw new Error('Invalid operands in interpolate() call: ' +
+					from + ', ' + to);
+		}
+		var current = items.length,
+			length = itemsTo.length;
+		if (current < length) {
+			var ctor = isPath ? Segment : Path;
+			for (var i = current; i < length; i++) {
+				this.add(new ctor());
+			}
+		} else if (current > length) {
+			this[isPath ? 'removeSegments' : 'removeChildren'](length, current);
+		}
+		for (var i = 0; i < length; i++) {
+			items[i].interpolate(itemsFrom[i], itemsTo[i], factor);
+		}
+		if (isPath) {
+			this.setClosed(from._closed);
+			this._changed(9);
+		}
+	},
+
+	compare: function(path) {
+		var ok = false;
+		if (path) {
+			var paths1 = this._children || [this],
+				paths2 = path._children ? path._children.slice() : [path],
+				length1 = paths1.length,
+				length2 = paths2.length,
+				matched = [],
+				count = 0;
+			ok = true;
+			for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) {
+				var path1 = paths1[i1];
+				ok = false;
+				for (var i2 = length2 - 1; i2 >= 0 && !ok; i2--) {
+					if (path1.compare(paths2[i2])) {
+						if (!matched[i2]) {
+							matched[i2] = true;
+							count++;
+						}
+						ok = true;
+					}
+				}
+			}
+			ok = ok && count === length2;
+		}
+		return ok;
+	},
 
 });
 
@@ -6811,7 +8093,7 @@
 			this.setSegments(segments);
 		} else {
 			this._curves = undefined;
-			this._selectedSegmentState = 0;
+			this._segmentSelection = 0;
 			if (!segments && typeof arg === 'string') {
 				this.setPathData(arg);
 				arg = null;
@@ -6825,30 +8107,22 @@
 				&& Base.equals(this._segments, item._segments);
 	},
 
-	clone: function(insert) {
-		var copy = new Path(Item.NO_INSERT);
-		copy.setSegments(this._segments);
-		copy._closed = this._closed;
-		if (this._clockwise !== undefined)
-			copy._clockwise = this._clockwise;
-		return this._clone(copy, insert);
+	copyContent: function(source) {
+		this.setSegments(source._segments);
+		this._closed = source._closed;
 	},
 
 	_changed: function _changed(flags) {
 		_changed.base.call(this, flags);
 		if (flags & 8) {
-			var parent = this._parent;
-			if (parent)
-				parent._currentPath = undefined;
-			this._length = this._clockwise = undefined;
-			if (flags & 16) {
+			this._length = this._area = undefined;
+			if (flags & 32) {
 				this._version++;
 			} else if (this._curves) {
 			   for (var i = 0, l = this._curves.length; i < l; i++)
 					this._curves[i]._changed();
 			}
-			this._monoCurves = undefined;
-		} else if (flags & 32) {
+		} else if (flags & 64) {
 			this._bounds = undefined;
 		}
 	},
@@ -6863,12 +8137,19 @@
 	},
 
 	setSegments: function(segments) {
-		var fullySelected = this.isFullySelected();
+		var fullySelected = this.isFullySelected(),
+			length = segments && segments.length;
 		this._segments.length = 0;
-		this._selectedSegmentState = 0;
+		this._segmentSelection = 0;
 		this._curves = undefined;
-		if (segments && segments.length > 0)
-			this._add(Segment.readAll(segments));
+		if (length) {
+			var last = segments[length - 1];
+			if (typeof last === 'boolean') {
+				this.setClosed(last);
+				length--;
+			}
+			this._add(Segment.readList(segments, 0, {}, length));
+		}
 		if (fullySelected)
 			this.setFullySelected(true);
 	},
@@ -6916,7 +8197,7 @@
 					this._curves[length - 1] = new Curve(this,
 						this._segments[length - 1], this._segments[0]);
 			}
-			this._changed(25);
+			this._changed(41);
 		}
 	}
 }, {
@@ -6935,7 +8216,7 @@
 			parts = [];
 
 		function addSegment(segment, skipLine) {
-			segment._transformCoordinates(_matrix, coords, false);
+			segment._transformCoordinates(_matrix, coords);
 			curX = coords[0];
 			curY = coords[1];
 			if (first) {
@@ -6946,12 +8227,18 @@
 				inY = coords[3];
 				if (inX === curX && inY === curY
 						&& outX === prevX && outY === prevY) {
-					if (!skipLine)
-						parts.push('l' + f.pair(curX - prevX, curY - prevY));
+					if (!skipLine) {
+						var dx = curX - prevX,
+							dy = curY - prevY;
+						parts.push(
+							  dx === 0 ? 'v' + f.number(dy)
+							: dy === 0 ? 'h' + f.number(dx)
+							: 'l' + f.pair(dx, dy));
+					}
 				} else {
 					parts.push('c' + f.pair(outX - prevX, outY - prevY)
-							+ ' ' + f.pair(inX - prevX, inY - prevY)
-							+ ' ' + f.pair(curX - prevX, curY - prevY));
+							 + ' ' + f.pair( inX - prevX,  inY - prevY)
+							 + ' ' + f.pair(curX - prevX, curY - prevY));
 				}
 			}
 			prevX = curX;
@@ -6960,7 +8247,7 @@
 			outY = coords[5];
 		}
 
-		if (length === 0)
+		if (!length)
 			return '';
 
 		for (var i = 0; i < length; i++)
@@ -6970,35 +8257,17 @@
 			parts.push('z');
 		}
 		return parts.join('');
-	}
-}, {
+	},
 
 	isEmpty: function() {
-		return this._segments.length === 0;
-	},
-
-	isLinear: function() {
-		var segments = this._segments;
-		for (var i = 0, l = segments.length; i < l; i++) {
-			if (!segments[i].isLinear())
-				return false;
-		}
-		return true;
-	},
-
-	hasHandles: function() {
-		var segments = this._segments;
-		for (var i = 0, l = segments.length; i < l; i++) {
-			if (segments[i].hasHandles())
-				return true;
-		}
-		return false;
+		return !this._segments.length;
 	},
 
 	_transformContent: 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 segments = this._segments,
+			coords = new Array(6);
+		for (var i = 0, l = segments.length; i < l; i++)
+			segments[i]._transformCoordinates(matrix, coords, true);
 		return true;
 	},
 
@@ -7014,52 +8283,52 @@
 				segment = segs[i] = segment.clone();
 			segment._path = this;
 			segment._index = index + i;
-			if (segment._selectionState)
-				this._updateSelection(segment, 0, segment._selectionState);
+			if (segment._selection)
+				this._updateSelection(segment, 0, segment._selection);
 		}
 		if (append) {
-			segments.push.apply(segments, segs);
+			Base.push(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 || segs._curves) {
-			if (!curves)
-				curves = this._curves = [];
-			var from = index > 0 ? index - 1 : index,
-				start = from,
-				to = Math.min(from + amount, this._countCurves());
+		if (curves) {
+			var total = this._countCurves(),
+				start = index > 0 && index + amount - 1 === total ? index - 1
+					: index,
+				insert = start,
+				end = Math.min(start + amount, total);
 			if (segs._curves) {
-				curves.splice.apply(curves, [from, 0].concat(segs._curves));
-				start += segs._curves.length;
-			}
-			for (var i = start; i < to; i++)
+				curves.splice.apply(curves, [start, 0].concat(segs._curves));
+				insert += segs._curves.length;
+			}
+			for (var i = insert; i < end; i++)
 				curves.splice(i, 0, new Curve(this, null, null));
-			this._adjustCurves(from, to);
-		}
-		this._changed(25);
+			this._adjustCurves(start, end);
+		}
+		this._changed(41);
 		return segs;
 	},
 
-	_adjustCurves: function(from, to) {
+	_adjustCurves: function(start, end) {
 		var segments = this._segments,
 			curves = this._curves,
 			curve;
-		for (var i = from; i < to; i++) {
+		for (var i = start; i < end; i++) {
 			curve = curves[i];
 			curve._path = this;
 			curve._segment1 = segments[i];
 			curve._segment2 = segments[i + 1] || segments[0];
 			curve._changed();
 		}
-		if (curve = curves[this._closed && from === 0 ? segments.length - 1
-				: from - 1]) {
-			curve._segment2 = segments[from] || segments[0];
+		if (curve = curves[this._closed && !start ? segments.length - 1
+				: start - 1]) {
+			curve._segment2 = segments[start] || segments[0];
 			curve._changed();
 		}
-		if (curve = curves[to]) {
-			curve._segment1 = segments[to];
+		if (curve = curves[end]) {
+			curve._segment1 = segments[end];
 			curve._changed();
 		}
 	},
@@ -7071,13 +8340,13 @@
 
 	add: function(segment1 ) {
 		return arguments.length > 1 && typeof segment1 !== 'number'
-			? this._add(Segment.readAll(arguments))
+			? this._add(Segment.readList(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.readList(arguments, 1), index)
 			: this._add([ Segment.read(arguments, 1) ], index)[0];
 	},
 
@@ -7090,71 +8359,98 @@
 	},
 
 	addSegments: function(segments) {
-		return this._add(Segment.readAll(segments));
+		return this._add(Segment.readList(segments));
 	},
 
 	insertSegments: function(index, segments) {
-		return this._add(Segment.readAll(segments), index);
+		return this._add(Segment.readList(segments), index);
 	},
 
 	removeSegment: function(index) {
 		return this.removeSegments(index, index + 1)[0] || null;
 	},
 
-	removeSegments: function(from, to, _includeCurves) {
-		from = from || 0;
-		to = Base.pick(to, this._segments.length);
+	removeSegments: function(start, end, _includeCurves) {
+		start = start || 0;
+		end = Base.pick(end, this._segments.length);
 		var segments = this._segments,
 			curves = this._curves,
 			count = segments.length,
-			removed = segments.splice(from, to - from),
+			removed = segments.splice(start, end - start),
 			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);
+			if (segment._selection)
+				this._updateSelection(segment, segment._selection, 0);
 			segment._index = segment._path = null;
 		}
-		for (var i = from, l = segments.length; i < l; i++)
+		for (var i = start, l = segments.length; i < l; i++)
 			segments[i]._index = i;
 		if (curves) {
-			var index = from > 0 && to === count + (this._closed ? 1 : 0)
-					? from - 1
-					: from,
+			var index = start > 0 && end === count + (this._closed ? 1 : 0)
+					? start - 1
+					: start,
 				curves = curves.splice(index, amount);
+			for (var i = curves.length - 1; i >= 0; i--)
+				curves[i]._path = null;
 			if (_includeCurves)
 				removed._curves = curves.slice(1);
 			this._adjustCurves(index, index);
 		}
-		this._changed(25);
+		this._changed(41);
 		return removed;
 	},
 
 	clear: '#removeSegments',
 
+	hasHandles: function() {
+		var segments = this._segments;
+		for (var i = 0, l = segments.length; i < l; i++) {
+			if (segments[i].hasHandles())
+				return true;
+		}
+		return false;
+	},
+
+	clearHandles: function() {
+		var segments = this._segments;
+		for (var i = 0, l = segments.length; i < l; i++)
+			segments[i].clearHandles();
+	},
+
 	getLength: function() {
 		if (this._length == null) {
-			var curves = this.getCurves();
-			this._length = 0;
+			var curves = this.getCurves(),
+				length = 0;
 			for (var i = 0, l = curves.length; i < l; i++)
-				this._length += curves[i].getLength();
+				length += curves[i].getLength();
+			this._length = length;
 		}
 		return this._length;
 	},
 
 	getArea: function() {
-		var curves = this.getCurves();
-		var area = 0;
-		for (var i = 0, l = curves.length; i < l; i++)
-			area += curves[i].getArea();
+		var area = this._area;
+		if (area == null) {
+			var segments = this._segments,
+				closed = this._closed;
+			area = 0;
+			for (var i = 0, l = segments.length; i < l; i++) {
+				var last = i + 1 === l;
+				area += Curve.getArea(Curve.getValues(
+						segments[i], segments[last ? 0 : i + 1],
+						null, last && !closed));
+			}
+			this._area = area;
+		}
 		return area;
 	},
 
 	isFullySelected: function() {
 		var length = this._segments.length;
-		return this._selected && length > 0 && this._selectedSegmentState
+		return this.isSelected() && length > 0 && this._segmentSelection
 				=== length * 7;
 	},
 
@@ -7164,79 +8460,50 @@
 		this.setSelected(selected);
 	},
 
-	setSelected: function setSelected(selected) {
-		if (!selected)
+	setSelection: function setSelection(selection) {
+		if (!(selection & 1))
 			this._selectSegments(false);
-		setSelected.base.call(this, selected);
+		setSelection.base.call(this, selection);
 	},
 
 	_selectSegments: function(selected) {
-		var length = this._segments.length;
-		this._selectedSegmentState = selected
-				? length * 7 : 0;
+		var segments = this._segments,
+			length = segments.length,
+			selection = selected ? 7 : 0;
+		this._segmentSelection = selection * length;
 		for (var i = 0; i < length; i++)
-			this._segments[i]._selectionState = selected
-					? 7 : 0;
-	},
-
-	_updateSelection: function(segment, oldState, newState) {
-		segment._selectionState = newState;
-		var total = this._selectedSegmentState += newState - oldState;
-		if (total > 0)
+			segments[i]._selection = selection;
+	},
+
+	_updateSelection: function(segment, oldSelection, newSelection) {
+		segment._selection = newSelection;
+		var selection = this._segmentSelection += newSelection - oldSelection;
+		if (selection > 0)
 			this.setSelected(true);
 	},
 
-	flatten: function(maxDistance) {
-		var iterator = new PathIterator(this, 64, 0.1),
-			pos = 0,
-			step = iterator.length / Math.ceil(iterator.length / maxDistance),
-			end = iterator.length + (this._closed ? -step : step) / 2;
-		var segments = [];
-		while (pos <= end) {
-			segments.push(new Segment(iterator.getPointAt(pos)));
-			pos += step;
-		}
-		this.setSegments(segments);
-	},
-
-	reduce: function() {
-		var curves = this.getCurves();
-		for (var i = curves.length - 1; i >= 0; i--) {
-			var curve = curves[i];
-			if (curve.isLinear() && curve.getLength() === 0)
-				curve.remove();
-		}
-		return this;
-	},
-
-	simplify: function(tolerance) {
-		if (this._segments.length > 2) {
-			var fitter = new PathFitter(this, tolerance || 2.5);
-			this.setSegments(fitter.fit());
-		}
-	},
-
-	split: function(index, parameter) {
-		if (parameter === null)
-			return null;
-		if (arguments.length === 1) {
-			var arg = index;
-			if (typeof arg === 'number')
-				arg = this.getLocationAt(arg);
-			if (!arg)
-				return null
-			index = arg.index;
-			parameter = arg.parameter;
-		}
-		var tolerance = 0.000001;
-		if (parameter >= 1 - tolerance) {
+	divideAt: function(location) {
+		var loc = this.getLocationAt(location),
+			curve;
+		return loc && (curve = loc.getCurve().divideAt(loc.getCurveOffset()))
+				? curve._segment1
+				: null;
+	},
+
+	splitAt: function(location) {
+		var loc = this.getLocationAt(location),
+			index = loc && loc.index,
+			time = loc && loc.time,
+			tMin = 1e-8,
+			tMax = 1 - tMin;
+		if (time > tMax) {
 			index++;
-			parameter--;
+			time = 0;
 		}
 		var curves = this.getCurves();
 		if (index >= 0 && index < curves.length) {
-			if (parameter > tolerance) {
-				curves[index++].divide(parameter, true);
+			if (time >= tMin) {
+				curves[index++].divideAtTime(time);
 			}
 			var segs = this.removeSegments(index, this._segments.length, true),
 				path;
@@ -7244,7 +8511,9 @@
 				this.setClosed(false);
 				path = this;
 			} else {
-				path = this._clone(new Path().insertAbove(this, true));
+				path = new Path(Item.NO_INSERT);
+				path.insertAbove(this);
+				path.copyAttributes(this);
 			}
 			path._add(segs, 0);
 			this.addSegment(segs[0]);
@@ -7253,16 +8522,65 @@
 		return null;
 	},
 
-	isClockwise: function() {
-		if (this._clockwise !== undefined)
-			return this._clockwise;
-		return Path.isClockwise(this._segments);
-	},
-
-	setClockwise: function(clockwise) {
-		if (this.isClockwise() != (clockwise = !!clockwise))
-			this.reverse();
-		this._clockwise = clockwise;
+	split: function(index, time) {
+		var curve,
+			location = time === undefined ? index
+				: (curve = this.getCurves()[index])
+					&& curve.getLocationAtTime(time);
+		return location != null ? this.splitAt(location) : null;
+	},
+
+	join: function(path, tolerance) {
+		var epsilon = tolerance || 0;
+		if (path && path !== this) {
+			var segments = path._segments,
+				last1 = this.getLastSegment(),
+				last2 = path.getLastSegment();
+			if (!last2)
+				return this;
+			if (last1 && last1._point.isClose(last2._point, epsilon))
+				path.reverse();
+			var first2 = path.getFirstSegment();
+			if (last1 && last1._point.isClose(first2._point, epsilon)) {
+				last1.setHandleOut(first2._handleOut);
+				this._add(segments.slice(1));
+			} else {
+				var first1 = this.getFirstSegment();
+				if (first1 && first1._point.isClose(first2._point, epsilon))
+					path.reverse();
+				last2 = path.getLastSegment();
+				if (first1 && first1._point.isClose(last2._point, epsilon)) {
+					first1.setHandleIn(last2._handleIn);
+					this._add(segments.slice(0, segments.length - 1), 0);
+				} else {
+					this._add(segments.slice());
+				}
+			}
+			if (path._closed)
+				this._add([segments[0]]);
+			path.remove();
+		}
+		var first = this.getFirstSegment(),
+			last = this.getLastSegment();
+		if (first !== last && first._point.isClose(last._point, epsilon)) {
+			first.setHandleIn(last._handleIn);
+			last.remove();
+			this.setClosed(true);
+		}
+		return this;
+	},
+
+	reduce: function(options) {
+		var curves = this.getCurves(),
+			simplify = options && options.simplify,
+			tolerance = simplify ? 1e-7 : 0;
+		for (var i = curves.length - 1; i >= 0; i--) {
+			var curve = curves[i];
+			if (!curve.hasHandles() && (!curve.hasLength(tolerance)
+					|| simplify && curve.isCollinear(curve.getNext())))
+				curve.remove();
+		}
+		return this;
 	},
 
 	reverse: function() {
@@ -7275,48 +8593,135 @@
 			segment._index = i;
 		}
 		this._curves = null;
-		if (this._clockwise !== undefined)
-			this._clockwise = !this._clockwise;
 		this._changed(9);
 	},
 
-	join: function(path) {
-		if (path) {
-			var segments = path._segments,
-				last1 = this.getLastSegment(),
-				last2 = path.getLastSegment();
-			if (!last2)
-				return this;
-			if (last1 && last1._point.equals(last2._point))
-				path.reverse();
-			var first2 = path.getFirstSegment();
-			if (last1 && last1._point.equals(first2._point)) {
-				last1.setHandleOut(first2._handleOut);
-				this._add(segments.slice(1));
-			} else {
-				var first1 = this.getFirstSegment();
-				if (first1 && first1._point.equals(first2._point))
-					path.reverse();
-				last2 = path.getLastSegment();
-				if (first1 && first1._point.equals(last2._point)) {
-					first1.setHandleIn(last2._handleIn);
-					this._add(segments.slice(0, segments.length - 1), 0);
-				} else {
-					this._add(segments.slice());
-				}
-			}
-			if (path.closed)
-				this._add([segments[0]]);
-			path.remove();
-		}
-		var first = this.getFirstSegment(),
-			last = this.getLastSegment();
-		if (first !== last && first._point.equals(last._point)) {
-			first.setHandleIn(last._handleIn);
-			last.remove();
-			this.setClosed(true);
-		}
-		return this;
+	flatten: function(flatness) {
+		var flattener = new PathFlattener(this, flatness || 0.25, 256, true),
+			parts = flattener.parts,
+			length = parts.length,
+			segments = [];
+		for (var i = 0; i < length; i++) {
+			segments.push(new Segment(parts[i].curve.slice(0, 2)));
+		}
+		if (!this._closed && length > 0) {
+			segments.push(new Segment(parts[length - 1].curve.slice(6)));
+		}
+		this.setSegments(segments);
+	},
+
+	simplify: function(tolerance) {
+		var segments = new PathFitter(this).fit(tolerance || 2.5);
+		if (segments)
+			this.setSegments(segments);
+		return !!segments;
+	},
+
+	smooth: function(options) {
+		var that = this,
+			opts = options || {},
+			type = opts.type || 'asymmetric',
+			segments = this._segments,
+			length = segments.length,
+			closed = this._closed;
+
+		function getIndex(value, _default) {
+			var index = value && value.index;
+			if (index != null) {
+				var path = value.path;
+				if (path && path !== that)
+					throw new Error(value._class + ' ' + index + ' of ' + path
+							+ ' is not part of ' + that);
+				if (_default && value instanceof Curve)
+					index++;
+			} else {
+				index = typeof value === 'number' ? value : _default;
+			}
+			return Math.min(index < 0 && closed
+					? index % length
+					: index < 0 ? index + length : index, length - 1);
+		}
+
+		var loop = closed && opts.from === undefined && opts.to === undefined,
+			from = getIndex(opts.from, 0),
+			to = getIndex(opts.to, length - 1);
+
+		if (from > to) {
+			if (closed) {
+				from -= length;
+			} else {
+				var tmp = from;
+				from = to;
+				to = tmp;
+			}
+		}
+		if (/^(?:asymmetric|continuous)$/.test(type)) {
+			var asymmetric = type === 'asymmetric',
+				min = Math.min,
+				amount = to - from + 1,
+				n = amount - 1,
+				padding = loop ? min(amount, 4) : 1,
+				paddingLeft = padding,
+				paddingRight = padding,
+				knots = [];
+			if (!closed) {
+				paddingLeft = min(1, from);
+				paddingRight = min(1, length - to - 1);
+			}
+			n += paddingLeft + paddingRight;
+			if (n <= 1)
+				return;
+			for (var i = 0, j = from - paddingLeft; i <= n; i++, j++) {
+				knots[i] = segments[(j < 0 ? j + length : j) % length]._point;
+			}
+
+			var x = knots[0]._x + 2 * knots[1]._x,
+				y = knots[0]._y + 2 * knots[1]._y,
+				f = 2,
+				n_1 = n - 1,
+				rx = [x],
+				ry = [y],
+				rf = [f],
+				px = [],
+				py = [];
+			for (var i = 1; i < n; i++) {
+				var internal = i < n_1,
+					a = internal ? 1 : asymmetric ? 1 : 2,
+					b = internal ? 4 : asymmetric ? 2 : 7,
+					u = internal ? 4 : asymmetric ? 3 : 8,
+					v = internal ? 2 : asymmetric ? 0 : 1,
+					m = a / f;
+				f = rf[i] = b - m;
+				x = rx[i] = u * knots[i]._x + v * knots[i + 1]._x - m * x;
+				y = ry[i] = u * knots[i]._y + v * knots[i + 1]._y - m * y;
+			}
+
+			px[n_1] = rx[n_1] / rf[n_1];
+			py[n_1] = ry[n_1] / rf[n_1];
+			for (var i = n - 2; i >= 0; i--) {
+				px[i] = (rx[i] - px[i + 1]) / rf[i];
+				py[i] = (ry[i] - py[i + 1]) / rf[i];
+			}
+			px[n] = (3 * knots[n]._x - px[n_1]) / 2;
+			py[n] = (3 * knots[n]._y - py[n_1]) / 2;
+
+			for (var i = paddingLeft, max = n - paddingRight, j = from;
+					i <= max; i++, j++) {
+				var segment = segments[j < 0 ? j + length : j],
+					pt = segment._point,
+					hx = px[i] - pt._x,
+					hy = py[i] - pt._y;
+				if (loop || i < max)
+					segment.setHandleOut(hx, hy);
+				if (loop || i > paddingLeft)
+					segment.setHandleIn(-hx, -hy);
+			}
+		} else {
+			for (var i = from; i <= to; i++) {
+				segments[i < 0 ? i + length : i].smooth(opts,
+						!loop && i === from, !loop && i === to);
+			}
+		}
 	},
 
 	toShape: function(insert) {
@@ -7330,15 +8735,43 @@
 			topCenter;
 
 		function isCollinear(i, j) {
-			return segments[i].isCollinear(segments[j]);
+			var seg1 = segments[i],
+				seg2 = seg1.getNext(),
+				seg3 = segments[j],
+				seg4 = seg3.getNext();
+			return seg1._handleOut.isZero() && seg2._handleIn.isZero()
+					&& seg3._handleOut.isZero() && seg4._handleIn.isZero()
+					&& seg2._point.subtract(seg1._point).isCollinear(
+						seg4._point.subtract(seg3._point));
 		}
 
 		function isOrthogonal(i) {
-			return segments[i].isOrthogonal();
+			var seg2 = segments[i],
+				seg1 = seg2.getPrevious(),
+				seg3 = seg2.getNext();
+			return seg1._handleOut.isZero() && seg2._handleIn.isZero()
+					&& seg2._handleOut.isZero() && seg3._handleIn.isZero()
+					&& seg2._point.subtract(seg1._point).isOrthogonal(
+						seg3._point.subtract(seg2._point));
 		}
 
 		function isArc(i) {
-			return segments[i].isOrthogonalArc();
+			var seg1 = segments[i],
+				seg2 = seg1.getNext(),
+				handle1 = seg1._handleOut,
+				handle2 = seg2._handleIn,
+				kappa = 0.5522847498307936;
+			if (handle1.isOrthogonal(handle2)) {
+				var pt1 = seg1._point,
+					pt2 = seg2._point,
+					corner = new Line(pt1, handle1, true).intersect(
+							new Line(pt2, handle2, true), true);
+				return corner && Numerical.isZero(handle1.getLength() /
+						corner.subtract(pt1).getLength() - kappa)
+					&& Numerical.isZero(handle2.getLength() /
+						corner.subtract(pt2).getLength() - kappa);
+			}
+			return false;
 		}
 
 		function getDistance(i, j) {
@@ -7371,19 +8804,86 @@
 
 		if (type) {
 			var center = this.getPosition(true),
-				shape = this._clone(new type({
+				shape = new type({
 					center: center,
 					size: size,
 					radius: radius,
 					insert: false
-				}), insert, false);
+				});
+			shape.copyAttributes(this, true);
+			shape._matrix.prepend(this._matrix);
 			shape.rotate(topCenter.subtract(center).getAngle() + 90);
+			if (insert === undefined || insert)
+				shape.insertAbove(this);
 			return shape;
 		}
 		return null;
 	},
 
-	_hitTestSelf: function(point, options) {
+	toPath: '#clone',
+
+	compare: function compare(path) {
+		if (!path || path instanceof CompoundPath)
+			return compare.base.call(this, path);
+		var curves1 = this.getCurves(),
+			curves2 = path.getCurves(),
+			length1 = curves1.length,
+			length2 = curves2.length;
+		if (!length1 || !length2) {
+			return length1 == length2;
+		}
+		var v1 = curves1[0].getValues(),
+			values2 = [],
+			pos1 = 0, pos2,
+			end1 = 0, end2;
+		for (var i = 0; i < length2; i++) {
+			var v2 = curves2[i].getValues();
+			values2.push(v2);
+			var overlaps = Curve.getOverlaps(v1, v2);
+			if (overlaps) {
+				pos2 = !i && overlaps[0][0] > 0 ? length2 - 1 : i;
+				end2 = overlaps[0][1];
+				break;
+			}
+		}
+		var abs = Math.abs,
+			epsilon = 1e-8,
+			v2 = values2[pos2],
+			start2;
+		while (v1 && v2) {
+			var overlaps = Curve.getOverlaps(v1, v2);
+			if (overlaps) {
+				var t1 = overlaps[0][0];
+				if (abs(t1 - end1) < epsilon) {
+					end1 = overlaps[1][0];
+					if (end1 === 1) {
+						v1 = ++pos1 < length1 ? curves1[pos1].getValues() : null;
+						end1 = 0;
+					}
+					var t2 = overlaps[0][1];
+					if (abs(t2 - end2) < epsilon) {
+						if (!start2)
+							start2 = [pos2, t2];
+						end2 = overlaps[1][1];
+						if (end2 === 1) {
+							if (++pos2 >= length2)
+								pos2 = 0;
+							v2 = values2[pos2] || curves2[pos2].getValues();
+							end2 = 0;
+						}
+						if (!v1) {
+							return start2[0] === pos2 && start2[1] === end2;
+						}
+						continue;
+					}
+				}
+			}
+			break;
+		}
+		return false;
+	},
+
+	_hitTestSelf: function(point, options, viewMatrix, strokeMatrix) {
 		var that = this,
 			style = this.getStyle(),
 			segments = this._segments,
@@ -7396,16 +8896,17 @@
 			hitStroke = options.stroke && style.hasStroke(),
 			hitFill = options.fill && style.hasFill(),
 			hitCurves = options.curves,
-			radius = hitStroke
+			strokeRadius = hitStroke
 					? style.getStrokeWidth() / 2
 					: hitFill && options.tolerance > 0 || hitCurves
 						? 0 : null;
-		if (radius !== null) {
-			if (radius > 0) {
+		if (strokeRadius !== null) {
+			if (strokeRadius > 0) {
 				join = style.getStrokeJoin();
 				cap = style.getStrokeCap();
-				miterLimit = radius * style.getMiterLimit();
-				strokePadding = tolerancePadding.add(new Point(radius, radius));
+				miterLimit = style.getMiterLimit();
+				strokePadding = strokePadding.add(
+					Path._getStrokePadding(strokeRadius, strokeMatrix));
 			} else {
 				join = cap = 'round';
 			}
@@ -7442,16 +8943,20 @@
 		}
 
 		function checkSegmentStroke(segment) {
-			if (join !== 'round' || cap !== 'round') {
+			var isJoin = closed || segment._index > 0
+					&& segment._index < numSegments - 1;
+			if ((isJoin ? join : cap) === 'round') {
+				return isCloseEnough(segment._point, strokePadding);
+			} else {
 				area = new Path({ internal: true, closed: true });
-				if (closed || segment._index > 0
-						&& segment._index < numSegments - 1) {
-					if (join !== 'round' && (segment._handleIn.isZero()
-							|| segment._handleOut.isZero()))
-						Path._addBevelJoin(segment, join, radius, miterLimit,
-								addToArea, true);
-				} else if (cap !== 'round') {
-					Path._addSquareCap(segment, cap, radius, addToArea, true);
+				if (isJoin) {
+					if (!segment.isSmooth()) {
+						Path._addBevelJoin(segment, join, strokeRadius,
+							   miterLimit, null, strokeMatrix, addToArea, true);
+					}
+				} else if (cap === 'square') {
+					Path._addSquareCap(segment, cap, strokeRadius, null,
+							strokeMatrix, addToArea, true);
 				}
 				if (!area.isEmpty()) {
 					var loc;
@@ -7460,7 +8965,6 @@
 							&& isCloseEnough(loc.getPoint(), tolerancePadding);
 				}
 			}
-			return isCloseEnough(segment._point, strokePadding);
 		}
 
 		if (options.ends && !options.segments && !closed) {
@@ -7472,11 +8976,11 @@
 				if (res = checkSegmentPoints(segments[i]))
 					return res;
 		}
-		if (radius !== null) {
+		if (strokeRadius !== null) {
 			loc = this.getNearestLocation(point);
 			if (loc) {
-				var parameter = loc.getParameter();
-				if (parameter === 0 || parameter === 1 && numSegments > 1) {
+				var time = loc.getTime();
+				if (time === 0 || time === 1 && numSegments > 1) {
 					if (!checkSegmentStroke(loc.getSegment()))
 						loc = null;
 				} else if (!isCloseEnough(loc.getPoint(), strokePadding)) {
@@ -7486,7 +8990,8 @@
 			if (!loc && join === 'miter' && numSegments > 1) {
 				for (var i = 0; i < numSegments; i++) {
 					var segment = segments[i];
-					if (point.getDistance(segment._point) <= miterLimit
+					if (point.getDistance(segment._point)
+							<= miterLimit * strokeRadius
 							&& checkSegmentStroke(segment)) {
 						loc = segment.getLocation();
 						break;
@@ -7505,32 +9010,16 @@
 						: null;
 	}
 
-}, Base.each(Curve.evaluateMethods,
+}, Base.each(Curve._evaluateMethods,
 	function(name) {
-		this[name + 'At'] = function(offset, isParameter) {
-			var loc = this.getLocationAt(offset, isParameter);
+		this[name + 'At'] = function(offset) {
+			var loc = this.getLocationAt(offset);
 			return loc && loc[name]();
 		};
 	},
 {
 	beans: false,
 
-	_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],
-				parameter = location.getParameter();
-			if (parameter > 0)
-				offset += curve.getPartLength(0, parameter);
-			return offset;
-		}
-		return null;
-	},
-
 	getLocationOf: function() {
 		var point = Point.read(arguments),
 			curves = this.getCurves();
@@ -7547,49 +9036,60 @@
 		return loc ? loc.getOffset() : null;
 	},
 
-	getLocationAt: function(offset, isParameter) {
-		var curves = this.getCurves(),
-			length = 0;
-		if (isParameter) {
-			var index = ~~offset,
-				curve = curves[index];
-			return curve ? curve.getLocationAt(offset - index, true) : null;
-		}
-		for (var i = 0, l = curves.length; i < l; i++) {
-			var start = length,
-				curve = curves[i];
-			length += curve.getLength();
-			if (length > offset) {
-				return curve.getLocationAt(offset - start);
-			}
-		}
-		if (offset <= this.getLength())
-			return new CurveLocation(curves[curves.length - 1], 1);
+	getLocationAt: function(offset) {
+		if (typeof offset === 'number') {
+			var curves = this.getCurves(),
+				length = 0;
+			for (var i = 0, l = curves.length; i < l; i++) {
+				var start = length,
+					curve = curves[i];
+				length += curve.getLength();
+				if (length > offset) {
+					return curve.getLocationAt(offset - start);
+				}
+			}
+			if (curves.length > 0 && offset <= this.getLength()) {
+				return new CurveLocation(curves[curves.length - 1], 1);
+			}
+		} else if (offset && offset.getPath && offset.getPath() === this) {
+			return offset;
+		}
 		return null;
 	},
 
-	getNearestLocation: function() {
-		var point = Point.read(arguments),
-			curves = this.getCurves(),
-			minDist = Infinity,
-			minLoc = null;
+	getOffsetsWithTangent: function() {
+		var tangent = Point.read(arguments);
+		if (tangent.isZero()) {
+			return [];
+		}
+
+		var offsets = [];
+		var curveStart = 0;
+		var curves = this.getCurves();
 		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() {
-		return this.getNearestLocation.apply(this, arguments).getPoint();
-	}
-}), new function() {
+			var curve = curves[i];
+			var curveTimes = curve.getTimesWithTangent(tangent);
+			for (var j = 0, m = curveTimes.length; j < m; j++) {
+				var offset = curveStart + curve.getOffsetAtTime(curveTimes[j]);
+				if (offsets.indexOf(offset) < 0) {
+					offsets.push(offset);
+				}
+			}
+			curveStart += curve.length;
+		}
+		return offsets;
+	}
+}),
+new function() {
 
 	function drawHandles(ctx, segments, matrix, size) {
-		var half = size / 2;
+		if (size <= 0) return;
+
+		var half = size / 2,
+			miniSize = size - 2,
+			miniHalf = half - 1,
+			coords = new Array(6),
+			pX, pY;
 
 		function drawHandle(index) {
 			var hX = coords[index],
@@ -7605,22 +9105,21 @@
 			}
 		}
 
-		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,
-				pX = coords[0],
-				pY = coords[1];
-			if (state & 1)
+			var segment = segments[i],
+				selection = segment._selection;
+			segment._transformCoordinates(matrix, coords);
+			pX = coords[0];
+			pY = coords[1];
+			if (selection & 2)
 				drawHandle(2);
-			if (state & 2)
+			if (selection & 4)
 				drawHandle(4);
 			ctx.fillRect(pX - half, pY - half, size, size);
-			if (!(state & 4)) {
+			if (miniSize > 0 && !(selection & 1)) {
 				var fillStyle = ctx.fillStyle;
 				ctx.fillStyle = '#ffffff';
-				ctx.fillRect(pX - half + 1, pY - half + 1, size - 2, size - 2);
+				ctx.fillRect(pX - miniHalf, pY - miniHalf, miniSize, miniSize);
 				ctx.fillStyle = fillStyle;
 			}
 		}
@@ -7638,7 +9137,7 @@
 
 		function drawSegment(segment) {
 			if (matrix) {
-				segment._transformCoordinates(matrix, coords, false);
+				segment._transformCoordinates(matrix, coords);
 				curX = coords[0];
 				curY = coords[1];
 			} else {
@@ -7684,7 +9183,7 @@
 	}
 
 	return {
-		_draw: function(ctx, param, strokeMatrix) {
+		_draw: function(ctx, param, viewMatrix, strokeMatrix) {
 			var dontStart = param.dontStart,
 				dontPaint = param.dontFinish || param.clip,
 				style = this.getStyle(),
@@ -7697,14 +9196,10 @@
 			if (!dontStart)
 				ctx.beginPath();
 
-			if (!dontStart && this._currentPath) {
-				ctx.currentPath = this._currentPath;
-			} else if (hasFill || hasStroke && !dashLength || dontPaint) {
+			if (hasFill || hasStroke && !dashLength || dontPaint) {
 				drawSegments(ctx, this, strokeMatrix);
 				if (this._closed)
 					ctx.closePath();
-				if (!dontStart)
-					this._currentPath = ctx.currentPath;
 			}
 
 			function getOffset(i) {
@@ -7712,18 +9207,18 @@
 			}
 
 			if (!dontPaint && (hasFill || hasStroke)) {
-				this._setStyles(ctx);
+				this._setStyles(ctx, param, viewMatrix);
 				if (hasFill) {
-					ctx.fill(style.getWindingRule());
+					ctx.fill(style.getFillRule());
 					ctx.shadowColor = 'rgba(0,0,0,0)';
 				}
 				if (hasStroke) {
 					if (dashLength) {
 						if (!dontStart)
 							ctx.beginPath();
-						var iterator = new PathIterator(this, 32, 0.25,
+						var flattener = new PathFlattener(this, 0.25, 32, false,
 								strokeMatrix),
-							length = iterator.length,
+							length = flattener.length,
 							from = -style.getDashOffset(), to,
 							i = 0;
 						from = from % length;
@@ -7733,7 +9228,7 @@
 						while (from < length) {
 							to = from + getOffset(i++);
 							if (from > 0 || to > 0)
-								iterator.drawPart(ctx,
+								flattener.drawPart(ctx,
 										Math.max(from, 0), Math.max(to, 0));
 							from = to + getOffset(i++);
 						}
@@ -7750,103 +9245,11 @@
 			drawHandles(ctx, this._segments, matrix, paper.settings.handleSize);
 		}
 	};
-}, 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,
-				closed = this._closed,
-				n = size,
-				overlap = 0;
-			if (size <= 2)
-				return;
-			if (closed) {
-				overlap = Math.min(size, 4);
-				n += Math.min(size, overlap) * 2;
-			}
-			var knots = [];
-			for (var i = 0; i < size; i++)
-				knots[i + overlap] = segments[i]._point;
-			if (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 (closed) {
-				for (var i = 0, j = size; i < overlap; i++, j++) {
-					var f1 = i / overlap,
-						f2 = 1 - f1,
-						ie = i + overlap,
-						je = j + overlap;
-					x[j] = x[i] * f1 + x[j] * f2;
-					y[j] = y[i] * f1 + y[j] * f2;
-					x[je] = x[ie] * f2 + x[je] * f1;
-					y[je] = y[ie] * f2 + y[je] * f1;
-				}
-				n--;
-			}
-			var handleIn = null;
-			for (var i = overlap; i <= n - overlap; i++) {
-				var segment = segments[i - overlap];
-				if (handleIn)
-					segment.setHandleIn(handleIn.subtract(segment._point));
-				if (i < n) {
-					segment.setHandleOut(
-							new Point(x[i], y[i]).subtract(segment._point));
-					handleIn = i < n - 1
-							? new Point(
-								2 * knots[i + 1]._x - x[i + 1],
-								2 * knots[i + 1]._y - y[i + 1])
-							: new Point(
-								(knots[n]._x + x[n - 1]) / 2,
-								(knots[n]._y + y[n - 1]) / 2);
-				}
-			}
-			if (closed && handleIn) {
-				var segment = this._segments[0];
-				segment.setHandleIn(handleIn.subtract(segment._point));
-			}
-		}
-	};
-}, new function() {
+},
+new function() {
 	function getCurrentSegment(that) {
 		var segments = that._segments;
-		if (segments.length === 0)
+		if (!segments.length)
 			throw new Error('Use a moveTo() command first');
 		return segments[segments.length - 1];
 	}
@@ -7903,7 +9306,9 @@
 		},
 
 		arcTo: function() {
-			var current = getCurrentSegment(this),
+			var abs = Math.abs,
+				sqrt = Math.sqrt,
+				current = getCurrentSegment(this),
 				from = current._point,
 				to = Point.read(arguments),
 				through,
@@ -7917,9 +9322,10 @@
 			} else if (Base.remain(arguments) <= 2) {
 				through = to;
 				to = Point.read(arguments);
-			} else {
-				var radius = Size.read(arguments);
-				if (radius.isZero())
+			} else if (!from.equals(to)) {
+				var radius = Size.read(arguments),
+					isZero = Numerical.isZero;
+				if (isZero(radius.width) || isZero(radius.height))
 					return this.lineTo(to);
 				var rotation = Base.read(arguments),
 					clockwise = !!Base.read(arguments),
@@ -7928,15 +9334,13 @@
 					pt = from.subtract(middle).rotate(-rotation),
 					x = pt.x,
 					y = pt.y,
-					abs = Math.abs,
-					epsilon = 1e-12,
 					rx = abs(radius.width),
 					ry = abs(radius.height),
 					rxSq = rx * rx,
 					rySq = ry * ry,
-					xSq =  x * x,
-					ySq =  y * y;
-				var factor = Math.sqrt(xSq / rxSq + ySq / rySq);
+					xSq = x * x,
+					ySq = y * y;
+				var factor = sqrt(xSq / rxSq + ySq / rySq);
 				if (factor > 1) {
 					rx *= factor;
 					ry *= factor;
@@ -7945,14 +9349,13 @@
 				}
 				factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) /
 						(rxSq * ySq + rySq * xSq);
-				if (abs(factor) < epsilon)
+				if (abs(factor) < 1e-12)
 					factor = 0;
 				if (factor < 0)
 					throw new Error(
 							'Cannot create an arc with the given arguments');
 				center = new Point(rx * y / ry, -ry * x / rx)
-						.multiply((large === clockwise ? -1 : 1)
-							* Math.sqrt(factor))
+						.multiply((large === clockwise ? -1 : 1) * sqrt(factor))
 						.rotate(rotation).add(middle);
 				matrix = new Matrix().translate(center).rotate(rotation)
 						.scale(rx, ry);
@@ -7979,45 +9382,48 @@
 				}
 				vector = from.subtract(center);
 				extent = vector.getDirectedAngle(to.subtract(center));
-				var centerSide = line.getSide(center);
+				var centerSide = line.getSide(center, true);
 				if (centerSide === 0) {
-					extent = throughSide * Math.abs(extent);
+					extent = throughSide * abs(extent);
 				} else if (throughSide === centerSide) {
 					extent += extent < 0 ? 360 : -360;
 				}
 			}
-			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 = to,
-					out = null;
-				if (i < count) {
-					out = vector.rotate(90).multiply(z);
-					if (matrix) {
-						pt = matrix._transformPoint(vector);
-						out = matrix._transformPoint(vector.add(out))
-								.subtract(pt);
+			if (extent) {
+				var epsilon = 1e-7,
+					ext = abs(extent),
+					count = ext >= 360 ? 4 : Math.ceil((ext - epsilon) / 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 = to,
+						out = null;
+					if (i < count) {
+						out = vector.rotate(90).multiply(z);
+						if (matrix) {
+							pt = matrix._transformPoint(vector);
+							out = matrix._transformPoint(vector.add(out))
+									.subtract(pt);
+						} else {
+							pt = center.add(vector);
+						}
+					}
+					if (!i) {
+						current.setHandleOut(out);
 					} else {
-						pt = center.add(vector);
-					}
-				}
-				if (i === 0) {
-					current.setHandleOut(out);
-				} else {
-					var _in = vector.rotate(-90).multiply(z);
-					if (matrix) {
-						_in = matrix._transformPoint(vector.add(_in))
-								.subtract(pt);
-					}
-					segments.push(new Segment(pt, _in, out));
-				}
-				vector = vector.rotate(inc);
-			}
-			this._add(segments);
+						var _in = vector.rotate(-90).multiply(z);
+						if (matrix) {
+							_in = matrix._transformPoint(vector.add(_in))
+									.subtract(pt);
+						}
+						segments.push(new Segment(pt, _in, out));
+					}
+					vector = vector.rotate(inc);
+				}
+				this._add(segments);
+			}
 		},
 
 		lineBy: function() {
@@ -8061,40 +9467,35 @@
 			}
 		},
 
-		closePath: function(join) {
+		closePath: function(tolerance) {
 			this.setClosed(true);
-			if (join)
-				this.join();
+			this.join(this, tolerance);
 		}
 	};
 }, {
 
-	_getBounds: function(getter, matrix) {
-		return Path[getter](this._segments, this._closed, this.getStyle(),
-				matrix);
+	_getBounds: function(matrix, options) {
+		var method = options.handle
+				? 'getHandleBounds'
+				: options.stroke
+				? 'getStrokeBounds'
+				: 'getBounds';
+		return Path[method](this._segments, this._closed, this, matrix, options);
 	},
 
 statics: {
-	isClockwise: function(segments) {
-		var sum = 0;
-		for (var i = 0, l = segments.length; i < l; i++)
-			sum += Curve.getEdgeSum(Curve.getValues(
-					segments[i], segments[i + 1 < l ? i + 1 : 0]));
-		return sum > 0;
-	},
-
-	getBounds: function(segments, closed, style, matrix, strokePadding) {
+	getBounds: function(segments, closed, path, matrix, options, strokePadding) {
 		var first = segments[0];
 		if (!first)
 			return new Rectangle();
 		var coords = new Array(6),
-			prevCoords = first._transformCoordinates(matrix, new Array(6), false),
+			prevCoords = first._transformCoordinates(matrix, new Array(6)),
 			min = prevCoords.slice(0, 2),
 			max = min.slice(),
 			roots = new Array(2);
 
 		function processSegment(segment) {
-			segment._transformCoordinates(matrix, coords, false);
+			segment._transformCoordinates(matrix, coords);
 			for (var i = 0; i < 2; i++) {
 				Curve._addBounds(
 					prevCoords[i],
@@ -8115,36 +9516,38 @@
 		return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
 	},
 
-	getStrokeBounds: function(segments, closed, style, matrix) {
-		if (!style.hasStroke())
-			return Path.getBounds(segments, closed, style, matrix);
-		var length = segments.length - (closed ? 0 : 1),
-			radius = style.getStrokeWidth() / 2,
-			padding = Path._getPenPadding(radius, matrix),
-			bounds = Path.getBounds(segments, closed, style, matrix, padding),
+	getStrokeBounds: function(segments, closed, path, matrix, options) {
+		var style = path.getStyle(),
+			stroke = style.hasStroke(),
+			strokeWidth = style.getStrokeWidth(),
+			strokeMatrix = stroke && path._getStrokeMatrix(matrix, options),
+			strokePadding = stroke && Path._getStrokePadding(strokeWidth,
+				strokeMatrix),
+			bounds = Path.getBounds(segments, closed, path, matrix, options,
+				strokePadding);
+		if (!stroke)
+			return bounds;
+		var strokeRadius = strokeWidth / 2,
 			join = style.getStrokeJoin(),
 			cap = style.getStrokeCap(),
-			miterLimit = radius * style.getMiterLimit();
-		var joinBounds = new Rectangle(new Size(padding).multiply(2));
-
-		function add(point) {
-			bounds = bounds.include(matrix
-				? matrix._transformPoint(point, point) : point);
+			miterLimit = style.getMiterLimit(),
+			joinBounds = new Rectangle(new Size(strokePadding));
+
+		function addPoint(point) {
+			bounds = bounds.include(point);
 		}
 
 		function addRound(segment) {
-			bounds = bounds.unite(joinBounds.setCenter(matrix
-				? matrix._transformPoint(segment._point) : segment._point));
+			bounds = bounds.unite(
+					joinBounds.setCenter(segment._point.transform(matrix)));
 		}
 
 		function addJoin(segment, join) {
-			var handleIn = segment._handleIn,
-				handleOut = segment._handleOut;
-			if (join === 'round' || !handleIn.isZero() && !handleOut.isZero()
-					&& handleIn.isCollinear(handleOut)) {
+			if (join === 'round' || segment.isSmooth()) {
 				addRound(segment);
 			} else {
-				Path._addBevelJoin(segment, join, radius, miterLimit, add);
+				Path._addBevelJoin(segment, join, strokeRadius, miterLimit,
+						matrix, strokeMatrix, addPoint);
 			}
 		}
 
@@ -8152,10 +9555,12 @@
 			if (cap === 'round') {
 				addRound(segment);
 			} else {
-				Path._addSquareCap(segment, cap, radius, add);
-			}
-		}
-
+				Path._addSquareCap(segment, cap, strokeRadius, matrix,
+						strokeMatrix, addPoint);
+			}
+		}
+
+		var length = segments.length - (closed ? 0 : 1);
 		for (var i = 1; i < length; i++)
 			addJoin(segments[i], join);
 		if (closed) {
@@ -8167,72 +9572,87 @@
 		return bounds;
 	},
 
-	_getPenPadding: function(radius, matrix) {
+	_getStrokePadding: function(radius, matrix) {
 		if (!matrix)
 			return [radius, radius];
-		var mx = matrix.shiftless(),
-			hor = mx.transform(new Point(radius, 0)),
-			ver = mx.transform(new Point(0, radius)),
+		var hor = new Point(radius, 0).transform(matrix),
+			ver = new Point(0, radius).transform(matrix),
 			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),
+			tx = Math.atan2(b * tan, a),
+			ty = Math.atan2(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)];
 	},
 
-	_addBevelJoin: function(segment, join, radius, miterLimit, addPoint, area) {
+	_addBevelJoin: function(segment, join, radius, miterLimit, matrix,
+			strokeMatrix, addPoint, isArea) {
 		var curve2 = segment.getCurve(),
 			curve1 = curve2.getPrevious(),
-			point = curve2.getPointAt(0, true),
-			normal1 = curve1.getNormalAt(1, true),
-			normal2 = curve2.getNormalAt(0, true),
-			step = normal1.getDirectedAngle(normal2) < 0 ? -radius : radius;
-		normal1.setLength(step);
-		normal2.setLength(step);
-		if (area) {
+			point = curve2.getPoint1().transform(matrix),
+			normal1 = curve1.getNormalAtTime(1).multiply(radius)
+				.transform(strokeMatrix),
+			normal2 = curve2.getNormalAtTime(0).multiply(radius)
+				.transform(strokeMatrix),
+				angle = normal1.getDirectedAngle(normal2);
+		if (angle < 0 || angle >= 180) {
+			normal1 = normal1.negate();
+			normal2 = normal2.negate();
+		}
+		if (isArea)
 			addPoint(point);
-			addPoint(point.add(normal1));
-		}
+		addPoint(point.add(normal1));
 		if (join === 'miter') {
-			var corner = new Line(
-					point.add(normal1),
+			var corner = new Line(point.add(normal1),
 					new Point(-normal1.y, normal1.x), true
-				).intersect(new Line(
-					point.add(normal2),
+				).intersect(new Line(point.add(normal2),
 					new Point(-normal2.y, normal2.x), true
 				), true);
-			if (corner && point.getDistance(corner) <= miterLimit) {
+			if (corner && point.getDistance(corner) <= miterLimit * radius) {
 				addPoint(corner);
-				if (!area)
-					return;
-			}
-		}
-		if (!area)
-			addPoint(point.add(normal1));
+			}
+		}
 		addPoint(point.add(normal2));
 	},
 
-	_addSquareCap: function(segment, cap, radius, addPoint, area) {
-		var point = segment._point,
+	_addSquareCap: function(segment, cap, radius, matrix, strokeMatrix,
+			addPoint, isArea) {
+		var point = segment._point.transform(matrix),
 			loc = segment.getLocation(),
-			normal = loc.getNormal().multiply(radius);
-		if (area) {
-			addPoint(point.subtract(normal));
-			addPoint(point.add(normal));
-		}
-		if (cap === 'square')
-			point = point.add(normal.rotate(loc.getParameter() === 0 ? -90 : 90));
+			normal = loc.getNormal()
+					.multiply(loc.getTime() === 0 ? radius : -radius)
+					.transform(strokeMatrix);
+		if (cap === 'square') {
+			if (isArea) {
+				addPoint(point.subtract(normal));
+				addPoint(point.add(normal));
+			}
+			point = point.add(normal.rotate(-90));
+		}
 		addPoint(point.add(normal));
 		addPoint(point.subtract(normal));
 	},
 
-	getHandleBounds: function(segments, closed, style, matrix, strokePadding,
-			joinPadding) {
+	getHandleBounds: function(segments, closed, path, matrix, options) {
+		var style = path.getStyle(),
+			stroke = options.stroke && style.hasStroke(),
+			strokePadding,
+			joinPadding;
+		if (stroke) {
+			var strokeMatrix = path._getStrokeMatrix(matrix, options),
+				strokeRadius = style.getStrokeWidth() / 2,
+				joinRadius = strokeRadius;
+			if (style.getStrokeJoin() === 'miter')
+				joinRadius = strokeRadius * style.getMiterLimit();
+			if (style.getStrokeCap() === 'square')
+				joinRadius = Math.max(joinRadius, strokeRadius * Math.SQRT2);
+			strokePadding = Path._getStrokePadding(strokeRadius, strokeMatrix);
+			joinPadding = Path._getStrokePadding(joinRadius, strokeMatrix);
+		}
 		var coords = new Array(6),
 			x1 = Infinity,
 			x2 = -x1,
@@ -8240,9 +9660,9 @@
 			y2 = x2;
 		for (var i = 0, l = segments.length; i < l; i++) {
 			var segment = segments[i];
-			segment._transformCoordinates(matrix, coords, false);
+			segment._transformCoordinates(matrix, coords);
 			for (var j = 0; j < 6; j += 2) {
-				var padding = j === 0 ? joinPadding : strokePadding,
+				var padding = !j ? joinPadding : strokePadding,
 					paddingX = padding ? padding[0] : 0,
 					paddingY = padding ? padding[1] : 0,
 					x = coords[j],
@@ -8258,20 +9678,6 @@
 			}
 		}
 		return new Rectangle(x1, y1, x2 - x1, y2 - y1);
-	},
-
-	getRoughBounds: function(segments, closed, style, matrix) {
-		var strokeRadius = style.hasStroke() ? style.getStrokeWidth() / 2 : 0,
-			joinRadius = strokeRadius;
-		if (strokeRadius > 0) {
-			if (style.getStrokeJoin() === 'miter')
-				joinRadius = strokeRadius * style.getMiterLimit();
-			if (style.getStrokeCap() === 'square')
-				joinRadius = Math.max(joinRadius, strokeRadius * Math.sqrt(2));
-		}
-		return Path.getHandleBounds(segments, closed, style, matrix,
-				Path._getPenPadding(strokeRadius, matrix),
-				Path._getPenPadding(joinRadius, matrix));
 	}
 }});
 
@@ -8287,10 +9693,10 @@
 
 	function createPath(segments, closed, args) {
 		var props = Base.getNamed(args),
-			path = new Path(props && props.insert === false && Item.NO_INSERT);
+			path = new Path(props && props.insert == false && Item.NO_INSERT);
 		path._add(segments);
 		path._closed = closed;
-		return path.set(props);
+		return path.set(props, { insert: true });
 	}
 
 	function createEllipse(center, radius, args) {
@@ -8370,7 +9776,7 @@
 				through = Point.readNamed(arguments, 'through'),
 				to = Point.readNamed(arguments, 'to'),
 				props = Base.getNamed(arguments),
-				path = new Path(props && props.insert === false
+				path = new Path(props && props.insert == false
 						&& Item.NO_INSERT);
 			path.moveTo(from);
 			path.arcTo(through, to);
@@ -8382,7 +9788,7 @@
 				sides = Base.readNamed(arguments, 'sides'),
 				radius = Base.readNamed(arguments, 'radius'),
 				step = 360 / sides,
-				three = !(sides % 3),
+				three = sides % 3 === 0,
 				vector = new Point(0, three ? -radius : radius),
 				offset = three ? -1 : 0.5,
 				segments = new Array(sides);
@@ -8413,6 +9819,7 @@
 	_serializeFields: {
 		children: []
 	},
+	beans: true,
 
 	initialize: function CompoundPath(arg) {
 		this._children = [];
@@ -8426,47 +9833,56 @@
 		}
 	},
 
-	insertChildren: function insertChildren(index, items, _preserve) {
-		items = insertChildren.base.call(this, index, items, _preserve, Path);
-		for (var i = 0, l = !_preserve && items && items.length; i < l; i++) {
-			var item = items[i];
-			if (item._clockwise === undefined)
-				item.setClockwise(item._index === 0);
-		}
-		return items;
-	},
-
-	reverse: function() {
+	insertChildren: function insertChildren(index, items) {
+		var list = items,
+			first = list[0];
+		if (first && typeof first[0] === 'number')
+			list = [list];
+		for (var i = items.length - 1; i >= 0; i--) {
+			var item = list[i];
+			if (list === items && !(item instanceof Path))
+				list = Base.slice(list);
+			if (Array.isArray(item)) {
+				list[i] = new Path({ segments: item, insert: false });
+			} else if (item instanceof CompoundPath) {
+				list.splice.apply(list, [i, 1].concat(item.removeChildren()));
+				item.remove();
+			}
+		}
+		return insertChildren.base.call(this, index, list);
+	},
+
+	reduce: function reduce(options) {
 		var children = this._children;
-		for (var i = 0, l = children.length; i < l; i++)
-			children[i].reverse();
-	},
-
-	smooth: function() {
-		for (var i = 0, l = this._children.length; i < l; i++)
-			this._children[i].smooth();
-	},
-
-	reduce: function reduce() {
-		if (this._children.length === 0) {
+		for (var i = children.length - 1; i >= 0; i--) {
+			var path = children[i].reduce(options);
+			if (path.isEmpty())
+				path.remove();
+		}
+		if (!children.length) {
 			var path = new Path(Item.NO_INSERT);
+			path.copyAttributes(this);
 			path.insertAbove(this);
-			path.setStyle(this._style);
 			this.remove();
 			return path;
-		} else {
-			return reduce.base.call(this);
-		}
-	},
-
-	isClockwise: function() {
-		var child = this.getFirstChild();
-		return child && child.isClockwise();
-	},
-
-	setClockwise: function(clockwise) {
-		if (this.isClockwise() !== !!clockwise)
-			this.reverse();
+		}
+		return reduce.base.call(this);
+	},
+
+	isClosed: function() {
+		var children = this._children;
+		for (var i = 0, l = children.length; i < l; i++) {
+			if (!children[i]._closed)
+				return false;
+		}
+		return true;
+	},
+
+	setClosed: function(closed) {
+		var children = this._children;
+		for (var i = 0, l = children.length; i < l; i++) {
+			children[i].setClosed(closed);
+		}
 	},
 
 	getFirstSegment: function() {
@@ -8482,8 +9898,9 @@
 	getCurves: function() {
 		var children = this._children,
 			curves = [];
-		for (var i = 0, l = children.length; i < l; i++)
-			curves.push.apply(curves, children[i].getCurves());
+		for (var i = 0, l = children.length; i < l; i++) {
+			Base.push(curves, children[i].getCurves());
+		}
 		return curves;
 	},
 
@@ -8494,7 +9911,7 @@
 
 	getLastCurve: function() {
 		var last = this.getLastChild();
-		return last && last.getFirstCurve();
+		return last && last.getLastCurve();
 	},
 
 	getArea: function() {
@@ -8503,9 +9920,15 @@
 		for (var i = 0, l = children.length; i < l; i++)
 			area += children[i].getArea();
 		return area;
-	}
-}, {
-	beans: true,
+	},
+
+	getLength: function() {
+		var children = this._children,
+			length = 0;
+		for (var i = 0, l = children.length; i < l; i++)
+			length += children[i].getLength();
+		return length;
+	},
 
 	getPathData: function(_matrix, _precision) {
 		var children = this._children,
@@ -8514,37 +9937,33 @@
 			var child = children[i],
 				mx = child._matrix;
 			paths.push(child.getPathData(_matrix && !mx.isIdentity()
-					? _matrix.chain(mx) : mx, _precision));
-		}
-		return paths.join(' ');
-	}
-}, {
-	_getChildHitTestOptions: function(options) {
-		return options.class === Path || options.type === 'path'
-				? options
-				: new Base(options, { fill: false });
-	},
-
-	_draw: function(ctx, param, strokeMatrix) {
+					? _matrix.appended(mx) : _matrix, _precision));
+		}
+		return paths.join('');
+	},
+
+	_hitTestChildren: function _hitTestChildren(point, options, viewMatrix) {
+		return _hitTestChildren.base.call(this, point,
+				options.class === Path || options.type === 'path' ? options
+					: Base.set({}, options, { fill: false }),
+				viewMatrix);
+	},
+
+	_draw: function(ctx, param, viewMatrix, strokeMatrix) {
 		var children = this._children;
-		if (children.length === 0)
+		if (!children.length)
 			return;
 
-		if (this._currentPath) {
-			ctx.currentPath = this._currentPath;
-		} else {
-			param = param.extend({ dontStart: true, dontFinish: true });
-			ctx.beginPath();
-			for (var i = 0, l = children.length; i < l; i++)
-				children[i].draw(ctx, param, strokeMatrix);
-			this._currentPath = ctx.currentPath;
-		}
+		param = param.extend({ dontStart: true, dontFinish: true });
+		ctx.beginPath();
+		for (var i = 0, l = children.length; i < l; i++)
+			children[i].draw(ctx, param, strokeMatrix);
 
 		if (!param.clip) {
-			this._setStyles(ctx);
+			this._setStyles(ctx, param, viewMatrix);
 			var style = this._style;
 			if (style.hasFill()) {
-				ctx.fill(style.getWindingRule());
+				ctx.fill(style.getFillRule());
 				ctx.shadowColor = 'rgba(0,0,0,0)';
 			}
 			if (style.hasStroke())
@@ -8552,553 +9971,906 @@
 		}
 	},
 
-	_drawSelected: function(ctx, matrix, selectedItems) {
+	_drawSelected: function(ctx, matrix, selectionItems) {
 		var children = this._children;
 		for (var i = 0, l = children.length; i < l; i++) {
 			var child = children[i],
 				mx = child._matrix;
-			if (!selectedItems[child._id])
+			if (!selectionItems[child._id]) {
 				child._drawSelected(ctx, mx.isIdentity() ? matrix
-						: matrix.chain(mx));
-		}
-	}
-}, new function() {
+						: matrix.appended(mx));
+			}
+		}
+	}
+},
+new function() {
 	function getCurrentPath(that, check) {
 		var children = that._children;
-		if (check && children.length === 0)
+		if (check && !children.length)
 			throw new Error('Use a moveTo() command first');
 		return children[children.length - 1];
 	}
 
-	var fields = {
-		moveTo: function() {
-			var current = getCurrentPath(this),
-				path = current && current.isEmpty() ? current : new Path();
-			if (path !== current)
-				this.addChild(path);
-			path.moveTo.apply(path, arguments);
-		},
-
-		moveBy: function() {
-			var current = getCurrentPath(this, true),
-				last = current && current.getLastSegment(),
-				point = Point.read(arguments);
-			this.moveTo(last ? point.add(last._point) : point);
-		},
-
-		closePath: function(join) {
-			getCurrentPath(this, true).closePath(join);
-		}
+	return Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo',
+			'arcTo', 'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy',
+			'arcBy'],
+		function(key) {
+			this[key] = function() {
+				var path = getCurrentPath(this, true);
+				path[key].apply(path, arguments);
+			};
+		}, {
+			moveTo: function() {
+				var current = getCurrentPath(this),
+					path = current && current.isEmpty() ? current
+							: new Path(Item.NO_INSERT);
+				if (path !== current)
+					this.addChild(path);
+				path.moveTo.apply(path, arguments);
+			},
+
+			moveBy: function() {
+				var current = getCurrentPath(this, true),
+					last = current && current.getLastSegment(),
+					point = Point.read(arguments);
+				this.moveTo(last ? point.add(last._point) : point);
+			},
+
+			closePath: function(tolerance) {
+				getCurrentPath(this, true).closePath(tolerance);
+			}
+		}
+	);
+}, Base.each(['reverse', 'flatten', 'simplify', 'smooth'], function(key) {
+	this[key] = function(param) {
+		var children = this._children,
+			res;
+		for (var i = 0, l = children.length; i < l; i++) {
+			res = children[i][key](param) || res;
+		}
+		return res;
 	};
-
-	Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo', 'arcTo',
-			'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy', 'arcBy'],
-			function(key) {
-				fields[key] = function() {
-					var path = getCurrentPath(this, true);
-					path[key].apply(path, arguments);
-				};
-			}
-	);
-
-	return fields;
-});
+}, {}));
 
 PathItem.inject(new function() {
-	var operators = {
-		unite: function(w) {
-			return w === 1 || w === 0;
-		},
-
-		intersect: function(w) {
-			return w === 2;
-		},
-
-		subtract: function(w) {
-			return w === 1;
-		},
-
-		exclude: function(w) {
-			return w === 1;
-		}
-	};
-
-	function computeBoolean(path1, path2, operation) {
-		var operator = operators[operation];
-		function preparePath(path) {
-			return path.clone(false).reduce().reorient().transform(null, true,
-					true);
-		}
-
-		var _path1 = preparePath(path1),
-			_path2 = path2 && path1 !== path2 && preparePath(path2);
-		if (_path2 && /^(subtract|exclude)$/.test(operation)
-				^ (_path2.isClockwise() !== _path1.isClockwise()))
+	var min = Math.min,
+		max = Math.max,
+		abs = Math.abs,
+		operators = {
+			unite:     { '1': true, '2': true },
+			intersect: { '2': true },
+			subtract:  { '1': true },
+			exclude:   { '1': true, '-1': true }
+		};
+
+	function preparePath(path, resolve) {
+		var res = path.clone(false).reduce({ simplify: true })
+				.transform(null, true, true);
+		return resolve
+				? res.resolveCrossings().reorient(
+					res.getFillRule() === 'nonzero', true)
+				: res;
+	}
+
+	function createResult(paths, simplify, path1, path2, options) {
+		var result = new CompoundPath(Item.NO_INSERT);
+		result.addChildren(paths, true);
+		result = result.reduce({ simplify: simplify });
+		if (!(options && options.insert == false)) {
+			result.insertAbove(path2 && path1.isSibling(path2)
+					&& path1.getIndex() < path2.getIndex() ? path2 : path1);
+		}
+		result.copyAttributes(path1, true);
+		return result;
+	}
+
+	function traceBoolean(path1, path2, operation, options) {
+		if (options && (options.trace == false || options.stroke) &&
+				/^(subtract|intersect)$/.test(operation))
+			return splitBoolean(path1, path2, operation);
+		var _path1 = preparePath(path1, true),
+			_path2 = path2 && path1 !== path2 && preparePath(path2, true),
+			operator = operators[operation];
+		operator[operation] = true;
+		if (_path2 && (operator.subtract || operator.exclude)
+				^ (_path2.isClockwise() ^ _path1.isClockwise()))
 			_path2.reverse();
-		splitPath(_path1.getIntersections(_path2, null, true));
-
-		var chain = [],
+		var crossings = divideLocations(
+				CurveLocation.expand(_path1.getCrossings(_path2))),
+			paths1 = _path1._children || [_path1],
+			paths2 = _path2 && (_path2._children || [_path2]),
 			segments = [],
-			monoCurves = [],
-			tolerance = 0.000001;
+			curves = [],
+			paths;
 
 		function collect(paths) {
 			for (var i = 0, l = paths.length; i < l; i++) {
 				var path = paths[i];
-				segments.push.apply(segments, path._segments);
-				monoCurves.push.apply(monoCurves, path._getMonoCurves());
-			}
-		}
-
-		collect(_path1._children || [_path1]);
-		if (_path2)
-			collect(_path2._children || [_path2]);
-		segments.sort(function(a, b) {
-			var _a = a._intersection,
-				_b = b._intersection;
-			return !_a && !_b || _a && _b ? 0 : _a ? -1 : 1;
-		});
-		for (var i = 0, l = segments.length; i < l; i++) {
-			var segment = segments[i];
-			if (segment._winding != null)
-				continue;
-			chain.length = 0;
-			var startSeg = segment,
-				totalLength = 0,
-				windingSum = 0;
-			do {
-				var length = segment.getCurve().getLength();
-				chain.push({ segment: segment, length: length });
-				totalLength += length;
-				segment = segment.getNext();
-			} while (segment && !segment._intersection && segment !== startSeg);
-			for (var j = 0; j < 3; j++) {
-				var length = totalLength * (j + 1) / 4;
-				for (var k = 0, m = chain.length; k < m; k++) {
-					var node = chain[k],
-						curveLength = node.length;
-					if (length <= curveLength) {
-						if (length < tolerance
-								|| curveLength - length < tolerance)
-							length = curveLength / 2;
-						var curve = node.segment.getCurve(),
-							pt = curve.getPointAt(length),
-							hor = curve.isLinear() && Math.abs(curve
-									.getTangentAt(0.5, true).y) < tolerance,
-							path = curve._path;
-						if (path._parent instanceof CompoundPath)
-							path = path._parent;
-						windingSum += operation === 'subtract' && _path2
-							&& (path === _path1 && _path2._getWinding(pt, hor)
-							|| path === _path2 && !_path1._getWinding(pt, hor))
-							? 0
-							: getWinding(pt, monoCurves, hor);
+				Base.push(segments, path._segments);
+				Base.push(curves, path.getCurves());
+				path._overlapsOnly = true;
+			}
+		}
+
+		if (crossings.length) {
+			collect(paths1);
+			if (paths2)
+				collect(paths2);
+			for (var i = 0, l = crossings.length; i < l; i++) {
+				propagateWinding(crossings[i]._segment, _path1, _path2, curves,
+						operator);
+			}
+			for (var i = 0, l = segments.length; i < l; i++) {
+				var segment = segments[i],
+					inter = segment._intersection;
+				if (!segment._winding) {
+					propagateWinding(segment, _path1, _path2, curves, operator);
+				}
+				if (!(inter && inter._overlap))
+					segment._path._overlapsOnly = false;
+			}
+			paths = tracePaths(segments, operator);
+		} else {
+			paths = reorientPaths(
+					paths2 ? paths1.concat(paths2) : paths1.slice(),
+					function(w) {
+						return !!operator[w];
+					});
+		}
+
+		return createResult(paths, true, path1, path2, options);
+	}
+
+	function splitBoolean(path1, path2, operation) {
+		var _path1 = preparePath(path1),
+			_path2 = preparePath(path2),
+			crossings = _path1.getCrossings(_path2),
+			subtract = operation === 'subtract',
+			divide = operation === 'divide',
+			added = {},
+			paths = [];
+
+		function addPath(path) {
+			if (!added[path._id] && (divide ||
+					_path2.contains(path.getPointAt(path.getLength() / 2))
+						^ subtract)) {
+				paths.unshift(path);
+				return added[path._id] = true;
+			}
+		}
+
+		for (var i = crossings.length - 1; i >= 0; i--) {
+			var path = crossings[i].split();
+			if (path) {
+				if (addPath(path))
+					path.getFirstSegment().setHandleIn(0, 0);
+				_path1.getLastSegment().setHandleOut(0, 0);
+			}
+		}
+		addPath(_path1);
+		return createResult(paths, false, path1, path2);
+	}
+
+	function linkIntersections(from, to) {
+		var prev = from;
+		while (prev) {
+			if (prev === to)
+				return;
+			prev = prev._previous;
+		}
+		while (from._next && from._next !== to)
+			from = from._next;
+		if (!from._next) {
+			while (to._previous)
+				to = to._previous;
+			from._next = to;
+			to._previous = from;
+		}
+	}
+
+	function clearCurveHandles(curves) {
+		for (var i = curves.length - 1; i >= 0; i--)
+			curves[i].clearHandles();
+	}
+
+	function reorientPaths(paths, isInside, clockwise) {
+		var length = paths && paths.length;
+		if (length) {
+			var lookup = Base.each(paths, function (path, i) {
+					this[path._id] = {
+						container: null,
+						winding: path.isClockwise() ? 1 : -1,
+						index: i
+					};
+				}, {}),
+				sorted = paths.slice().sort(function (a, b) {
+					return abs(b.getArea()) - abs(a.getArea());
+				}),
+				first = sorted[0];
+			if (clockwise == null)
+				clockwise = first.isClockwise();
+			for (var i = 0; i < length; i++) {
+				var path1 = sorted[i],
+					entry1 = lookup[path1._id],
+					point = path1.getInteriorPoint(),
+					containerWinding = 0;
+				for (var j = i - 1; j >= 0; j--) {
+					var path2 = sorted[j];
+					if (path2.contains(point)) {
+						var entry2 = lookup[path2._id];
+						containerWinding = entry2.winding;
+						entry1.winding += containerWinding;
+						entry1.container = entry2.exclude ? entry2.container
+								: path2;
 						break;
 					}
-					length -= curveLength;
-				}
-			}
-			var winding = Math.round(windingSum / 3);
-			for (var j = chain.length - 1; j >= 0; j--)
-				chain[j].segment._winding = winding;
-		}
-		var result = new CompoundPath(Item.NO_INSERT);
-		result.insertAbove(path1);
-		result.addChildren(tracePaths(segments, operator), true);
-		result = result.reduce();
-		result.setStyle(path1._style);
-		return result;
-	}
-
-	function splitPath(intersections) {
-		var tMin = 0.000001,
+				}
+				if (isInside(entry1.winding) === isInside(containerWinding)) {
+					entry1.exclude = true;
+					paths[entry1.index] = null;
+				} else {
+					var container = entry1.container;
+					path1.setClockwise(container ? !container.isClockwise()
+							: clockwise);
+				}
+			}
+		}
+		return paths;
+	}
+
+	function divideLocations(locations, include, clearLater) {
+		var results = include && [],
+			tMin = 1e-8,
 			tMax = 1 - tMin,
-			linearHandles;
-
-		function resetLinear() {
-			for (var i = 0, l = linearHandles.length; i < l; i++)
-				linearHandles[i].set(0, 0);
-		}
-
-		for (var i = intersections.length - 1, curve, prev; i >= 0; i--) {
-			var loc = intersections[i],
-				t = loc._parameter;
-			if (prev && prev._curve === loc._curve && prev._parameter > 0) {
-				t /= prev._parameter;
-			} else {
-				curve = loc._curve;
-				if (linearHandles)
-					resetLinear();
-				linearHandles = curve.isLinear() ? [
-						curve._segment1._handleOut,
-						curve._segment2._handleIn
-					] : null;
-			}
-			var newCurve,
+			clearHandles = false,
+			clearCurves = clearLater || [],
+			clearLookup = clearLater && {},
+			renormalizeLocs,
+			prevCurve,
+			prevTime;
+
+		function getId(curve) {
+			return curve._path._id + '.' + curve._segment1._index;
+		}
+
+		for (var i = (clearLater && clearLater.length) - 1; i >= 0; i--) {
+			var curve = clearLater[i];
+			if (curve._path)
+				clearLookup[getId(curve)] = true;
+		}
+
+		for (var i = locations.length - 1; i >= 0; i--) {
+			var loc = locations[i],
+				time = loc._time,
+				origTime = time,
+				exclude = include && !include(loc),
+				curve = loc._curve,
 				segment;
-			if (newCurve = curve.divide(t, true, true)) {
+			if (curve) {
+				if (curve !== prevCurve) {
+					clearHandles = !curve.hasHandles()
+							|| clearLookup && clearLookup[getId(curve)];
+					renormalizeLocs = [];
+					prevTime = null;
+					prevCurve = curve;
+				} else if (prevTime >= tMin) {
+					time /= prevTime;
+				}
+			}
+			if (exclude) {
+				if (renormalizeLocs)
+					renormalizeLocs.push(loc);
+				continue;
+			} else if (include) {
+				results.unshift(loc);
+			}
+			prevTime = origTime;
+			if (time < tMin) {
+				segment = curve._segment1;
+			} else if (time > tMax) {
+				segment = curve._segment2;
+			} else {
+				var newCurve = curve.divideAtTime(time, true);
+				if (clearHandles)
+					clearCurves.push(curve, newCurve);
 				segment = newCurve._segment1;
-				curve = newCurve.getPrevious();
-				if (linearHandles)
-					linearHandles.push(segment._handleOut, segment._handleIn);
-			} else {
-				segment = t < tMin
-					? curve._segment1
-					: t > tMax
-						? curve._segment2
-						: curve.getPartLength(0, t) < curve.getPartLength(t, 1)
-							? curve._segment1
-							: curve._segment2;
-			}
-			segment._intersection = loc.getIntersection();
-			loc._segment = segment;
-			prev = loc;
-		}
-		if (linearHandles)
-			resetLinear();
-	}
-
-	function getWinding(point, curves, horizontal, testContains) {
-		var tolerance = 0.000001,
-			tMin = tolerance,
-			tMax = 1 - tMin,
-			px = point.x,
-			py = point.y,
-			windLeft = 0,
-			windRight = 0,
+				for (var j = renormalizeLocs.length - 1; j >= 0; j--) {
+					var l = renormalizeLocs[j];
+					l._time = (l._time - time) / (1 - time);
+				}
+			}
+			loc._setSegment(segment);
+			var inter = segment._intersection,
+				dest = loc._intersection;
+			if (inter) {
+				linkIntersections(inter, dest);
+				var other = inter;
+				while (other) {
+					linkIntersections(other._intersection, inter);
+					other = other._next;
+				}
+			} else {
+				segment._intersection = dest;
+			}
+		}
+		if (!clearLater)
+			clearCurveHandles(clearCurves);
+		return results || locations;
+	}
+
+	function getWinding(point, curves, dir, closed, dontFlip) {
+		var ia = dir ? 1 : 0,
+			io = ia ^ 1,
+			pv = [point.x, point.y],
+			pa = pv[ia],
+			po = pv[io],
+			windingEpsilon = 1e-9,
+			qualityEpsilon = 1e-6,
+			paL = pa - windingEpsilon,
+			paR = pa + windingEpsilon,
+			windingL = 0,
+			windingR = 0,
+			pathWindingL = 0,
+			pathWindingR = 0,
+			onPath = false,
+			onAnyPath = false,
+			quality = 1,
 			roots = [],
-			abs = Math.abs;
-		if (horizontal) {
-			var yTop = -Infinity,
-				yBottom = Infinity,
-				yBefore = py - tolerance,
-				yAfter = py + tolerance;
-			for (var i = 0, l = curves.length; i < l; i++) {
-				var values = curves[i].values;
-				if (Curve.solveCubic(values, 0, px, roots, 0, 1) > 0) {
-					for (var j = roots.length - 1; j >= 0; j--) {
-						var y = Curve.getPoint(values, roots[j]).y;
-						if (y < yBefore && y > yTop) {
-							yTop = y;
-						} else if (y > yAfter && y < yBottom) {
-							yBottom = y;
-						}
-					}
-				}
-			}
-			yTop = (yTop + py) / 2;
-			yBottom = (yBottom + py) / 2;
-			if (yTop > -Infinity)
-				windLeft = getWinding(new Point(px, yTop), curves);
-			if (yBottom < Infinity)
-				windRight = getWinding(new Point(px, yBottom), curves);
-		} else {
-			var xBefore = px - tolerance,
-				xAfter = px + tolerance;
-			var startCounted = false,
-				prevCurve,
-				prevT;
-			for (var i = 0, l = curves.length; i < l; i++) {
-				var curve = curves[i],
-					values = curve.values,
-					winding = curve.winding;
-				if (winding && (winding === 1
-						&& py >= values[1] && py <= values[7]
-						|| py >= values[7] && py <= values[1])
-					&& Curve.solveCubic(values, 1, py, roots, 0, 1) === 1) {
-					var t = roots[0];
-					if (!(
-						t > tMax && startCounted && curve.next !== curves[i + 1]
-						|| t < tMin && prevT > tMax
-							&& curve.previous === prevCurve)) {
-						var x = Curve.getPoint(values, t).x,
-							slope = Curve.getTangent(values, t).y,
-							counted = false;
-						if (Numerical.isZero(slope) && !Curve.isLinear(values)
-								|| t < tMin && slope * Curve.getTangent(
-									curve.previous.values, 1).y < 0
-								|| t > tMax && slope * Curve.getTangent(
-									curve.next.values, 0).y < 0) {
-							if (testContains && x >= xBefore && x <= xAfter) {
-								++windLeft;
-								++windRight;
-								counted = true;
-							}
-						} else if (x <= xBefore) {
-							windLeft += winding;
-							counted = true;
-						} else if (x >= xAfter) {
-							windRight += winding;
-							counted = true;
+			vPrev,
+			vClose;
+
+		function addWinding(v) {
+			var o0 = v[io + 0],
+				o3 = v[io + 6];
+			if (po < min(o0, o3) || po > max(o0, o3)) {
+				return;
+			}
+			var a0 = v[ia + 0],
+				a1 = v[ia + 2],
+				a2 = v[ia + 4],
+				a3 = v[ia + 6];
+			if (o0 === o3) {
+				if (a0 < paR && a3 > paL || a3 < paR && a0 > paL) {
+					onPath = true;
+				}
+				return;
+			}
+			var t =   po === o0 ? 0
+					: po === o3 ? 1
+					: paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3)
+					? 1
+					: Curve.solveCubic(v, io, po, roots, 0, 1) > 0
+						? roots[0]
+						: 1,
+				a =   t === 0 ? a0
+					: t === 1 ? a3
+					: Curve.getPoint(v, t)[dir ? 'y' : 'x'],
+				winding = o0 > o3 ? 1 : -1,
+				windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1,
+				a3Prev = vPrev[ia + 6];
+			if (po !== o0) {
+				if (a < paL) {
+					pathWindingL += winding;
+				} else if (a > paR) {
+					pathWindingR += winding;
+				} else {
+					onPath = true;
+				}
+				if (a > pa - qualityEpsilon && a < pa + qualityEpsilon)
+					quality /= 2;
+			} else {
+				if (winding !== windingPrev) {
+					if (a0 < paL) {
+						pathWindingL += winding;
+					} else if (a0 > paR) {
+						pathWindingR += winding;
+					}
+				} else if (a0 != a3Prev) {
+					if (a3Prev < paR && a > paR) {
+						pathWindingR += winding;
+						onPath = true;
+					} else if (a3Prev > paL && a < paL) {
+						pathWindingL += winding;
+						onPath = true;
+					}
+				}
+				quality = 0;
+			}
+			vPrev = v;
+			return !dontFlip && a > paL && a < paR
+					&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
+					&& getWinding(point, curves, !dir, closed, true);
+		}
+
+		function handleCurve(v) {
+			var o0 = v[io + 0],
+				o1 = v[io + 2],
+				o2 = v[io + 4],
+				o3 = v[io + 6];
+			if (po <= max(o0, o1, o2, o3) && po >= min(o0, o1, o2, o3)) {
+				var a0 = v[ia + 0],
+					a1 = v[ia + 2],
+					a2 = v[ia + 4],
+					a3 = v[ia + 6],
+					monoCurves = paL > max(a0, a1, a2, a3) ||
+								 paR < min(a0, a1, a2, a3)
+							? [v] : Curve.getMonoCurves(v, dir),
+					res;
+				for (var i = 0, l = monoCurves.length; i < l; i++) {
+					if (res = addWinding(monoCurves[i]))
+						return res;
+				}
+			}
+		}
+
+		for (var i = 0, l = curves.length; i < l; i++) {
+			var curve = curves[i],
+				path = curve._path,
+				v = curve.getValues(),
+				res;
+			if (!i || curves[i - 1]._path !== path) {
+				vPrev = null;
+				if (!path._closed) {
+					vClose = Curve.getValues(
+							path.getLastCurve().getSegment2(),
+							curve.getSegment1(),
+							null, !closed);
+					if (vClose[io] !== vClose[io + 6]) {
+						vPrev = vClose;
+					}
+				}
+
+				if (!vPrev) {
+					vPrev = v;
+					var prev = path.getLastCurve();
+					while (prev && prev !== curve) {
+						var v2 = prev.getValues();
+						if (v2[io] !== v2[io + 6]) {
+							vPrev = v2;
+							break;
 						}
-						if (curve.previous !== curves[i - 1])
-							startCounted = t < tMin && counted;
-					}
-					prevCurve = curve;
-					prevT = t;
-				}
-			}
-		}
-		return Math.max(abs(windLeft), abs(windRight));
-	}
-
-	function tracePaths(segments, operator, selfOp) {
-		var paths = [],
-			tMin = 0.000001,
+						prev = prev.getPrevious();
+					}
+				}
+			}
+
+			if (res = handleCurve(v))
+				return res;
+
+			if (i + 1 === l || curves[i + 1]._path !== path) {
+				if (vClose && (res = handleCurve(vClose)))
+					return res;
+				if (onPath && !pathWindingL && !pathWindingR) {
+					pathWindingL = pathWindingR = path.isClockwise(closed) ^ dir
+							? 1 : -1;
+				}
+				windingL += pathWindingL;
+				windingR += pathWindingR;
+				pathWindingL = pathWindingR = 0;
+				if (onPath) {
+					onAnyPath = true;
+					onPath = false;
+				}
+				vClose = null;
+			}
+		}
+		windingL = abs(windingL);
+		windingR = abs(windingR);
+		return {
+			winding: max(windingL, windingR),
+			windingL: windingL,
+			windingR: windingR,
+			quality: quality,
+			onPath: onAnyPath
+		};
+	}
+
+	function propagateWinding(segment, path1, path2, curves, operator) {
+		var chain = [],
+			start = segment,
+			totalLength = 0,
+			winding;
+		do {
+			var curve = segment.getCurve(),
+				length = curve.getLength();
+			chain.push({ segment: segment, curve: curve, length: length });
+			totalLength += length;
+			segment = segment.getNext();
+		} while (segment && !segment._intersection && segment !== start);
+		var offsets = [0.5, 0.25, 0.75],
+			winding = { winding: 0, quality: -1 },
+			tMin = 1e-8,
 			tMax = 1 - tMin;
-		for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
-			seg = startSeg = segments[i];
-			if (seg._visited || !operator(seg._winding))
-				continue;
-			var path = new Path(Item.NO_INSERT),
-				inter = seg._intersection,
-				startInterSeg = inter && inter._segment,
-				added = false,
-				dir = 1;
-			do {
-				var handleIn = dir > 0 ? seg._handleIn : seg._handleOut,
-					handleOut = dir > 0 ? seg._handleOut : seg._handleIn,
-					interSeg;
-				if (added && (!operator(seg._winding) || selfOp)
-						&& (inter = seg._intersection)
-						&& (interSeg = inter._segment)
-						&& interSeg !== startSeg) {
-					if (selfOp) {
-						seg._visited = interSeg._visited;
-						seg = interSeg;
-						dir = 1;
-					} else {
-						var c1 = seg.getCurve();
-						if (dir > 0)
-							c1 = c1.getPrevious();
-						var t1 = c1.getTangentAt(dir < 1 ? tMin : tMax, true),
-							c4 = interSeg.getCurve(),
-							c3 = c4.getPrevious(),
-							t3 = c3.getTangentAt(tMax, true),
-							t4 = c4.getTangentAt(tMin, true),
-							w3 = t1.cross(t3),
-							w4 = t1.cross(t4);
-						if (w3 * w4 !== 0) {
-							var curve = w3 < w4 ? c3 : c4,
-								nextCurve = operator(curve._segment1._winding)
-									? curve
-									: w3 < w4 ? c4 : c3,
-								nextSeg = nextCurve._segment1;
-							dir = nextCurve === c3 ? -1 : 1;
-							if (nextSeg._visited && seg._path !== nextSeg._path
-										|| !operator(nextSeg._winding)) {
-								dir = 1;
+		for (var i = 0; i < offsets.length && winding.quality < 0.5; i++) {
+			var length = totalLength * offsets[i];
+			for (var j = 0, l = chain.length; j < l; j++) {
+				var entry = chain[j],
+					curveLength = entry.length;
+				if (length <= curveLength) {
+					var curve = entry.curve,
+						path = curve._path,
+						parent = path._parent,
+						operand = parent instanceof CompoundPath ? parent : path,
+						t = Numerical.clamp(curve.getTimeAt(length), tMin, tMax),
+						pt = curve.getPointAtTime(t),
+						dir = abs(curve.getTangentAtTime(t).y) < Math.SQRT1_2;
+					var wind = null;
+					if (operator.subtract && path2) {
+						var pathWinding = operand === path1
+										  ? path2._getWinding(pt, dir, true)
+										  : path1._getWinding(pt, dir, true);
+						if (operand === path1 && pathWinding.winding ||
+							operand === path2 && !pathWinding.winding) {
+							if (pathWinding.quality < 1) {
+								continue;
 							} else {
-								seg._visited = interSeg._visited;
-								seg = interSeg;
-								if (nextSeg._visited)
-									dir = 1;
+								wind = { winding: 0, quality: 1 };
 							}
-						} else {
-							dir = 1;
+						}
+					}
+					wind = wind || getWinding(pt, curves, dir, true);
+					if (wind.quality > winding.quality)
+						winding = wind;
+					break;
+				}
+				length -= curveLength;
+			}
+		}
+		for (var j = chain.length - 1; j >= 0; j--) {
+			chain[j].segment._winding = winding;
+		}
+	}
+
+	function tracePaths(segments, operator) {
+		var paths = [],
+			starts;
+
+		function isValid(seg) {
+			var winding;
+			return !!(seg && !seg._visited && (!operator
+					|| operator[(winding = seg._winding || {}).winding]
+						&& !(operator.unite && winding.winding === 2
+							&& winding.windingL && winding.windingR)));
+		}
+
+		function isStart(seg) {
+			if (seg) {
+				for (var i = 0, l = starts.length; i < l; i++) {
+					if (seg === starts[i])
+						return true;
+				}
+			}
+			return false;
+		}
+
+		function visitPath(path) {
+			var segments = path._segments;
+			for (var i = 0, l = segments.length; i < l; i++) {
+				segments[i]._visited = true;
+			}
+		}
+
+		function getCrossingSegments(segment, collectStarts) {
+			var inter = segment._intersection,
+				start = inter,
+				crossings = [];
+			if (collectStarts)
+				starts = [segment];
+
+			function collect(inter, end) {
+				while (inter && inter !== end) {
+					var other = inter._segment,
+						path = other && other._path;
+					if (path) {
+						var next = other.getNext() || path.getFirstSegment(),
+							nextInter = next._intersection;
+						if (other !== segment && (isStart(other)
+							|| isStart(next)
+							|| next && (isValid(other) && (isValid(next)
+								|| nextInter && isValid(nextInter._segment))))
+						) {
+							crossings.push(other);
 						}
-					}
-					handleOut = dir > 0 ? seg._handleOut : seg._handleIn;
-				}
-				path.add(new Segment(seg._point, added && handleIn, handleOut));
-				added = true;
+						if (collectStarts)
+							starts.push(other);
+					}
+					inter = inter._next;
+				}
+			}
+
+			if (inter) {
+				collect(inter);
+				while (inter && inter._prev)
+					inter = inter._prev;
+				collect(inter, start);
+			}
+			return crossings;
+		}
+
+		segments.sort(function(seg1, seg2) {
+			var inter1 = seg1._intersection,
+				inter2 = seg2._intersection,
+				over1 = !!(inter1 && inter1._overlap),
+				over2 = !!(inter2 && inter2._overlap),
+				path1 = seg1._path,
+				path2 = seg2._path;
+			return over1 ^ over2
+					? over1 ? 1 : -1
+					: !inter1 ^ !inter2
+						? inter1 ? 1 : -1
+						: path1 !== path2
+							? path1._id - path2._id
+							: seg1._index - seg2._index;
+		});
+
+		for (var i = 0, l = segments.length; i < l; i++) {
+			var seg = segments[i],
+				valid = isValid(seg),
+				path = null,
+				finished = false,
+				closed = true,
+				branches = [],
+				branch,
+				visited,
+				handleIn;
+			if (valid && seg._path._overlapsOnly) {
+				var path1 = seg._path,
+					path2 = seg._intersection._segment._path;
+				if (path1.compare(path2)) {
+					if (path1.getArea())
+						paths.push(path1.clone(false));
+					visitPath(path1);
+					visitPath(path2);
+					valid = false;
+				}
+			}
+			while (valid) {
+				var first = !path,
+					crossings = getCrossingSegments(seg, first),
+					other = crossings.shift(),
+					finished = !first && (isStart(seg) || isStart(other)),
+					cross = !finished && other;
+				if (first) {
+					path = new Path(Item.NO_INSERT);
+					branch = null;
+				}
+				if (finished) {
+					if (seg.isFirst() || seg.isLast())
+						closed = seg._path._closed;
+					seg._visited = true;
+					break;
+				}
+				if (cross && branch) {
+					branches.push(branch);
+					branch = null;
+				}
+				if (!branch) {
+					if (cross)
+						crossings.push(seg);
+					branch = {
+						start: path._segments.length,
+						crossings: crossings,
+						visited: visited = [],
+						handleIn: handleIn
+					};
+				}
+				if (cross)
+					seg = other;
+				if (!isValid(seg)) {
+					path.removeSegments(branch.start);
+					for (var j = 0, k = visited.length; j < k; j++) {
+						visited[j]._visited = false;
+					}
+					visited.length = 0;
+					do {
+						seg = branch && branch.crossings.shift();
+						if (!seg || !seg._path) {
+							seg = null;
+							branch = branches.pop();
+							if (branch) {
+								visited = branch.visited;
+								handleIn = branch.handleIn;
+							}
+						}
+					} while (branch && !isValid(seg));
+					if (!seg)
+						break;
+				}
+				var next = seg.getNext();
+				path.add(new Segment(seg._point, handleIn,
+						next && seg._handleOut));
 				seg._visited = true;
-				seg = dir > 0 ? seg.getNext() : seg. getPrevious();
-			} while (seg && !seg._visited
-					&& seg !== startSeg && seg !== startInterSeg
-					&& (seg._intersection || operator(seg._winding)));
-			if (seg && (seg === startSeg || seg === startInterSeg)) {
-				path.firstSegment.setHandleIn((seg === startInterSeg
-						? startInterSeg : seg)._handleIn);
-				path.setClosed(true);
-			} else {
-				path.lastSegment._handleOut.set(0, 0);
-			}
-			if (path._segments.length >
-					(path._closed ? path.isLinear() ? 2 : 0 : 1))
-				paths.push(path);
+				visited.push(seg);
+				seg = next || seg._path.getFirstSegment();
+				handleIn = next && next._handleIn;
+			}
+			if (finished) {
+				if (closed) {
+					path.getFirstSegment().setHandleIn(handleIn);
+					path.setClosed(closed);
+				}
+				if (path.getArea() !== 0) {
+					paths.push(path);
+				}
+			}
 		}
 		return paths;
 	}
 
 	return {
-		_getWinding: function(point, horizontal, testContains) {
-			return getWinding(point, this._getMonoCurves(),
-					horizontal, testContains);
-		},
-
-		unite: function(path) {
-			return computeBoolean(this, path, 'unite');
-		},
-
-		intersect: function(path) {
-			return computeBoolean(this, path, 'intersect');
-		},
-
-		subtract: function(path) {
-			return computeBoolean(this, path, 'subtract');
-		},
-
-		exclude: function(path) {
-			return computeBoolean(this, path, 'exclude');
-		},
-
-		divide: function(path) {
-			return new Group([this.subtract(path), this.intersect(path)]);
+		_getWinding: function(point, dir, closed) {
+			return getWinding(point, this.getCurves(), dir, closed);
+		},
+
+		unite: function(path, options) {
+			return traceBoolean(this, path, 'unite', options);
+		},
+
+		intersect: function(path, options) {
+			return traceBoolean(this, path, 'intersect', options);
+		},
+
+		subtract: function(path, options) {
+			return traceBoolean(this, path, 'subtract', options);
+		},
+
+		exclude: function(path, options) {
+			return traceBoolean(this, path, 'exclude', options);
+		},
+
+		divide: function(path, options) {
+			return options && (options.trace == false || options.stroke)
+					? splitBoolean(this, path, 'divide')
+					: createResult([
+						this.subtract(path, options),
+						this.intersect(path, options)
+					], true, this, path, options);
+		},
+
+		resolveCrossings: function() {
+			var children = this._children,
+				paths = children || [this];
+
+			function hasOverlap(seg, path) {
+				var inter = seg && seg._intersection;
+				return inter && inter._overlap && inter._path === path;
+			}
+
+			var hasOverlaps = false,
+				hasCrossings = false,
+				intersections = this.getIntersections(null, function(inter) {
+					return inter.hasOverlap() && (hasOverlaps = true) ||
+							inter.isCrossing() && (hasCrossings = true);
+				}),
+				clearCurves = hasOverlaps && hasCrossings && [];
+			intersections = CurveLocation.expand(intersections);
+			if (hasOverlaps) {
+				var overlaps = divideLocations(intersections, function(inter) {
+					return inter.hasOverlap();
+				}, clearCurves);
+				for (var i = overlaps.length - 1; i >= 0; i--) {
+					var overlap = overlaps[i],
+						path = overlap._path,
+						seg = overlap._segment,
+						prev = seg.getPrevious(),
+						next = seg.getNext();
+					if (hasOverlap(prev, path) && hasOverlap(next, path)) {
+						seg.remove();
+						prev._handleOut._set(0, 0);
+						next._handleIn._set(0, 0);
+						if (prev !== seg && !prev.getCurve().hasLength()) {
+							next._handleIn.set(prev._handleIn);
+							prev.remove();
+						}
+					}
+				}
+			}
+			if (hasCrossings) {
+				divideLocations(intersections, hasOverlaps && function(inter) {
+					var curve1 = inter.getCurve(),
+						seg1 = inter.getSegment(),
+						other = inter._intersection,
+						curve2 = other._curve,
+						seg2 = other._segment;
+					if (curve1 && curve2 && curve1._path && curve2._path)
+						return true;
+					if (seg1)
+						seg1._intersection = null;
+					if (seg2)
+						seg2._intersection = null;
+				}, clearCurves);
+				if (clearCurves)
+					clearCurveHandles(clearCurves);
+				paths = tracePaths(Base.each(paths, function(path) {
+					Base.push(this, path._segments);
+				}, []));
+			}
+			var length = paths.length,
+				item;
+			if (length > 1 && children) {
+				if (paths !== children)
+					this.setChildren(paths);
+				item = this;
+			} else if (length === 1 && !children) {
+				if (paths[0] !== this)
+					this.setSegments(paths[0].removeSegments());
+				item = this;
+			}
+			if (!item) {
+				item = new CompoundPath(Item.NO_INSERT);
+				item.addChildren(paths);
+				item = item.reduce();
+				item.copyAttributes(this);
+				this.replaceWith(item);
+			}
+			return item;
+		},
+
+		reorient: function(nonZero, clockwise) {
+			var children = this._children;
+			if (children && children.length) {
+				this.setChildren(reorientPaths(this.removeChildren(),
+						function(w) {
+							return !!(nonZero ? w : w & 1);
+						},
+						clockwise));
+			} else if (clockwise !== undefined) {
+				this.setClockwise(clockwise);
+			}
+			return this;
+		},
+
+		getInteriorPoint: function() {
+			var bounds = this.getBounds(),
+				point = bounds.getCenter(true);
+			if (!this.contains(point)) {
+				var curves = this.getCurves(),
+					y = point.y,
+					intercepts = [],
+					roots = [];
+				for (var i = 0, l = curves.length; i < l; i++) {
+					var v = curves[i].getValues(),
+						o0 = v[1],
+						o1 = v[3],
+						o2 = v[5],
+						o3 = v[7];
+					if (y >= min(o0, o1, o2, o3) && y <= max(o0, o1, o2, o3)) {
+						var monoCurves = Curve.getMonoCurves(v);
+						for (var j = 0, m = monoCurves.length; j < m; j++) {
+							var mv = monoCurves[j],
+								mo0 = mv[1],
+								mo3 = mv[7];
+							if ((mo0 !== mo3) &&
+								(y >= mo0 && y <= mo3 || y >= mo3 && y <= mo0)){
+								var x = y === mo0 ? mv[0]
+									: y === mo3 ? mv[6]
+									: Curve.solveCubic(mv, 1, y, roots, 0, 1)
+										=== 1
+										? Curve.getPoint(mv, roots[0]).x
+										: (mv[0] + mv[6]) / 2;
+								intercepts.push(x);
+							}
+						}
+					}
+				}
+				if (intercepts.length > 1) {
+					intercepts.sort(function(a, b) { return a - b; });
+					point.x = (intercepts[0] + intercepts[1]) / 2;
+				}
+			}
+			return point;
 		}
 	};
 });
 
-Path.inject({
-	_getMonoCurves: function() {
-		var monoCurves = this._monoCurves,
-			prevCurve;
-
-		function insertCurve(v) {
-			var y0 = v[1],
-				y1 = v[7],
-				curve = {
-					values: v,
-					winding: y0 === y1
-						? 0
-						: y0 > y1
-							? -1
-							: 1,
-					previous: prevCurve,
-					next: null
-				};
-			if (prevCurve)
-				prevCurve.next = curve;
-			monoCurves.push(curve);
-			prevCurve = curve;
-		}
-
-		function handleCurve(v) {
-			if (Curve.getLength(v) === 0)
-				return;
-			var y0 = v[1],
-				y1 = v[3],
-				y2 = v[5],
-				y3 = v[7];
-			if (Curve.isLinear(v)) {
-				insertCurve(v);
-			} else {
-				var a = 3 * (y1 - y2) - y0 + y3,
-					b = 2 * (y0 + y2) - 4 * y1,
-					c = y1 - y0,
-					tolerance = 0.000001,
-					roots = [];
-				var count = Numerical.solveQuadratic(a, b, c, roots, tolerance,
-						1 - tolerance);
-				if (count === 0) {
-					insertCurve(v);
-				} else {
-					roots.sort();
-					var t = roots[0],
-						parts = Curve.subdivide(v, t);
-					insertCurve(parts[0]);
-					if (count > 1) {
-						t = (roots[1] - t) / (1 - t);
-						parts = Curve.subdivide(parts[1], t);
-						insertCurve(parts[0]);
-					}
-					insertCurve(parts[1]);
-				}
-			}
-		}
-
-		if (!monoCurves) {
-			monoCurves = this._monoCurves = [];
-			var curves = this.getCurves(),
-				segments = this._segments;
-			for (var i = 0, l = curves.length; i < l; i++)
-				handleCurve(curves[i].getValues());
-			if (!this._closed && segments.length > 1) {
-				var p1 = segments[segments.length - 1]._point,
-					p2 = segments[0]._point,
-					p1x = p1._x, p1y = p1._y,
-					p2x = p2._x, p2y = p2._y;
-				handleCurve([p1x, p1y, p1x, p1y, p2x, p2y, p2x, p2y]);
-			}
-			if (monoCurves.length > 0) {
-				var first = monoCurves[0],
-					last = monoCurves[monoCurves.length - 1];
-				first.previous = last;
-				last.next = first;
-			}
-		}
-		return monoCurves;
-	},
-
-	getInteriorPoint: function() {
-		var bounds = this.getBounds(),
-			point = bounds.getCenter(true);
-		if (!this.contains(point)) {
-			var curves = this._getMonoCurves(),
-				roots = [],
-				y = point.y,
-				xIntercepts = [];
-			for (var i = 0, l = curves.length; i < l; i++) {
-				var values = curves[i].values;
-				if ((curves[i].winding === 1
-						&& y >= values[1] && y <= values[7]
-						|| y >= values[7] && y <= values[1])
-						&& Curve.solveCubic(values, 1, y, roots, 0, 1) > 0) {
-					for (var j = roots.length - 1; j >= 0; j--)
-						xIntercepts.push(Curve.getPoint(values, roots[j]).x);
-				}
-				if (xIntercepts.length > 1)
-					break;
-			}
-			point.x = (xIntercepts[0] + xIntercepts[1]) / 2;
-		}
-		return point;
-	},
-
-	reorient: function() {
-		this.setClockwise(true);
-		return this;
-	}
-});
-
-CompoundPath.inject({
-	_getMonoCurves: function() {
-		var children = this._children,
-			monoCurves = [];
-		for (var i = 0, l = children.length; i < l; i++)
-			monoCurves.push.apply(monoCurves, children[i]._getMonoCurves());
-		return monoCurves;
-	},
-
-	reorient: function() {
-		var children = this.removeChildren().sort(function(a, b) {
-			return b.getBounds().getArea() - a.getBounds().getArea();
-		});
-		if (children.length > 0) {
-			this.addChildren(children);
-			var clockwise = children[0].isClockwise();
-			for (var i = 1, l = children.length; i < l; i++) {
-				var point = children[i].getInteriorPoint(),
-					counters = 0;
-				for (var j = i - 1; j >= 0; j--) {
-					if (children[j].contains(point))
-						counters++;
-				}
-				children[i].setClockwise(counters % 2 === 0 && clockwise);
-			}
-		}
-		return this;
-	}
-});
-
-var PathIterator = Base.extend({
-	_class: 'PathIterator',
-
-	initialize: function(path, maxRecursion, tolerance, matrix) {
+var PathFlattener = Base.extend({
+	_class: 'PathFlattener',
+
+	initialize: function(path, flatness, maxRecursion, ignoreStraight, matrix) {
 		var curves = [],
 			parts = [],
 			length = 0,
-			minDifference = 1 / (maxRecursion || 32),
+			minSpan = 1 / (maxRecursion || 32),
 			segments = path._segments,
 			segment1 = segments[0],
 			segment2;
@@ -9109,23 +10881,25 @@
 			computeParts(curve, segment1._index, 0, 1);
 		}
 
-		function computeParts(curve, index, minT, maxT) {
-			if ((maxT - minT) > minDifference
-					&& !Curve.isFlatEnough(curve, tolerance || 0.25)) {
-				var split = Curve.subdivide(curve),
-					halfT = (minT + maxT) / 2;
-				computeParts(split[0], index, minT, halfT);
-				computeParts(split[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 > 0.000001) {
+		function computeParts(curve, index, t1, t2) {
+			if ((t2 - t1) > minSpan
+					&& !(ignoreStraight && Curve.isStraight(curve))
+					&& !Curve.isFlatEnough(curve, flatness || 0.25)) {
+				var halves = Curve.subdivide(curve, 0.5),
+					tMid = (t1 + t2) / 2;
+				computeParts(halves[0], index, t1, tMid);
+				computeParts(halves[1], index, tMid, t2);
+			} else {
+				var dx = curve[6] - curve[0],
+					dy = curve[7] - curve[1],
+					dist = Math.sqrt(dx * dx + dy * dy);
+				if (dist > 0) {
 					length += dist;
 					parts.push({
 						offset: length,
-						value: maxT,
-						index: index
+						curve: curve,
+						index: index,
+						time: t2,
 					});
 				}
 			}
@@ -9137,121 +10911,120 @@
 			segment1 = segment2;
 		}
 		if (path._closed)
-			addCurve(segment2, segments[0]);
-
+			addCurve(segment2 || segment1, segments[0]);
 		this.curves = curves;
 		this.parts = parts;
 		this.length = length;
 		this.index = 0;
 	},
 
-	getParameterAt: function(offset) {
-		var i, j = this.index;
+	_get: function(offset) {
+		var parts = this.parts,
+			length = parts.length,
+			start,
+			i, j = this.index;
 		for (;;) {
 			i = j;
-			if (j == 0 || this.parts[--j].offset < offset)
+			if (!j || parts[--j].offset < offset)
 				break;
 		}
-		for (var l = this.parts.length; i < l; i++) {
-			var part = this.parts[i];
+		for (; i < length; i++) {
+			var part = 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;
+				var prev = parts[i - 1],
+					prevTime = prev && prev.index === part.index ? prev.time : 0,
+					prevOffset = prev ? prev.offset : 0;
 				return {
-					value: prevVal + (part.value - prevVal)
-						* (offset - prevLen) / (part.offset - prevLen),
-					index: part.index
+					index: part.index,
+					time: prevTime + (part.time - prevTime)
+						* (offset - prevOffset) / (part.offset - prevOffset)
 				};
 			}
 		}
-		var part = this.parts[this.parts.length - 1];
 		return {
-			value: 1,
-			index: part.index
+			index: parts[length - 1].index,
+			time: 1
 		};
 	},
 
 	drawPart: function(ctx, from, to) {
-		from = this.getParameterAt(from);
-		to = this.getParameterAt(to);
-		for (var i = from.index; i <= to.index; i++) {
+		var start = this._get(from),
+			end = this._get(to);
+		for (var i = start.index, l = end.index; i <= l; i++) {
 			var curve = Curve.getPart(this.curves[i],
-					i == from.index ? from.value : 0,
-					i == to.index ? to.value : 1);
-			if (i == from.index)
+					i === start.index ? start.time : 0,
+					i === end.index ? end.time : 1);
+			if (i === start.index)
 				ctx.moveTo(curve[0], curve[1]);
 			ctx.bezierCurveTo.apply(ctx, curve.slice(2));
 		}
 	}
-}, Base.each(Curve.evaluateMethods,
+}, Base.each(Curve._evaluateMethods,
 	function(name) {
-		this[name + 'At'] = function(offset, weighted) {
-			var param = this.getParameterAt(offset);
-			return Curve[name](this.curves[param.index], param.value, weighted);
+		this[name + 'At'] = function(offset) {
+			var param = this._get(offset);
+			return Curve[name](this.curves[param.index], param.time);
 		};
 	}, {})
 );
 
 var PathFitter = Base.extend({
-	initialize: function(path, error) {
+	initialize: function(path) {
 		var points = this.points = [],
 			segments = path._segments,
-			prev;
-		for (var i = 0, l = segments.length; i < l; i++) {
-			var point = segments[i].point.clone();
+			closed = path._closed;
+		for (var i = 0, prev, l = segments.length; i < l; i++) {
+			var point = segments[i].point;
 			if (!prev || !prev.equals(point)) {
-				points.push(point);
-				prev = point;
-			}
-		}
-
-		if (path._closed) {
-			this.closed = true;
+				points.push(prev = point.clone());
+			}
+		}
+		if (closed) {
 			points.unshift(points[points.length - 1]);
 			points.push(points[1]);
 		}
-
-		this.error = error;
-	},
-
-	fit: function() {
+		this.closed = closed;
+	},
+
+	fit: function(error) {
 		var points = this.points,
 			length = points.length,
-			segments = this.segments = length > 0
-					? [new Segment(points[0])] : [];
-		if (length > 1)
-			this.fitCubic(0, length - 1,
-				points[1].subtract(points[0]).normalize(),
-				points[length - 2].subtract(points[length - 1]).normalize());
-
-		if (this.closed) {
-			segments.shift();
-			segments.pop();
-		}
-
+			segments = null;
+		if (length > 0) {
+			segments = [new Segment(points[0])];
+			if (length > 1) {
+				this.fitCubic(segments, error, 0, length - 1,
+						points[1].subtract(points[0]),
+						points[length - 2].subtract(points[length - 1]));
+				if (this.closed) {
+					segments.shift();
+					segments.pop();
+				}
+			}
+		}
 		return segments;
 	},
 
-	fitCubic: function(first, last, tan1, tan2) {
-		if (last - first == 1) {
-			var pt1 = this.points[first],
-				pt2 = this.points[last],
+	fitCubic: function(segments, error, first, last, tan1, tan2) {
+		var points = this.points;
+		if (last - first === 1) {
+			var pt1 = points[first],
+				pt2 = points[last],
 				dist = pt1.getDistance(pt2) / 3;
-			this.addCurve([pt1, pt1.add(tan1.normalize(dist)),
+			this.addCurve(segments, [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),
+			maxError = Math.max(error, error * error),
 			split,
 			parametersInOrder = true;
 		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 && parametersInOrder) {
-				this.addCurve(curve);
+			if (max.error < error && parametersInOrder) {
+				this.addCurve(segments, curve);
 				return;
 			}
 			split = max.index;
@@ -9260,24 +11033,23 @@
 			parametersInOrder = 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];
+		var tanCenter = points[split - 1].subtract(points[split + 1]);
+		this.fitCubic(segments, error, first, split, tan1, tanCenter);
+		this.fitCubic(segments, error, split, last, tanCenter.negate(), tan2);
+	},
+
+	addCurve: function(segments, curve) {
+		var prev = segments[segments.length - 1];
 		prev.setHandleOut(curve[1].subtract(curve[0]));
-		this.segments.push(
-				new Segment(curve[3], curve[2].subtract(curve[3])));
+		segments.push(new Segment(curve[3], curve[2].subtract(curve[3])));
 	},
 
 	generateBezier: function(first, last, uPrime, tan1, tan2) {
 		var epsilon = 1e-12,
-			pt1 = this.points[first],
-			pt2 = this.points[last],
+			abs = Math.abs,
+			points = this.points,
+			pt1 = points[first],
+			pt2 = points[last],
 			C = [[0, 0], [0, 0]],
 			X = [0, 0];
 
@@ -9291,7 +11063,7 @@
 				b3 = u * u * u,
 				a1 = tan1.normalize(b1),
 				a2 = tan2.normalize(b2),
-				tmp = this.points[first + i]
+				tmp = points[first + i]
 					.subtract(pt1.multiply(b0 + b1))
 					.subtract(pt2.multiply(b2 + b3));
 			C[0][0] += a1.dot(a1);
@@ -9303,22 +11075,19 @@
 		}
 
 		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,
+			alpha2;
+		if (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;
-			}
+			alpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0
+							: abs(c1) > epsilon ? X[1] / c1
+							: 0;
 		}
 
 		var segLength = pt2.getDistance(pt1),
@@ -9337,8 +11106,10 @@
 			}
 		}
 
-		return [pt1, pt1.add(handle1 || tan1.normalize(alpha1)),
-				pt2.add(handle2 || tan2.normalize(alpha2)), pt2];
+		return [pt1,
+				pt1.add(handle1 || tan1.normalize(alpha1)),
+				pt2.add(handle2 || tan2.normalize(alpha2)),
+				pt2];
 	},
 
 	reparameterize: function(first, last, u, curve) {
@@ -9366,9 +11137,7 @@
 			pt2 = this.evaluate(1, curve2, u),
 			diff = pt.subtract(point),
 			df = pt1.dot(pt1) + diff.dot(pt2);
-		if (Math.abs(df) < 0.000001)
-			return u;
-		return u - diff.dot(pt1) / df;
+		return Numerical.isZero(df) ? u : u - diff.dot(pt1) / df;
 	},
 
 	evaluate: function(degree, curve, t) {
@@ -9414,13 +11183,12 @@
 
 var TextItem = Item.extend({
 	_class: 'TextItem',
-	_boundsSelected: true,
 	_applyMatrix: false,
 	_canApplyMatrix: false,
 	_serializeFields: {
 		content: null
 	},
-	_boundsGetter: 'getBounds',
+	_boundsOptions: { stroke: false, handle: false },
 
 	initialize: function TextItem(arg) {
 		this._content = '';
@@ -9434,9 +11202,8 @@
 		return this._content === item._content;
 	},
 
-	_clone: function _clone(copy, insert, includeMatrix) {
-		copy.setContent(this._content);
-		return _clone.base.call(this, copy, insert, includeMatrix);
+	copyContent: function(source) {
+		this.setContent(source._content);
 	},
 
 	getContent: function() {
@@ -9446,7 +11213,7 @@
 	setContent: function(content) {
 		this._content = '' + content;
 		this._lines = this._content.split(/\r\n|\n|\r/mg);
-		this._changed(265);
+		this._changed(521);
 	},
 
 	isEmpty: function() {
@@ -9467,10 +11234,6 @@
 		TextItem.apply(this, arguments);
 	},
 
-	clone: function(insert) {
-		return this._clone(new PointText(Item.NO_INSERT), insert);
-	},
-
 	getPoint: function() {
 		var point = this._matrix.getTranslation();
 		return new LinkedPoint(point.x, point.y, this, 'setPoint');
@@ -9481,12 +11244,14 @@
 		this.translate(point.subtract(this._matrix.getTranslation()));
 	},
 
-	_draw: function(ctx) {
+	_draw: function(ctx, param, viewMatrix) {
 		if (!this._content)
 			return;
-		this._setStyles(ctx);
-		var style = this._style,
-			lines = this._lines,
+		this._setStyles(ctx, param, viewMatrix);
+		var lines = this._lines,
+			style = this._style,
+			hasFill = style.hasFill(),
+			hasStroke = style.hasStroke(),
 			leading = style.getLeading(),
 			shadowColor = ctx.shadowColor;
 		ctx.font = style.getFontStyle();
@@ -9494,17 +11259,17 @@
 		for (var i = 0, l = lines.length; i < l; i++) {
 			ctx.shadowColor = shadowColor;
 			var line = lines[i];
-			if (style.hasFill()) {
+			if (hasFill) {
 				ctx.fillText(line, 0, 0);
 				ctx.shadowColor = 'rgba(0,0,0,0)';
 			}
-			if (style.hasStroke())
+			if (hasStroke)
 				ctx.strokeText(line, 0, 0);
 			ctx.translate(0, leading);
 		}
 	},
 
-	_getBounds: function(getter, matrix) {
+	_getBounds: function(matrix, options) {
 		var style = this._style,
 			lines = this._lines,
 			numLines = lines.length,
@@ -9514,10 +11279,10 @@
 			x = 0;
 		if (justification !== 'left')
 			x -= width / (justification === 'center' ? 2: 1);
-		var bounds = new Rectangle(x,
+		var rect = new Rectangle(x,
 					numLines ? - 0.75 * leading : 0,
 					width, numLines * leading);
-		return matrix ? matrix._transformBounds(bounds, bounds) : bounds;
+		return matrix ? matrix._transformBounds(rect, rect) : rect;
 	}
 });
 
@@ -9531,45 +11296,74 @@
 	};
 
 	var componentParsers = {},
-		colorCache = {},
+		namedColors = {
+			transparent: [0, 0, 0, 0]
+		},
 		colorCtx;
 
 	function fromCSS(string) {
-		var match = string.match(/^#(\w{1,2})(\w{1,2})(\w{1,2})$/),
+		var match = string.match(
+				/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})([\da-f]{2})?$/i
+			) || string.match(
+				/^#([\da-f])([\da-f])([\da-f])([\da-f])?$/i
+			),
+			type = 'rgb',
 			components;
 		if (match) {
-			components = [0, 0, 0];
-			for (var i = 0; i < 3; i++) {
+			var amount = match[4] ? 4 : 3;
+			components = new Array(amount);
+			for (var i = 0; i < amount; i++) {
 				var value = match[i + 1];
 				components[i] = parseInt(value.length == 1
 						? value + value : value, 16) / 255;
 			}
-		} else if (match = string.match(/^rgba?\((.*)\)$/)) {
-			components = match[1].split(',');
-			for (var i = 0, l = components.length; i < l; i++) {
-				var value = +components[i];
-				components[i] = i < 3 ? value / 255 : value;
+		} else if (match = string.match(/^(rgb|hsl)a?\((.*)\)$/)) {
+			type = match[1];
+			components = match[2].trim().split(/[,\s]+/g);
+			var isHSL = type === 'hsl';
+			for (var i = 0, l = Math.min(components.length, 4); i < l; i++) {
+				var component = components[i];
+				var value = parseFloat(component);
+				if (isHSL) {
+					if (i === 0) {
+						var unit = component.match(/([a-z]*)$/)[1];
+						value *= ({
+							turn: 360,
+							rad: 180 / Math.PI,
+							grad: 0.9
+						}[unit] || 1);
+					} else if (i < 3) {
+						value /= 100;
+					}
+				} else if (i < 3) {
+					value /= 255;
+				}
+				components[i] = value;
 			}
 		} else {
-			var cached = colorCache[string];
-			if (!cached) {
-				if (!colorCtx) {
-					colorCtx = CanvasProvider.getContext(1, 1);
-					colorCtx.globalCompositeOperation = 'copy';
-				}
-				colorCtx.fillStyle = 'rgba(0,0,0,0)';
-				colorCtx.fillStyle = string;
-				colorCtx.fillRect(0, 0, 1, 1);
-				var data = colorCtx.getImageData(0, 0, 1, 1).data;
-				cached = colorCache[string] = [
-					data[0] / 255,
-					data[1] / 255,
-					data[2] / 255
-				];
-			}
-			components = cached.slice();
-		}
-		return components;
+			var color = namedColors[string];
+			if (!color) {
+				if (window) {
+					if (!colorCtx) {
+						colorCtx = CanvasProvider.getContext(1, 1);
+						colorCtx.globalCompositeOperation = 'copy';
+					}
+					colorCtx.fillStyle = 'rgba(0,0,0,0)';
+					colorCtx.fillStyle = string;
+					colorCtx.fillRect(0, 0, 1, 1);
+					var data = colorCtx.getImageData(0, 0, 1, 1).data;
+					color = namedColors[string] = [
+						data[0] / 255,
+						data[1] / 255,
+						data[2] / 255
+					];
+				} else {
+					color = [0, 0, 0];
+				}
+			}
+			components = color.slice();
+		}
+		return [type, components];
 	}
 
 	var hsbIndices = [
@@ -9587,9 +11381,9 @@
 				min = Math.min(r, g, b),
 				delta = max - min,
 				h = delta === 0 ? 0
-					:	( max == r ? (g - b) / delta + (g < b ? 6 : 0)
+					:   ( max == r ? (g - b) / delta + (g < b ? 6 : 0)
 						: max == g ? (b - r) / delta + 2
-						:			 (r - g) / delta + 4) * 60;
+						:            (r - g) / delta + 4) * 60;
 			return [h, max === 0 ? 0 : delta / max, max];
 		},
 
@@ -9613,9 +11407,9 @@
 				delta = max - min,
 				achromatic = delta === 0,
 				h = achromatic ? 0
-					:	( max == r ? (g - b) / delta + (g < b ? 6 : 0)
+					:   ( max == r ? (g - b) / delta + (g < b ? 6 : 0)
 						: max == g ? (b - r) / delta + 2
-						:			 (r - g) / delta + 4) * 60,
+						:            (r - g) / delta + 4) * 60,
 				l = (max + min) / 2,
 				s = achromatic ? 0 : l < 0.5
 						? delta / (max + min)
@@ -9677,30 +11471,32 @@
 		Base.each(properties, function(name, index) {
 			var part = Base.capitalize(name),
 				hasOverlap = /^(hue|saturation)$/.test(name),
-				parser = componentParsers[type][index] = name === 'gradient'
-					? function(value) {
-						var current = this._components[0];
-						value = Gradient.read(Array.isArray(value) ? value
-								: arguments, 0, { readNull: true });
-						if (current !== value) {
-							if (current)
-								current._removeOwner(this);
-							if (value)
-								value._addOwner(this);
+				parser = componentParsers[type][index] = type === 'gradient'
+					? name === 'gradient'
+						? function(value) {
+							var current = this._components[0];
+							value = Gradient.read(
+								Array.isArray(value)
+									? value
+									: arguments, 0, { readNull: true }
+							);
+							if (current !== value) {
+								if (current)
+									current._removeOwner(this);
+								if (value)
+									value._addOwner(this);
+							}
+							return value;
 						}
-						return value;
-					}
-					: type === 'gradient'
-						? function() {
+						: function() {
 							return Point.read(arguments, 0, {
 									readNull: name === 'highlight',
 									clone: true
 							});
 						}
-						: function(value) {
-							return value == null || isNaN(value) ? 0 : value;
-						};
-
+					: function(value) {
+						return value == null || isNaN(value) ? 0 : +value;
+					};
 			this['get' + part] = function() {
 				return this._type === type
 					|| hasOverlap && /^hs[bl]$/.test(this._type)
@@ -9724,8 +11520,8 @@
 		_readIndex: true,
 
 		initialize: function Color(arg) {
-			var slice = Array.prototype.slice,
-				args = arguments,
+			var args = arguments,
+				reading = this.__read,
 				read = 0,
 				type,
 				components,
@@ -9743,9 +11539,9 @@
 					components = arg;
 					alpha = args[2];
 				} else {
-					if (this.__read)
+					if (reading)
 						read = 1;
-					args = slice.call(args, 1);
+					args = Base.slice(args, 1);
 					argType = typeof arg;
 				}
 			}
@@ -9762,15 +11558,17 @@
 								: 'gray';
 					var length = types[type].length;
 					alpha = values[length];
-					if (this.__read)
+					if (reading) {
 						read += values === arguments
 							? length + (alpha != null ? 1 : 0)
 							: 1;
+					}
 					if (values.length > length)
-						values = slice.call(values, 0, length);
+						values = Base.slice(values, 0, length);
 				} else if (argType === 'string') {
-					type = 'rgb';
-					components = fromCSS(arg);
+					var converted = fromCSS(arg);
+					type = converted[0];
+					components = converted[1];
 					if (components.length === 4) {
 						alpha = components[3];
 						components.length--;
@@ -9801,12 +11599,12 @@
 								: 'gray' in arg
 									? 'gray'
 									: 'rgb';
-						var properties = types[type];
+						var properties = types[type],
 							parsers = componentParsers[type];
 						this._components = components = [];
 						for (var i = 0, l = properties.length; i < l; i++) {
 							var value = arg[properties[i]];
-							if (value == null && i === 0 && type === 'gradient'
+							if (value == null && !i && type === 'gradient'
 									&& 'stops' in arg) {
 								value = {
 									stops: arg.stops,
@@ -9820,11 +11618,10 @@
 						alpha = arg.alpha;
 					}
 				}
-				if (this.__read && type)
+				if (reading && type)
 					read = 1;
 			}
 			this._type = type || 'rgb';
-			this._id = UID.get(Color);
 			if (!components) {
 				this._components = components = [];
 				var parsers = componentParsers[this._type];
@@ -9837,9 +11634,12 @@
 			this._components = components;
 			this._properties = types[this._type];
 			this._alpha = alpha;
-			if (this.__read)
+			if (reading)
 				this.__read = read;
-		},
+			return this;
+		},
+
+		set: '#initialize',
 
 		_serialize: function(options, dictionary) {
 			var components = this.getComponents();
@@ -9852,8 +11652,13 @@
 
 		_changed: function() {
 			this._canvasStyle = null;
-			if (this._owner)
-				this._owner._changed(65);
+			if (this._owner) {
+				if (this._setter) {
+					this._owner[this._setter](this);
+				} else {
+					this._owner._changed(129);
+				}
+			}
 		},
 
 		_convert: function(type) {
@@ -9907,7 +11712,7 @@
 					: color;
 			return col === this || col && this._class === col._class
 					&& this._type === col._type
-					&& this._alpha === col._alpha
+					&& this.getAlpha() === col.getAlpha()
 					&& Base.equals(this._components, col._components)
 					|| false;
 		},
@@ -9949,7 +11754,7 @@
 						+ components.join(',') + ')';
 		},
 
-		toCanvasStyle: function(ctx) {
+		toCanvasStyle: function(ctx, matrix) {
 			if (this._canvasStyle)
 				return this._canvasStyle;
 			if (this._type !== 'gradient')
@@ -9959,10 +11764,17 @@
 				stops = gradient._stops,
 				origin = components[1],
 				destination = components[2],
+				highlight = components[3],
+				inverse = matrix && matrix.inverted(),
 				canvasGradient;
+			if (inverse) {
+				origin = inverse._transformPoint(origin);
+				destination = inverse._transformPoint(destination);
+				if (highlight)
+					highlight = inverse._transformPoint(highlight);
+			}
 			if (gradient._radial) {
-				var radius = destination.getDistance(origin),
-					highlight = components[3];
+				var radius = destination.getDistance(origin);
 				if (highlight) {
 					var vector = highlight.subtract(origin);
 					if (vector.getLength() > radius)
@@ -9976,8 +11788,10 @@
 						destination.x, destination.y);
 			}
 			for (var i = 0, l = stops.length; i < l; i++) {
-				var stop = stops[i];
-				canvasGradient.addColorStop(stop._rampPoint,
+				var stop = stops[i],
+					offset = stop._offset;
+				canvasGradient.addColorStop(
+						offset == null ? i / (l - 1) : offset,
 						stop._color.toCanvasStyle());
 			}
 			return this._canvasStyle = canvasGradient;
@@ -10000,10 +11814,24 @@
 			random: function() {
 				var random = Math.random;
 				return new Color(random(), random(), random());
+			},
+
+			_setOwner: function(color, owner, setter) {
+				if (color) {
+					if (color._owner && owner && color._owner !== owner) {
+						color = color.clone();
+					}
+					if (!color._owner ^ !owner) {
+						color._owner = owner || null;
+						color._setter = setter || null;
+					}
+				}
+				return color;
 			}
 		}
 	});
-}, new function() {
+},
+new function() {
 	var operators = {
 		add: function(a, b) {
 			return a + b;
@@ -10039,36 +11867,22 @@
 	});
 });
 
-Base.each(Color._types, function(properties, type) {
-	var ctor = this[Base.capitalize(type) + 'Color'] = function(arg) {
-			var argType = arg != null && typeof arg,
-				components = argType === 'object' && arg.length != null
-					? arg
-					: argType === 'string'
-						? null
-						: arguments;
-			return components
-					? new Color(type, components)
-					: new Color(arg);
-		};
-	if (type.length == 3) {
-		var acronym = type.toUpperCase();
-		Color[acronym] = this[acronym + 'Color'] = ctor;
-	}
-}, Base.exports);
-
 var Gradient = Base.extend({
 	_class: 'Gradient',
 
 	initialize: function Gradient(stops, radial) {
 		this._id = UID.get();
-		if (stops && this._set(stops))
+		if (stops && Base.isPlainObject(stops)) {
+			this.set(stops);
 			stops = radial = null;
-		if (!this._stops)
+		}
+		if (this._stops == null) {
 			this.setStops(stops || ['white', 'black']);
-		if (this._radial == null)
+		}
+		if (this._radial == null) {
 			this.setRadial(typeof radial === 'string' && radial === 'radial'
 					|| radial || false);
+		}
 	},
 
 	_serialize: function(options, dictionary) {
@@ -10079,8 +11893,9 @@
 	},
 
 	_changed: function() {
-		for (var i = 0, l = this._owners && this._owners.length; i < l; i++)
+		for (var i = 0, l = this._owners && this._owners.length; i < l; i++) {
 			this._owners[i]._changed();
+		}
 	},
 
 	_addOwner: function(color) {
@@ -10093,15 +11908,16 @@
 		var index = this._owners ? this._owners.indexOf(color) : -1;
 		if (index != -1) {
 			this._owners.splice(index, 1);
-			if (this._owners.length === 0)
+			if (!this._owners.length)
 				this._owners = undefined;
 		}
 	},
 
 	clone: function() {
 		var stops = [];
-		for (var i = 0, l = this._stops.length; i < l; i++)
+		for (var i = 0, l = this._stops.length; i < l; i++) {
 			stops[i] = this._stops[i].clone();
+		}
 		return new Gradient(stops, this._radial);
 	},
 
@@ -10110,20 +11926,18 @@
 	},
 
 	setStops: function(stops) {
-		if (this.stops) {
-			for (var i = 0, l = this._stops.length; i < l; i++)
-				this._stops[i]._owner = undefined;
-		}
-		if (stops.length < 2)
+		if (stops.length < 2) {
 			throw new Error(
 					'Gradient stop list needs to contain at least two stops.');
-		this._stops = GradientStop.readAll(stops, 0, { clone: true });
-		for (var i = 0, l = this._stops.length; i < l; i++) {
-			var stop = this._stops[i];
-			stop._owner = this;
-			if (stop._defaultRamp)
-				stop.setRampPoint(i / (l - 1));
-		}
+		}
+		var _stops = this._stops;
+		if (_stops) {
+			for (var i = 0, l = _stops.length; i < l; i++)
+				_stops[i]._owner = undefined;
+		}
+		_stops = this._stops = GradientStop.readList(stops, 0, { clone: true });
+		for (var i = 0, l = _stops.length; i < l; i++)
+			_stops[i]._owner = this;
 		this._changed();
 	},
 
@@ -10139,13 +11953,17 @@
 	equals: function(gradient) {
 		if (gradient === this)
 			return true;
-		if (gradient && this._class === gradient._class
-				&& 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;
+		if (gradient && this._class === gradient._class) {
+			var stops1 = this._stops,
+				stops2 = gradient._stops,
+				length = stops1.length;
+			if (length === stops2.length) {
+				for (var i = 0; i < length; i++) {
+					if (!stops1[i].equals(stops2[i]))
+						return false;
+				}
+				return true;
+			}
 		}
 		return false;
 	}
@@ -10155,71 +11973,74 @@
 	_class: 'GradientStop',
 
 	initialize: function GradientStop(arg0, arg1) {
-		if (arg0) {
-			var color, rampPoint;
-			if (arg1 === undefined && Array.isArray(arg0)) {
+		var color = arg0,
+			offset = arg1;
+		if (typeof arg0 === 'object' && arg1 === undefined) {
+			if (Array.isArray(arg0) && typeof arg0[0] !== 'number') {
 				color = arg0[0];
-				rampPoint = arg0[1];
-			} else if (arg0.color) {
+				offset = arg0[1];
+			} else if ('color' in arg0 || 'offset' in arg0
+					|| 'rampPoint' in arg0) {
 				color = arg0.color;
-				rampPoint = arg0.rampPoint;
-			} else {
-				color = arg0;
-				rampPoint = arg1;
-			}
-			this.setColor(color);
-			this.setRampPoint(rampPoint);
-		}
+				offset = arg0.offset || arg0.rampPoint || 0;
+			}
+		}
+		this.setColor(color);
+		this.setOffset(offset);
 	},
 
 	clone: function() {
-		return new GradientStop(this._color.clone(), this._rampPoint);
+		return new GradientStop(this._color.clone(), this._offset);
 	},
 
 	_serialize: function(options, dictionary) {
-		return Base.serialize([this._color, this._rampPoint], options, true,
-				dictionary);
+		var color = this._color,
+			offset = this._offset;
+		return Base.serialize(offset == null ? [color] : [color, offset],
+				options, true, dictionary);
 	},
 
 	_changed: function() {
 		if (this._owner)
-			this._owner._changed(65);
-	},
-
-	getRampPoint: function() {
-		return this._rampPoint;
-	},
-
-	setRampPoint: function(rampPoint) {
-		this._defaultRamp = rampPoint == null;
-		this._rampPoint = rampPoint || 0;
+			this._owner._changed(129);
+	},
+
+	getOffset: function() {
+		return this._offset;
+	},
+
+	setOffset: function(offset) {
+		this._offset = offset;
 		this._changed();
 	},
 
+	getRampPoint: '#getOffset',
+	setRampPoint: '#setOffset',
+
 	getColor: function() {
 		return this._color;
 	},
 
-	setColor: function(color) {
-		this._color = Color.read(arguments);
-		if (this._color === color)
-			this._color = color.clone();
-		this._color._owner = this;
+	setColor: function() {
+		Color._setOwner(this._color, null);
+		this._color = Color._setOwner(Color.read(arguments, 0), this,
+				'setColor');
 		this._changed();
 	},
 
 	equals: function(stop) {
 		return stop === this || stop && this._class === stop._class
 				&& this._color.equals(stop._color)
-				&& this._rampPoint == stop._rampPoint
+				&& this._offset == stop._offset
 				|| false;
 	}
 });
 
 var Style = Base.extend(new function() {
-	var defaults = {
-		fillColor: undefined,
-		strokeColor: undefined,
+	var itemDefaults = {
+		fillColor: null,
+		fillRule: 'nonzero',
+		strokeColor: null,
 		strokeWidth: 1,
 		strokeCap: 'butt',
 		strokeJoin: 'miter',
@@ -10227,43 +12048,55 @@
 		miterLimit: 10,
 		dashOffset: 0,
 		dashArray: [],
-		windingRule: 'nonzero',
-		shadowColor: undefined,
+		shadowColor: null,
 		shadowBlur: 0,
 		shadowOffset: new Point(),
-		selectedColor: undefined,
+		selectedColor: null
+	},
+	groupDefaults = Base.set({}, itemDefaults, {
 		fontFamily: 'sans-serif',
 		fontWeight: 'normal',
 		fontSize: 12,
-		font: 'sans-serif',
 		leading: null,
 		justification: 'left'
-	};
-
-	var flags = {
-		strokeWidth: 97,
-		strokeCap: 97,
-		strokeJoin: 97,
-		strokeScaling: 105,
-		miterLimit: 97,
+	}),
+	textDefaults = Base.set({}, groupDefaults, {
+		fillColor: new Color()
+	}),
+	flags = {
+		strokeWidth: 193,
+		strokeCap: 193,
+		strokeJoin: 193,
+		strokeScaling: 201,
+		miterLimit: 193,
 		fontFamily: 9,
 		fontWeight: 9,
 		fontSize: 9,
 		font: 9,
 		leading: 9,
 		justification: 9
+	},
+	item = {
+		beans: true
+	},
+	fields = {
+		_class: 'Style',
+		beans: true,
+
+		initialize: function Style(style, _owner, _project) {
+			this._values = {};
+			this._owner = _owner;
+			this._project = _owner && _owner._project || _project
+					|| paper.project;
+			this._defaults = !_owner || _owner instanceof Group ? groupDefaults
+					: _owner instanceof TextItem ? textDefaults
+					: itemDefaults;
+			if (style)
+				this.set(style);
+		}
 	};
 
-	var item = { beans: true },
-		fields = {
-			_defaults: defaults,
-			_textDefaults: new Base(defaults, {
-				fillColor: new Color()
-			}),
-			beans: true
-		};
-
-	Base.each(defaults, function(value, key) {
+	Base.each(groupDefaults, function(value, key) {
 		var isColor = /Color$/.test(key),
 			isPoint = key === 'shadowOffset',
 			part = Base.capitalize(key),
@@ -10273,26 +12106,30 @@
 
 		fields[set] = function(value) {
 			var owner = this._owner,
-				children = owner && owner._children;
-			if (children && children.length > 0
-					&& !(owner instanceof CompoundPath)) {
+				children = owner && owner._children,
+				applyToChildren = children && children.length > 0
+					&& !(owner instanceof CompoundPath);
+			if (applyToChildren) {
 				for (var i = 0, l = children.length; i < l; i++)
 					children[i]._style[set](value);
-			} else {
+			}
+			if ((key === 'selectedColor' || !applyToChildren)
+					&& key in this._defaults) {
 				var old = this._values[key];
 				if (old !== value) {
 					if (isColor) {
-						if (old)
-							old._owner = undefined;
+						if (old) {
+							Color._setOwner(old, null);
+							old._canvasStyle = null;
+						}
 						if (value && value.constructor === Color) {
-							if (value._owner)
-								value = value.clone();
-							value._owner = owner;
+							value = Color._setOwner(value, owner,
+									applyToChildren && set);
 						}
 					}
 					this._values[key] = value;
 					if (owner)
-						owner._changed(flag || 65);
+						owner._changed(flag || 129);
 				}
 			}
 		};
@@ -10300,32 +12137,35 @@
 		fields[get] = function(_dontMerge) {
 			var owner = this._owner,
 				children = owner && owner._children,
+				applyToChildren = children && children.length > 0
+					&& !(owner instanceof CompoundPath),
 				value;
-			if (!children || children.length === 0 || _dontMerge
-					|| owner instanceof CompoundPath) {
+			if (applyToChildren && !_dontMerge) {
+				for (var i = 0, l = children.length; i < l; i++) {
+					var childValue = children[i]._style[get]();
+					if (!i) {
+						value = childValue;
+					} else if (!Base.equals(value, childValue)) {
+						return undefined;
+					}
+				}
+			} else if (key in this._defaults) {
 				var value = this._values[key];
 				if (value === undefined) {
 					value = this._defaults[key];
-					if (value && value.clone)
+					if (value && value.clone) {
 						value = value.clone();
+					}
 				} else {
 					var ctor = isColor ? Color : isPoint ? Point : null;
 					if (ctor && !(value && value.constructor === ctor)) {
 						this._values[key] = value = ctor.read([value], 0,
 								{ readNull: true, clone: true });
-						if (value && isColor)
-							value._owner = owner;
-					}
-				}
-				return value;
-			}
-			for (var i = 0, l = children.length; i < l; i++) {
-				var childValue = children[i]._style[get]();
-				if (i === 0) {
-					value = childValue;
-				} else if (!Base.equals(value, childValue)) {
-					return undefined;
-				}
+					}
+				}
+			}
+			if (value && isColor) {
+				value = Color._setOwner(value, owner, applyToChildren && set);
 			}
 			return value;
 		};
@@ -10339,21 +12179,19 @@
 		};
 	});
 
+	Base.each({
+		Font: 'FontFamily',
+		WindingRule: 'FillRule'
+	}, function(value, key) {
+		var get = 'get' + key,
+			set = 'set' + key;
+		fields[get] = item[get] = '#get' + value;
+		fields[set] = item[set] = '#set' + value;
+	});
+
 	Item.inject(item);
 	return fields;
 }, {
-	_class: 'Style',
-
-	initialize: function Style(style, _owner, _project) {
-		this._values = {};
-		this._owner = _owner;
-		this._project = _owner && _owner._project || _project || paper.project;
-		if (_owner instanceof TextItem)
-			this._defaults = this._textDefaults;
-		if (style)
-			this.set(style);
-	},
-
 	set: function(style) {
 		var isStyle = style instanceof Style,
 			values = isStyle ? style._values : style;
@@ -10369,25 +12207,54 @@
 	},
 
 	equals: function(style) {
+		function compare(style1, style2, secondary) {
+			var values1 = style1._values,
+				values2 = style2._values,
+				defaults2 = style2._defaults;
+			for (var key in values1) {
+				var value1 = values1[key],
+					value2 = values2[key];
+				if (!(secondary && key in values2) && !Base.equals(value1,
+						value2 === undefined ? defaults2[key] : value2))
+					return false;
+			}
+			return true;
+		}
+
 		return style === this || style && this._class === style._class
-				&& Base.equals(this._values, style._values)
+				&& compare(this, style)
+				&& compare(style, this, true)
 				|| false;
 	},
 
+	_dispose: function() {
+		var color;
+		color = this.getFillColor();
+		if (color) color._canvasStyle = null;
+		color = this.getStrokeColor();
+		if (color) color._canvasStyle = null;
+		color = this.getShadowColor();
+		if (color) color._canvasStyle = null;
+	},
+
 	hasFill: function() {
-		return !!this.getFillColor();
+		var color = this.getFillColor();
+		return !!color && color.alpha > 0;
 	},
 
 	hasStroke: function() {
-		return !!this.getStrokeColor() && this.getStrokeWidth() > 0;
+		var color = this.getStrokeColor();
+		return !!color && color.alpha > 0 && this.getStrokeWidth() > 0;
 	},
 
 	hasShadow: function() {
-		return !!this.getShadowColor() && this.getShadowBlur() > 0;
+		var color = this.getShadowColor();
+		return !!color && color.alpha > 0 && (this.getShadowBlur() > 0
+				|| !this.getShadowOffset().isZero());
 	},
 
 	getView: function() {
-		return this._project.getView();
+		return this._project._view;
 	},
 
 	getFontStyle: function() {
@@ -10483,8 +12350,12 @@
 						DomElement.getBounds(el, true));
 		},
 
+		isInserted: function(el) {
+			return document.body.contains(el);
+		},
+
 		getPrefixed: function(el, name) {
-			return handlePrefix(el, name);
+			return el && handlePrefix(el, name);
 		},
 
 		setPrefixed: function(el, name, value) {
@@ -10500,20 +12371,30 @@
 
 var DomEvent = {
 	add: function(el, events) {
-		for (var type in events) {
-			var func = events[type],
-				parts = type.split(/[\s,]+/g);
-			for (var i = 0, l = parts.length; i < l; i++)
-				el.addEventListener(parts[i], func, false);
+		if (el) {
+			for (var type in events) {
+				var func = events[type],
+					parts = type.split(/[\s,]+/g);
+				for (var i = 0, l = parts.length; i < l; i++) {
+					var name = parts[i];
+					var options = (
+						el === document
+						&& (name === 'touchstart' || name === 'touchmove')
+					) ? { passive: false } : false;
+					el.addEventListener(name, func, options);
+				}
+			}
 		}
 	},
 
 	remove: function(el, events) {
-		for (var type in events) {
-			var func = events[type],
-				parts = type.split(/[\s,]+/g);
-			for (var i = 0, l = parts.length; i < l; i++)
-				el.removeEventListener(parts[i], func, false);
+		if (el) {
+			for (var type in events) {
+				var func = events[type],
+					parts = type.split(/[\s,]+/g);
+				for (var i = 0, l = parts.length; i < l; i++)
+					el.removeEventListener(parts[i], func, false);
+			}
 		}
 	},
 
@@ -10540,11 +12421,6 @@
 	getOffset: function(event, target) {
 		return DomEvent.getPoint(event).subtract(DomElement.getOffset(
 				target || DomEvent.getTarget(event)));
-	},
-
-	stop: function(event) {
-		event.stopPropagation();
-		event.preventDefault();
 	}
 };
 
@@ -10552,40 +12428,20 @@
 	var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'),
 		requested = false,
 		callbacks = [],
-		focused = true,
 		timer;
 
-	DomEvent.add(window, {
-		focus: function() {
-			focused = true;
-		},
-		blur: function() {
-			focused = false;
-		}
-	});
-
 	function handleCallbacks() {
-		for (var i = callbacks.length - 1; i >= 0; i--) {
-			var entry = callbacks[i],
-				func = entry[0],
-				el = entry[1];
-			if (!el || (PaperScope.getAttribute(el, 'keepalive') == 'true'
-					|| focused) && DomElement.isInView(el)) {
-				callbacks.splice(i, 1);
-				func();
-			}
-		}
-		if (nativeRequest) {
-			if (callbacks.length) {
-				nativeRequest(handleCallbacks);
-			} else {
-				requested = false;
-			}
-		}
-	}
-
-	return function(callback, element) {
-		callbacks.push([callback, element]);
+		var functions = callbacks;
+		callbacks = [];
+		for (var i = 0, l = functions.length; i < l; i++)
+			functions[i]();
+		requested = nativeRequest && callbacks.length;
+		if (requested)
+			nativeRequest(handleCallbacks);
+	}
+
+	return function(callback) {
+		callbacks.push(callback);
 		if (nativeRequest) {
 			if (!requested) {
 				nativeRequest(handleCallbacks);
@@ -10601,66 +12457,76 @@
 	_class: 'View',
 
 	initialize: function View(project, element) {
-		this._project = project;
-		this._scope = project._scope;
-		this._element = element;
-		var size;
-		if (!this._pixelRatio)
-			this._pixelRatio = window.devicePixelRatio || 1;
-		this._id = element.getAttribute('id');
-		if (this._id == null)
-			element.setAttribute('id', this._id = 'view-' + View._id++);
-		DomEvent.add(element, this._viewEvents);
-		var none = 'none';
-		DomElement.setPrefixed(element.style, {
-			userSelect: none,
-			touchAction: none,
-			touchCallout: none,
-			contentZooming: none,
-			userDrag: none,
-			tapHighlightColor: 'rgba(0,0,0,0)'
-		});
 
 		function getSize(name) {
 			return element[name] || parseInt(element.getAttribute(name), 10);
-		};
+		}
 
 		function getCanvasSize() {
 			var size = DomElement.getSize(element);
 			return size.isNaN() || size.isZero()
 					? new Size(getSize('width'), getSize('height'))
 					: size;
-		};
-
-		if (PaperScope.hasAttribute(element, 'resize')) {
-			var that = this;
-			DomEvent.add(window, this._windowEvents = {
-				resize: function() {
-					that.setViewSize(getCanvasSize());
-				}
+		}
+
+		var size;
+		if (window && element) {
+			this._id = element.getAttribute('id');
+			if (this._id == null)
+				element.setAttribute('id', this._id = 'view-' + View._id++);
+			DomEvent.add(element, this._viewEvents);
+			var none = 'none';
+			DomElement.setPrefixed(element.style, {
+				userDrag: none,
+				userSelect: none,
+				touchCallout: none,
+				contentZooming: none,
+				tapHighlightColor: 'rgba(0,0,0,0)'
 			});
-		}
-		this._setViewSize(size = getCanvasSize());
-		if (PaperScope.hasAttribute(element, 'stats')
-				&& typeof Stats !== 'undefined') {
-			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);
-		}
+
+			if (PaperScope.hasAttribute(element, 'resize')) {
+				var that = this;
+				DomEvent.add(window, this._windowEvents = {
+					resize: function() {
+						that.setViewSize(getCanvasSize());
+					}
+				});
+			}
+
+			size = getCanvasSize();
+
+			if (PaperScope.hasAttribute(element, 'stats')
+					&& typeof Stats !== 'undefined') {
+				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);
+			}
+		} else {
+			size = new Size(element);
+			element = null;
+		}
+		this._project = project;
+		this._scope = project._scope;
+		this._element = element;
+		if (!this._pixelRatio)
+			this._pixelRatio = window && window.devicePixelRatio || 1;
+		this._setElementSize(size.width, size.height);
+		this._viewSize = size;
 		View._views.push(this);
 		View._viewsById[this._id] = this;
-		this._viewSize = size;
 		(this._matrix = new Matrix())._owner = this;
-		this._zoom = 1;
 		if (!View._focused)
 			View._focused = this;
 		this._frameItems = {};
 		this._frameItemCount = 0;
+		this._itemEvents = { native: {}, virtual: {} };
+		this._autoUpdate = !paper.agent.node;
+		this._needsUpdate = false;
 	},
 
 	remove: function() {
@@ -10670,8 +12536,9 @@
 			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;
+		var project = this._project;
+		if (project._view === this)
+			project._view = null;
 		DomEvent.remove(this._element, this._viewEvents);
 		DomEvent.remove(window, this._windowEvents);
 		this._element = this._project = null;
@@ -10681,42 +12548,79 @@
 		return true;
 	},
 
-	_events: {
-		onFrame: {
-			install: function() {
-				this.play();
-			},
-
-			uninstall: function() {
-				this.pause();
-			}
-		},
-
-		onResize: {}
-	},
+	_events: Base.each(
+		Item._itemHandlers.concat(['onResize', 'onKeyDown', 'onKeyUp']),
+		function(name) {
+			this[name] = {};
+		}, {
+			onFrame: {
+				install: function() {
+					this.play();
+				},
+
+				uninstall: function() {
+					this.pause();
+				}
+			}
+		}
+	),
 
 	_animate: false,
 	_time: 0,
 	_count: 0,
 
-	_requestFrame: function() {
-		var that = this;
-		DomEvent.requestAnimationFrame(function() {
-			that._requested = false;
-			if (!that._animate)
-				return;
-			that._requestFrame();
-			that._handleFrame();
-		}, this._element);
-		this._requested = true;
+	getAutoUpdate: function() {
+		return this._autoUpdate;
+	},
+
+	setAutoUpdate: function(autoUpdate) {
+		this._autoUpdate = autoUpdate;
+		if (autoUpdate)
+			this.requestUpdate();
+	},
+
+	update: function() {
+	},
+
+	draw: function() {
+		this.update();
+	},
+
+	requestUpdate: function() {
+		if (!this._requested) {
+			var that = this;
+			DomEvent.requestAnimationFrame(function() {
+				that._requested = false;
+				if (that._animate) {
+					that.requestUpdate();
+					var element = that._element;
+					if ((!DomElement.getPrefixed(document, 'hidden')
+							|| PaperScope.getAttribute(element, 'keepalive')
+								=== 'true') && DomElement.isInView(element)) {
+						that._handleFrame();
+					}
+				}
+				if (that._autoUpdate)
+					that.update();
+			});
+			this._requested = true;
+		}
+	},
+
+	play: function() {
+		this._animate = true;
+		this.requestUpdate();
+	},
+
+	pause: function() {
+		this._animate = false;
 	},
 
 	_handleFrame: function() {
 		paper = this._scope;
 		var now = Date.now() / 1000,
-			delta = this._before ? now - this._before : 0;
-		this._before = now;
-		this._handlingFrame = true;
+			delta = this._last ? now - this._last : 0;
+		this._last = now;
 		this.emit('frame', new Base({
 			delta: delta,
 			time: this._time += delta,
@@ -10724,8 +12628,6 @@
 		}));
 		if (this._stats)
 			this._stats.update();
-		this._handlingFrame = false;
-		this.update();
 	},
 
 	_animateItem: function(item, animate) {
@@ -10756,26 +12658,9 @@
 		}
 	},
 
-	_update: function() {
-		this._project._needsUpdate = true;
-		if (this._handlingFrame)
-			return;
-		if (this._animate) {
-			this._handleFrame();
-		} else {
-			this.update();
-		}
-	},
-
-	_changed: function(flags) {
-		if (flags & 1)
-			this._project._needsUpdate = true;
-	},
-
-	_transform: function(matrix) {
-		this._matrix.concatenate(matrix);
-		this._bounds = null;
-		this._update();
+	_changed: function() {
+		this._project._changed(4097);
+		this._bounds = this._decomposed = undefined;
 	},
 
 	getElement: function() {
@@ -10800,20 +12685,23 @@
 			delta = size.subtract(this._viewSize);
 		if (delta.isZero())
 			return;
-		this._viewSize.set(size.width, size.height);
-		this._setViewSize(size);
-		this._bounds = null;
-		this.emit('resize', {
-			size: size,
-			delta: delta
-		});
-		this._update();
-	},
-
-	_setViewSize: function(size) {
+		this._setElementSize(size.width, size.height);
+		this._viewSize.set(size);
+		this._changed();
+		this.emit('resize', { size: size, delta: delta });
+		if (this._autoUpdate) {
+			this.update();
+		}
+	},
+
+	_setElementSize: function(width, height) {
 		var element = this._element;
-		element.width = size.width;
-		element.height = size.height;
+		if (element) {
+			if (element.width !== width)
+				element.width = width;
+			if (element.height !== height)
+				element.height = height;
+		}
 	},
 
 	getBounds: function() {
@@ -10827,46 +12715,111 @@
 		return this.getBounds().getSize();
 	},
 
+	isVisible: function() {
+		return DomElement.isInView(this._element);
+	},
+
+	isInserted: function() {
+		return DomElement.isInserted(this._element);
+	},
+
+	getPixelSize: function(size) {
+		var element = this._element,
+			pixels;
+		if (element) {
+			var parent = element.parentNode,
+				temp = document.createElement('div');
+			temp.style.fontSize = size;
+			parent.appendChild(temp);
+			pixels = parseFloat(DomElement.getStyles(temp).fontSize);
+			parent.removeChild(temp);
+		} else {
+			pixels = parseFloat(pixels);
+		}
+		return pixels;
+	},
+
+	getTextWidth: function(font, lines) {
+		return 0;
+	}
+}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) {
+	var rotate = key === 'rotate';
+	this[key] = function() {
+		var value = (rotate ? Base : Point).read(arguments),
+			center = Point.read(arguments, 0, { readNull: true });
+		return this.transform(new Matrix()[key](value,
+				center || this.getCenter(true)));
+	};
+}, {
+	_decompose: function() {
+		return this._decomposed || (this._decomposed = this._matrix.decompose());
+	},
+
+	translate: function() {
+		var mx = new Matrix();
+		return this.transform(mx.translate.apply(mx, arguments));
+	},
+
 	getCenter: function() {
 		return this.getBounds().getCenter();
 	},
 
 	setCenter: function() {
 		var center = Point.read(arguments);
-		this.scrollBy(center.subtract(this.getCenter()));
+		this.translate(this.getCenter().subtract(center));
 	},
 
 	getZoom: function() {
-		return this._zoom;
+		var scaling = this._decompose().scaling;
+		return (scaling.x + scaling.y) / 2;
 	},
 
 	setZoom: function(zoom) {
-		this._transform(new Matrix().scale(zoom / this._zoom,
+		this.transform(new Matrix().scale(zoom / this.getZoom(),
 			this.getCenter()));
-		this._zoom = zoom;
-	},
-
-	isVisible: function() {
-		return DomElement.isInView(this._element);
+	},
+
+	getRotation: function() {
+		return this._decompose().rotation;
+	},
+
+	setRotation: function(rotation) {
+		var current = this.getRotation();
+		if (current != null && rotation != null) {
+			this.rotate(rotation - current);
+		}
+	},
+
+	getScaling: function() {
+		var scaling = this._decompose().scaling;
+		return new LinkedPoint(scaling.x, scaling.y, this, 'setScaling');
+	},
+
+	setScaling: function() {
+		var current = this.getScaling(),
+			scaling = Point.read(arguments, 0, { clone: true, readNull: true });
+		if (current && scaling) {
+			this.scale(scaling.x / current.x, scaling.y / current.y);
+		}
+	},
+
+	getMatrix: function() {
+		return this._matrix;
+	},
+
+	setMatrix: function() {
+		var matrix = this._matrix;
+		matrix.initialize.apply(matrix, arguments);
+	},
+
+	transform: function(matrix) {
+		this._matrix.append(matrix);
 	},
 
 	scrollBy: function() {
-		this._transform(new Matrix().translate(Point.read(arguments).negate()));
-	},
-
-	play: function() {
-		this._animate = true;
-		if (!this._requested)
-			this._requestFrame();
-	},
-
-	pause: function() {
-		this._animate = false;
-	},
-
-	draw: function() {
-		this.update();
-	},
+		this.translate(Point.read(arguments).negate());
+	}
+}), {
 
 	projectToView: function() {
 		return this._matrix._transformPoint(Point.read(arguments));
@@ -10874,7 +12827,11 @@
 
 	viewToProject: function() {
 		return this._matrix._inverseTransform(Point.read(arguments));
-	}
+	},
+
+	getEventPoint: function(event) {
+		return this.viewToProject(DomEvent.getOffset(event, this._element));
+	},
 
 }, {
 	statics: {
@@ -10883,31 +12840,32 @@
 		_id: 0,
 
 		create: function(project, element) {
-			if (typeof element === 'string')
+			if (document && typeof element === 'string')
 				element = document.getElementById(element);
-			return new CanvasView(project, element);
-		}
-	}
-}, new function() {
-	var tool,
-		prevFocus,
+			var ctor = window ? CanvasView : View;
+			return new ctor(project, element);
+		}
+	}
+},
+new function() {
+	if (!window)
+		return;
+	var prevFocus,
 		tempFocus,
-		dragging = false;
+		dragging = false,
+		mouseDown = false;
 
 	function getView(event) {
 		var target = DomEvent.getTarget(event);
-		return target.getAttribute && View._viewsById[target.getAttribute('id')];
-	}
-
-	function viewToProject(view, event) {
-		return view.viewToProject(DomEvent.getOffset(event, view._element));
+		return target.getAttribute && View._viewsById[
+				target.getAttribute('id')];
 	}
 
 	function updateFocus() {
-		if (!View._focused || !View._focused.isVisible()) {
+		var view = View._focused;
+		if (!view || !view.isVisible()) {
 			for (var i = 0, l = View._views.length; i < l; i++) {
-				var view = View._views[i];
-				if (view && view.isVisible()) {
+				if ((view = View._views[i]).isVisible()) {
 					View._focused = tempFocus = view;
 					break;
 				}
@@ -10915,15 +12873,8 @@
 		}
 	}
 
-	function handleMouseMove(view, point, event) {
-		view._handleEvent('mousemove', point, event);
-		var tool = view._scope.tool;
-		if (tool) {
-			tool._handleEvent(dragging && tool.responds('mousedrag')
-					? 'mousedrag' : 'mousemove', point, event);
-		}
-		view.update();
-		return tool;
+	function handleMouseMove(view, event, point) {
+		view._handleMouseEvent('mousemove', event, point);
 	}
 
 	var navigator = window.navigator,
@@ -10944,65 +12895,67 @@
 		}
 	}
 
-	var viewEvents = {
-		'selectstart dragstart': function(event) {
-			if (dragging)
-				event.preventDefault();
-		}
-	};
-
-	var docEvents = {
-		mouseout: function(event) {
-			var view = View._focused,
-				target = DomEvent.getRelatedTarget(event);
-			if (view && (!target || target.nodeName === 'HTML'))
-				handleMouseMove(view, viewToProject(view, event), event);
-		},
-
-		scroll: updateFocus
-	};
+	var viewEvents = {},
+		docEvents = {
+			mouseout: function(event) {
+				var view = View._focused,
+					target = DomEvent.getRelatedTarget(event);
+				if (view && (!target || target.nodeName === 'HTML')) {
+					var offset = DomEvent.getOffset(event, view._element),
+						x = offset.x,
+						abs = Math.abs,
+						ax = abs(x),
+						max = 1 << 25,
+						diff = ax - max;
+					offset.x = abs(diff) < ax ? diff * (x < 0 ? -1 : 1) : x;
+					handleMouseMove(view, event, view.viewToProject(offset));
+				}
+			},
+
+			scroll: updateFocus
+		};
 
 	viewEvents[mousedown] = function(event) {
-		var view = View._focused = getView(event),
-			point = viewToProject(view, event);
-		dragging = true;
-		view._handleEvent('mousedown', point, event);
-		if (tool = view._scope.tool)
-			tool._handleEvent('mousedown', point, event);
-		view.update();
+		var view = View._focused = getView(event);
+		if (!dragging) {
+			dragging = true;
+			view._handleMouseEvent('mousedown', event);
+		}
 	};
 
 	docEvents[mousemove] = function(event) {
 		var view = View._focused;
-		if (!dragging) {
+		if (!mouseDown) {
 			var target = getView(event);
 			if (target) {
-				if (view !== target)
-					handleMouseMove(view, viewToProject(view, event), event);
-				prevFocus = view;
-				view = View._focused = tempFocus = target;
+				if (view !== target) {
+					if (view)
+						handleMouseMove(view, event);
+					if (!prevFocus)
+						prevFocus = view;
+					view = View._focused = tempFocus = target;
+				}
 			} else if (tempFocus && tempFocus === view) {
+				if (prevFocus && !prevFocus.isInserted())
+					prevFocus = null;
 				view = View._focused = prevFocus;
+				prevFocus = null;
 				updateFocus();
 			}
 		}
-		if (view) {
-			var point = viewToProject(view, event);
-			if (dragging || view.getBounds().contains(point))
-				tool = handleMouseMove(view, point, event);
-		}
+		if (view)
+			handleMouseMove(view, event);
+	};
+
+	docEvents[mousedown] = function() {
+		mouseDown = true;
 	};
 
 	docEvents[mouseup] = function(event) {
 		var view = View._focused;
-		if (!view || !dragging)
-			return;
-		var point = viewToProject(view, event);
-		dragging = false;
-		view._handleEvent('mouseup', point, event);
-		if (tool)
-			tool._handleEvent('mouseup', point, event);
-		view.update();
+		if (view && dragging)
+			view._handleMouseEvent('mouseup', event);
+		mouseDown = dragging = false;
 	};
 
 	DomEvent.add(document, docEvents);
@@ -11011,13 +12964,223 @@
 		load: updateFocus
 	});
 
+	var called = false,
+		prevented = false,
+		fallbacks = {
+			doubleclick: 'click',
+			mousedrag: 'mousemove'
+		},
+		wasInView = false,
+		overView,
+		downPoint,
+		lastPoint,
+		downItem,
+		overItem,
+		dragItem,
+		clickItem,
+		clickTime,
+		dblClick;
+
+	function emitMouseEvent(obj, target, type, event, point, prevPoint,
+			stopItem) {
+		var stopped = false,
+			mouseEvent;
+
+		function emit(obj, type) {
+			if (obj.responds(type)) {
+				if (!mouseEvent) {
+					mouseEvent = new MouseEvent(type, event, point,
+							target || obj,
+							prevPoint ? point.subtract(prevPoint) : null);
+				}
+				if (obj.emit(type, mouseEvent)) {
+					called = true;
+					if (mouseEvent.prevented)
+						prevented = true;
+					if (mouseEvent.stopped)
+						return stopped = true;
+				}
+			} else {
+				var fallback = fallbacks[type];
+				if (fallback)
+					return emit(obj, fallback);
+			}
+		}
+
+		while (obj && obj !== stopItem) {
+			if (emit(obj, type))
+				break;
+			obj = obj._parent;
+		}
+		return stopped;
+	}
+
+	function emitMouseEvents(view, hitItem, type, event, point, prevPoint) {
+		view._project.removeOn(type);
+		prevented = called = false;
+		return (dragItem && emitMouseEvent(dragItem, null, type, event,
+					point, prevPoint)
+			|| hitItem && hitItem !== dragItem
+				&& !hitItem.isDescendant(dragItem)
+				&& emitMouseEvent(hitItem, null, type === 'mousedrag' ?
+					'mousemove' : type, event, point, prevPoint, dragItem)
+			|| emitMouseEvent(view, dragItem || hitItem || view, type, event,
+					point, prevPoint));
+	}
+
+	var itemEventsMap = {
+		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
+		}
+	};
+
 	return {
 		_viewEvents: viewEvents,
 
-		_handleEvent: function() {},
+		_handleMouseEvent: function(type, event, point) {
+			var itemEvents = this._itemEvents,
+				hitItems = itemEvents.native[type],
+				nativeMove = type === 'mousemove',
+				tool = this._scope.tool,
+				view = this;
+
+			function responds(type) {
+				return itemEvents.virtual[type] || view.responds(type)
+						|| tool && tool.responds(type);
+			}
+
+			if (nativeMove && dragging && responds('mousedrag'))
+				type = 'mousedrag';
+			if (!point)
+				point = this.getEventPoint(event);
+
+			var inView = this.getBounds().contains(point),
+				hit = hitItems && inView && view._project.hitTest(point, {
+					tolerance: 0,
+					fill: true,
+					stroke: true
+				}),
+				hitItem = hit && hit.item || null,
+				handle = false,
+				mouse = {};
+			mouse[type.substr(5)] = true;
+
+			if (hitItems && hitItem !== overItem) {
+				if (overItem) {
+					emitMouseEvent(overItem, null, 'mouseleave', event, point);
+				}
+				if (hitItem) {
+					emitMouseEvent(hitItem, null, 'mouseenter', event, point);
+				}
+				overItem = hitItem;
+			}
+			if (wasInView ^ inView) {
+				emitMouseEvent(this, null, inView ? 'mouseenter' : 'mouseleave',
+						event, point);
+				overView = inView ? this : null;
+				handle = true;
+			}
+			if ((inView || mouse.drag) && !point.equals(lastPoint)) {
+				emitMouseEvents(this, hitItem, nativeMove ? type : 'mousemove',
+						event, point, lastPoint);
+				handle = true;
+			}
+			wasInView = inView;
+			if (mouse.down && inView || mouse.up && downPoint) {
+				emitMouseEvents(this, hitItem, type, event, point, downPoint);
+				if (mouse.down) {
+					dblClick = hitItem === clickItem
+						&& (Date.now() - clickTime < 300);
+					downItem = clickItem = hitItem;
+					if (!prevented && hitItem) {
+						var item = hitItem;
+						while (item && !item.responds('mousedrag'))
+							item = item._parent;
+						if (item)
+							dragItem = hitItem;
+					}
+					downPoint = point;
+				} else if (mouse.up) {
+					if (!prevented && hitItem === downItem) {
+						clickTime = Date.now();
+						emitMouseEvents(this, hitItem, dblClick ? 'doubleclick'
+								: 'click', event, point, downPoint);
+						dblClick = false;
+					}
+					downItem = dragItem = null;
+				}
+				wasInView = false;
+				handle = true;
+			}
+			lastPoint = point;
+			if (handle && tool) {
+				called = tool._handleMouseEvent(type, event, point, mouse)
+					|| called;
+			}
+
+			if (
+				event.cancelable !== false
+				&& (called && !mouse.move || mouse.down && responds('mouseup'))
+			) {
+				event.preventDefault();
+			}
+		},
+
+		_handleKeyEvent: function(type, event, key, character) {
+			var scope = this._scope,
+				tool = scope.tool,
+				keyEvent;
+
+			function emit(obj) {
+				if (obj.responds(type)) {
+					paper = scope;
+					obj.emit(type, keyEvent = keyEvent
+							|| new KeyEvent(type, event, key, character));
+				}
+			}
+
+			if (this.isVisible()) {
+				emit(this);
+				if (tool && tool.responds(type))
+					emit(tool);
+			}
+		},
+
+		_countItemEvent: function(type, sign) {
+			var itemEvents = this._itemEvents,
+				native = itemEvents.native,
+				virtual = itemEvents.virtual;
+			for (var key in itemEventsMap) {
+				native[key] = (native[key] || 0)
+						+ (itemEventsMap[key][type] || 0) * sign;
+			}
+			virtual[type] = (virtual[type] || 0) + sign;
+		},
 
 		statics: {
-			updateFocus: updateFocus
+			updateFocus: updateFocus,
+
+			_resetState: function() {
+				dragging = mouseDown = called = wasInView = false;
+				prevFocus = tempFocus = overView = downPoint = lastPoint =
+					downItem = overItem = dragItem = clickItem = clickTime =
+					dblClick = null;
+			}
 		}
 	};
 });
@@ -11026,53 +13189,58 @@
 	_class: 'CanvasView',
 
 	initialize: function CanvasView(project, canvas) {
-		if (!(canvas instanceof HTMLCanvasElement)) {
+		if (!(canvas instanceof window.HTMLCanvasElement)) {
 			var size = Size.read(arguments, 1);
 			if (size.isZero())
 				throw new Error(
 						'Cannot create CanvasView with the provided argument: '
-						+ [].slice.call(arguments, 1));
+						+ Base.slice(arguments, 1));
 			canvas = CanvasProvider.getCanvas(size);
 		}
-		this._context = canvas.getContext('2d');
-		this._eventCounters = {};
+		var ctx = this._context = canvas.getContext('2d');
+		ctx.save();
 		this._pixelRatio = 1;
 		if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) {
 			var deviceRatio = window.devicePixelRatio || 1,
-				backingStoreRatio = DomElement.getPrefixed(this._context,
+				backingStoreRatio = DomElement.getPrefixed(ctx,
 						'backingStorePixelRatio') || 1;
 			this._pixelRatio = deviceRatio / backingStoreRatio;
 		}
 		View.call(this, project, canvas);
-	},
-
-	_setViewSize: function(size) {
-		var element = this._element,
-			pixelRatio = this._pixelRatio,
-			width = size.width,
-			height = size.height;
-		element.width = width * pixelRatio;
-		element.height = height * pixelRatio;
+		this._needsUpdate = true;
+	},
+
+	remove: function remove() {
+		this._context.restore();
+		return remove.base.call(this);
+	},
+
+	_setElementSize: function _setElementSize(width, height) {
+		var pixelRatio = this._pixelRatio;
+		_setElementSize.base.call(this, width * pixelRatio, height * pixelRatio);
 		if (pixelRatio !== 1) {
+			var element = this._element,
+				ctx = this._context;
 			if (!PaperScope.hasAttribute(element, 'resize')) {
 				var style = element.style;
 				style.width = width + 'px';
 				style.height = height + 'px';
 			}
-			this._context.scale(pixelRatio, pixelRatio);
-		}
-	},
-
-	getPixelSize: function(size) {
-		var browser = paper.browser,
+			ctx.restore();
+			ctx.save();
+			ctx.scale(pixelRatio, pixelRatio);
+		}
+	},
+
+	getContext: function() {
+		return this._context;
+	},
+
+	getPixelSize: function getPixelSize(size) {
+		var agent = paper.agent,
 			pixels;
-		if (browser && browser.firefox) {
-			var parent = this._element.parentNode,
-				temp = document.createElement('div');
-			temp.style.fontSize = size;
-			parent.appendChild(temp);
-			pixels = parseFloat(DomElement.getStyles(temp).fontSize);
-			parent.removeChild(temp);
+		if (agent && agent.firefox) {
+			pixels = getPixelSize.base.call(this, size);
 		} else {
 			var ctx = this._context,
 				prevFont = ctx.font;
@@ -11094,119 +13262,18 @@
 		return width;
 	},
 
-	update: function(force) {
-		var project = this._project;
-		if (!project || !force && !project._needsUpdate)
+	update: function() {
+		if (!this._needsUpdate)
 			return false;
-		var ctx = this._context,
+		var project = this._project,
+			ctx = this._context,
 			size = this._viewSize;
 		ctx.clearRect(0, 0, size.width + 1, size.height + 1);
-		project.draw(ctx, this._matrix, this._pixelRatio);
-		project._needsUpdate = false;
+		if (project)
+			project.draw(ctx, this._matrix, this._pixelRatio);
+		this._needsUpdate = false;
 		return true;
 	}
-}, new function() {
-
-	var downPoint,
-		lastPoint,
-		overPoint,
-		downItem,
-		lastItem,
-		overItem,
-		dragItem,
-		dblClick,
-		clickTime;
-
-	function callEvent(view, type, event, point, target, lastPoint) {
-		var item = target,
-			mouseEvent;
-
-		function call(obj) {
-			if (obj.responds(type)) {
-				if (!mouseEvent) {
-					mouseEvent = new MouseEvent(type, event, point, target,
-							lastPoint ? point.subtract(lastPoint) : null);
-				}
-				if (obj.emit(type, mouseEvent) && mouseEvent.isStopped) {
-					event.preventDefault();
-					return true;
-				}
-			}
-		}
-
-		while (item) {
-			if (call(item))
-				return true;
-			item = item.getParent();
-		}
-		if (call(view))
-			return true;
-		return false;
-	}
-
-	return {
-		_handleEvent: function(type, point, event) {
-			if (!this._eventCounters[type])
-				return;
-			var project = this._project,
-				hit = project.hitTest(point, {
-					tolerance: 0,
-					fill: true,
-					stroke: true
-				}),
-				item = hit && hit.item,
-				stopped = false;
-			switch (type) {
-			case 'mousedown':
-				stopped = callEvent(this, type, event, point, item);
-				dblClick = lastItem == item && (Date.now() - clickTime < 300);
-				downItem = lastItem = item;
-				downPoint = lastPoint = overPoint = point;
-				dragItem = !stopped && item;
-				while (dragItem && !dragItem.responds('mousedrag'))
-					dragItem = dragItem._parent;
-				break;
-			case 'mouseup':
-				stopped = callEvent(this, type, event, point, item, downPoint);
-				if (dragItem) {
-					if (lastPoint && !lastPoint.equals(point))
-						callEvent(this, 'mousedrag', event, point, dragItem,
-								lastPoint);
-					if (item !== dragItem) {
-						overPoint = point;
-						callEvent(this, 'mousemove', event, point, item,
-								overPoint);
-					}
-				}
-				if (!stopped && item && item === downItem) {
-					clickTime = Date.now();
-					callEvent(this, dblClick && downItem.responds('doubleclick')
-							? 'doubleclick' : 'click', event, downPoint, item);
-					dblClick = false;
-				}
-				downItem = dragItem = null;
-				break;
-			case 'mousemove':
-				if (dragItem)
-					stopped = callEvent(this, 'mousedrag', event, point,
-							dragItem, lastPoint);
-				if (!stopped) {
-					if (item !== overItem)
-						overPoint = point;
-					stopped = callEvent(this, type, event, point, item,
-							overPoint);
-				}
-				lastPoint = overPoint = point;
-				if (item !== overItem) {
-					callEvent(this, 'mouseleave', event, point, overItem);
-					overItem = item;
-					callEvent(this, 'mouseenter', event, point, item);
-				}
-				break;
-			}
-			return stopped;
-		}
-	};
 });
 
 var Event = Base.extend({
@@ -11214,18 +13281,19 @@
 
 	initialize: function Event(event) {
 		this.event = event;
-	},
-
-	isPrevented: false,
-	isStopped: false,
+		this.type = event && event.type;
+	},
+
+	prevented: false,
+	stopped: false,
 
 	preventDefault: function() {
-		this.isPrevented = true;
+		this.prevented = true;
 		this.event.preventDefault();
 	},
 
 	stopPropagation: function() {
-		this.isStopped = true;
+		this.stopped = true;
 		this.event.stopPropagation();
 	},
 
@@ -11234,6 +13302,10 @@
 		this.preventDefault();
 	},
 
+	getTimeStamp: function() {
+		return this.event.timeStamp;
+	},
+
 	getModifiers: function() {
 		return Key.modifiers;
 	}
@@ -11242,9 +13314,9 @@
 var KeyEvent = Event.extend({
 	_class: 'KeyEvent',
 
-	initialize: function KeyEvent(down, key, character, event) {
-		Event.call(this, event);
-		this.type = down ? 'keydown' : 'keyup';
+	initialize: function KeyEvent(type, event, key, character) {
+		this.type = type;
+		this.event = event;
 		this.key = key;
 		this.character = character;
 	},
@@ -11259,120 +13331,134 @@
 });
 
 var Key = new function() {
-
-	var specialKeys = {
-		8: 'backspace',
-		9: 'tab',
-		13: 'enter',
-		16: 'shift',
-		17: 'control',
-		18: 'option',
-		19: 'pause',
-		20: 'caps-lock',
-		27: 'escape',
-		32: 'space',
-		35: 'end',
-		36: 'home',
-		37: 'left',
-		38: 'up',
-		39: 'right',
-		40: 'down',
-		46: 'delete',
-		91: 'command',
-		93: 'command',
-		224: 'command'
-	},
-
-	specialChars = {
-		9: true,
-		13: true,
-		32: true
-	},
-
-	modifiers = new Base({
-		shift: false,
-		control: false,
-		option: false,
-		command: false,
-		capsLock: false,
-		space: false
-	}),
-
-	charCodeMap = {},
-	keyMap = {},
-	commandFixMap,
-	downCode;
-
-	function handleKey(down, keyCode, charCode, event) {
-		var character = charCode ? String.fromCharCode(charCode) : '',
-			specialKey = specialKeys[keyCode],
-			key = specialKey || character.toLowerCase(),
-			type = down ? 'keydown' : 'keyup',
+	var keyLookup = {
+			'\t': 'tab',
+			' ': 'space',
+			'\b': 'backspace',
+			'\x7f': 'delete',
+			'Spacebar': 'space',
+			'Del': 'delete',
+			'Win': 'meta',
+			'Esc': 'escape'
+		},
+
+		charLookup = {
+			'tab': '\t',
+			'space': ' ',
+			'enter': '\r'
+		},
+
+		keyMap = {},
+		charMap = {},
+		metaFixMap,
+		downKey,
+
+		modifiers = new Base({
+			shift: false,
+			control: false,
+			alt: false,
+			meta: false,
+			capsLock: false,
+			space: false
+		}).inject({
+			option: {
+				get: function() {
+					return this.alt;
+				}
+			},
+
+			command: {
+				get: function() {
+					var agent = paper && paper.agent;
+					return agent && agent.mac ? this.meta : this.control;
+				}
+			}
+		});
+
+	function getKey(event) {
+		var key = event.key || event.keyIdentifier;
+		key = /^U\+/.test(key)
+				? String.fromCharCode(parseInt(key.substr(2), 16))
+				: /^Arrow[A-Z]/.test(key) ? key.substr(5)
+				: key === 'Unidentified'  || key === undefined
+					? String.fromCharCode(event.keyCode)
+					: key;
+		return keyLookup[key] ||
+				(key.length > 1 ? Base.hyphenate(key) : key.toLowerCase());
+	}
+
+	function handleKey(down, key, character, event) {
+		var type = down ? 'keydown' : 'keyup',
 			view = View._focused,
-			scope = view && view.isVisible() && view._scope,
-			tool = scope && scope.tool,
 			name;
 		keyMap[key] = down;
 		if (down) {
-			charCodeMap[keyCode] = charCode;
+			charMap[key] = character;
 		} else {
-			delete charCodeMap[keyCode];
-		}
-		if (specialKey && (name = Base.camelize(specialKey)) in modifiers) {
+			delete charMap[key];
+		}
+		if (key.length > 1 && (name = Base.camelize(key)) in modifiers) {
 			modifiers[name] = down;
-			var browser = paper.browser;
-			if (name === 'command' && browser && browser.mac) {
+			var agent = paper && paper.agent;
+			if (name === 'meta' && agent && agent.mac) {
 				if (down) {
-					commandFixMap = {};
+					metaFixMap = {};
 				} else {
-					for (var code in commandFixMap) {
-						if (code in charCodeMap)
-							handleKey(false, code, commandFixMap[code], event);
-					}
-					commandFixMap = null;
-				}
-			}
-		} else if (down && commandFixMap) {
-			commandFixMap[keyCode] = charCode;
-		}
-		if (tool && tool.responds(type)) {
-			paper = scope;
-			tool.emit(type, new KeyEvent(down, key, character, event));
-			if (view)
-				view.update();
+					for (var k in metaFixMap) {
+						if (k in charMap)
+							handleKey(false, k, metaFixMap[k], event);
+					}
+					metaFixMap = null;
+				}
+			}
+		} else if (down && metaFixMap) {
+			metaFixMap[key] = character;
+		}
+		if (view) {
+			view._handleKeyEvent(down ? 'keydown' : 'keyup', event, key,
+					character);
 		}
 	}
 
 	DomEvent.add(document, {
 		keydown: function(event) {
-			var code = event.which || event.keyCode;
-			if (code in specialKeys || modifiers.command) {
-				handleKey(true, code,
-						code in specialChars || modifiers.command ? code : 0,
-						event);
-			} else {
-				downCode = code;
+			var key = getKey(event),
+				agent = paper && paper.agent;
+			if (key.length > 1 || agent && (agent.chrome && (event.altKey
+						|| agent.mac && event.metaKey
+						|| !agent.mac && event.ctrlKey))) {
+				handleKey(true, key,
+						charLookup[key] || (key.length > 1 ? '' : key), event);
+			} else {
+				downKey = key;
 			}
 		},
 
 		keypress: function(event) {
-			if (downCode != null) {
-				handleKey(true, downCode, event.which || event.keyCode, event);
-				downCode = null;
+			if (downKey) {
+				var key = getKey(event),
+					code = event.charCode,
+					character = code >= 32 ? String.fromCharCode(code)
+						: key.length > 1 ? '' : key;
+				if (key !== downKey) {
+					key = character.toLowerCase();
+				}
+				handleKey(true, key, character, event);
+				downKey = null;
 			}
 		},
 
 		keyup: function(event) {
-			var code = event.which || event.keyCode;
-			if (code in charCodeMap)
-				handleKey(false, code, charCodeMap[code], event);
+			var key = getKey(event);
+			if (key in charMap)
+				handleKey(false, key, charMap[key], event);
 		}
 	});
 
 	DomEvent.add(window, {
 		blur: function(event) {
-			for (var code in charCodeMap)
-				handleKey(false, code, charCodeMap[code], event);
+			for (var key in charMap)
+				handleKey(false, key, charMap[key], event);
 		}
 	});
 
@@ -11389,8 +13475,8 @@
 	_class: 'MouseEvent',
 
 	initialize: function MouseEvent(type, event, point, target, delta) {
-		Event.call(this, event);
 		this.type = type;
+		this.event = event;
 		this.point = point;
 		this.target = target;
 		this.delta = delta;
@@ -11466,9 +13552,8 @@
 	},
 
 	getCount: function() {
-		return /^mouse(down|up)$/.test(this.type)
-				? this.tool._downCount
-				: this.tool._count;
+		return this.tool[/^mouse(down|up)$/.test(this.type)
+				? '_downCount' : '_moveCount'];
 	},
 
 	setCount: function(count) {
@@ -11509,16 +13594,15 @@
 	_class: 'Tool',
 	_list: 'tools',
 	_reference: 'tool',
-	_events: [ 'onActivate', 'onDeactivate', 'onEditOptions',
-			'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
-			'onKeyDown', 'onKeyUp' ],
+	_events: ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
+			'onActivate', 'onDeactivate', 'onEditOptions', 'onKeyDown',
+			'onKeyUp'],
 
 	initialize: function Tool(props) {
 		PaperScopeItem.call(this);
-		this._firstMove = true;
-		this._count = 0;
-		this._downCount = 0;
-		this._set(props);
+		this._moveCount = -1;
+		this._downCount = -1;
+		this.set(props);
 	},
 
 	getMinDistance: function() {
@@ -11551,131 +13635,334 @@
 	},
 
 	setFixedDistance: function(distance) {
-		this._minDistance = distance;
-		this._maxDistance = distance;
-	},
-
-	_updateEvent: function(type, point, minDistance, maxDistance, start,
-			needsChange, matchMaxDistance) {
-		if (!start) {
-			if (minDistance != null || maxDistance != null) {
-				var minDist = minDistance != null ? minDistance : 0,
-					vector = point.subtract(this._point),
-					distance = vector.getLength();
-				if (distance < minDist)
+		this._minDistance = this._maxDistance = distance;
+	},
+
+	_handleMouseEvent: function(type, event, point, mouse) {
+		paper = this._scope;
+		if (mouse.drag && !this.responds(type))
+			type = 'mousemove';
+		var move = mouse.move || mouse.drag,
+			responds = this.responds(type),
+			minDistance = this.minDistance,
+			maxDistance = this.maxDistance,
+			called = false,
+			tool = this;
+		function update(minDistance, maxDistance) {
+			var pt = point,
+				toolPoint = move ? tool._point : (tool._downPoint || pt);
+			if (move) {
+				if (tool._moveCount >= 0 && pt.equals(toolPoint)) {
 					return false;
-				if (maxDistance != null && maxDistance != 0) {
-					if (distance > maxDistance) {
-						point = this._point.add(vector.normalize(maxDistance));
-					} else if (matchMaxDistance) {
+				}
+				if (toolPoint && (minDistance != null || maxDistance != null)) {
+					var vector = pt.subtract(toolPoint),
+						distance = vector.getLength();
+					if (distance < (minDistance || 0))
 						return false;
-					}
-				}
-			}
-			if (needsChange && point.equals(this._point))
-				return false;
-		}
-		this._lastPoint = start && type == 'mousemove' ? point : this._point;
-		this._point = point;
-		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;
-	},
-
-	_fireEvent: function(type, event) {
-		var sets = paper.project._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)
-							delete other[item._id];
-					}
-					item.remove();
-				}
-				sets[type] = null;
-			}
-		}
-		return this.responds(type)
-				&& this.emit(type, new ToolEvent(this, type, event));
-	},
-
-	_handleEvent: function(type, point, event) {
-		paper = this._scope;
-		var called = false;
-		switch (type) {
-		case 'mousedown':
-			this._updateEvent(type, point, null, null, true, false, false);
-			called = this._fireEvent(type, event);
-			break;
-		case 'mousedrag':
-			var needsChange = false,
-				matchMaxDistance = false;
-			while (this._updateEvent(type, point, this.minDistance,
-					this.maxDistance, false, needsChange, matchMaxDistance)) {
-				called = this._fireEvent(type, event) || called;
-				needsChange = true;
-				matchMaxDistance = true;
-			}
-			break;
-		case 'mouseup':
-			if (!point.equals(this._point)
-					&& this._updateEvent('mousedrag', point, this.minDistance,
-							this.maxDistance, false, false, false)) {
-				called = this._fireEvent('mousedrag', event);
-			}
-			this._updateEvent(type, point, null, this.maxDistance, false,
-					false, false);
-			called = this._fireEvent(type, event) || called;
-			this._updateEvent(type, point, null, null, true, false, false);
-			this._firstMove = true;
-			break;
-		case 'mousemove':
-			while (this._updateEvent(type, point, this.minDistance,
-					this.maxDistance, this._firstMove, true, false)) {
-				called = this._fireEvent(type, event) || called;
-				this._firstMove = false;
-			}
-			break;
-		}
-		if (called)
-			event.preventDefault();
+					if (maxDistance) {
+						pt = toolPoint.add(vector.normalize(
+								Math.min(distance, maxDistance)));
+					}
+				}
+				tool._moveCount++;
+			}
+			tool._point = pt;
+			tool._lastPoint = toolPoint || pt;
+			if (mouse.down) {
+				tool._moveCount = -1;
+				tool._downPoint = pt;
+				tool._downCount++;
+			}
+			return true;
+		}
+
+		function emit() {
+			if (responds) {
+				called = tool.emit(type, new ToolEvent(tool, type, event))
+						|| called;
+			}
+		}
+
+		if (mouse.down) {
+			update();
+			emit();
+		} else if (mouse.up) {
+			update(null, maxDistance);
+			emit();
+		} else if (responds) {
+			while (update(minDistance, maxDistance))
+				emit();
+		}
 		return called;
 	}
 
 });
 
+var Tween = Base.extend(Emitter, {
+	_class: 'Tween',
+
+	statics: {
+		easings: {
+			linear: function(t) {
+				return t;
+			},
+
+			easeInQuad: function(t) {
+				return t * t;
+			},
+
+			easeOutQuad: function(t) {
+				return t * (2 - t);
+			},
+
+			easeInOutQuad: function(t) {
+				return t < 0.5
+					? 2 * t * t
+					: -1 + 2 * (2 - t) * t;
+			},
+
+			easeInCubic: function(t) {
+				return t * t * t;
+			},
+
+			easeOutCubic: function(t) {
+				return --t * t * t + 1;
+			},
+
+			easeInOutCubic: function(t) {
+				return t < 0.5
+					? 4 * t * t * t
+					: (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
+			},
+
+			easeInQuart: function(t) {
+				return t * t * t * t;
+			},
+
+			easeOutQuart: function(t) {
+				return 1 - (--t) * t * t * t;
+			},
+
+			easeInOutQuart: function(t) {
+				return t < 0.5
+					? 8 * t * t * t * t
+					: 1 - 8 * (--t) * t * t * t;
+			},
+
+			easeInQuint: function(t) {
+				return t * t * t * t * t;
+			},
+
+			easeOutQuint: function(t) {
+				return 1 + --t * t * t * t * t;
+			},
+
+			easeInOutQuint: function(t) {
+				return t < 0.5
+					? 16 * t * t * t * t * t
+					: 1 + 16 * (--t) * t * t * t * t;
+			}
+		}
+	},
+
+	initialize: function Tween(object, from, to, duration, easing, start) {
+		this.object = object;
+		var type = typeof easing;
+		var isFunction = type === 'function';
+		this.type = isFunction
+			? type
+			: type === 'string'
+				? easing
+				: 'linear';
+		this.easing = isFunction ? easing : Tween.easings[this.type];
+		this.duration = duration;
+		this.running = false;
+
+		this._then = null;
+		this._startTime = null;
+		var state = from || to;
+		this._keys = state ? Object.keys(state) : [];
+		this._parsedKeys = this._parseKeys(this._keys);
+		this._from = state && this._getState(from);
+		this._to = state && this._getState(to);
+		if (start !== false) {
+			this.start();
+		}
+	},
+
+	then: function(then) {
+		this._then = then;
+		return this;
+	},
+
+	start: function() {
+		this._startTime = null;
+		this.running = true;
+		return this;
+	},
+
+	stop: function() {
+		this.running = false;
+		return this;
+	},
+
+	update: function(progress) {
+		if (this.running) {
+			if (progress > 1) {
+				progress = 1;
+				this.running = false;
+			}
+
+			var factor = this.easing(progress),
+				keys = this._keys,
+				getValue = function(value) {
+					return typeof value === 'function'
+						? value(factor, progress)
+						: value;
+				};
+			for (var i = 0, l = keys && keys.length; i < l; i++) {
+				var key = keys[i],
+					from = getValue(this._from[key]),
+					to = getValue(this._to[key]),
+					value = (from && to && from.__add && to.__add)
+						? to.__subtract(from).__multiply(factor).__add(from)
+						: ((to - from) * factor) + from;
+				this._setProperty(this._parsedKeys[key], value);
+			}
+
+			if (!this.running && this._then) {
+				this._then(this.object);
+			}
+			if (this.responds('update')) {
+				this.emit('update', new Base({
+					progress: progress,
+					factor: factor
+				}));
+			}
+		}
+		return this;
+	},
+
+	_events: {
+		onUpdate: {}
+	},
+
+	_handleFrame: function(time) {
+		var startTime = this._startTime,
+			progress = startTime
+				? (time - startTime) / this.duration
+				: 0;
+		if (!startTime) {
+			this._startTime = time;
+		}
+		this.update(progress);
+	},
+
+	_getState: function(state) {
+		var keys = this._keys,
+			result = {};
+		for (var i = 0, l = keys.length; i < l; i++) {
+			var key = keys[i],
+				path = this._parsedKeys[key],
+				current = this._getProperty(path),
+				value;
+			if (state) {
+				var resolved = this._resolveValue(current, state[key]);
+				this._setProperty(path, resolved);
+				value = this._getProperty(path);
+				value = value && value.clone ? value.clone() : value;
+				this._setProperty(path, current);
+			} else {
+				value = current && current.clone ? current.clone() : current;
+			}
+			result[key] = value;
+		}
+		return result;
+	},
+
+	_resolveValue: function(current, value) {
+		if (value) {
+			if (Array.isArray(value) && value.length === 2) {
+				var operator = value[0];
+				return (
+					operator &&
+					operator.match &&
+					operator.match(/^[+\-\*\/]=/)
+				)
+					? this._calculate(current, operator[0], value[1])
+					: value;
+			} else if (typeof value === 'string') {
+				var match = value.match(/^[+\-*/]=(.*)/);
+				if (match) {
+					var parsed = JSON.parse(match[1].replace(
+						/(['"])?([a-zA-Z0-9_]+)(['"])?:/g,
+						'"$2": '
+					));
+					return this._calculate(current, value[0], parsed);
+				}
+			}
+		}
+		return value;
+	},
+
+	_calculate: function(left, operator, right) {
+		return paper.PaperScript.calculateBinary(left, operator, right);
+	},
+
+	_parseKeys: function(keys) {
+		var parsed = {};
+		for (var i = 0, l = keys.length; i < l; i++) {
+			var key = keys[i],
+				path = key
+					.replace(/\.([^.]*)/g, '/$1')
+					.replace(/\[['"]?([^'"\]]*)['"]?\]/g, '/$1');
+			parsed[key] = path.split('/');
+		}
+		return parsed;
+	},
+
+	_getProperty: function(path, offset) {
+		var obj = this.object;
+		for (var i = 0, l = path.length - (offset || 0); i < l && obj; i++) {
+			obj = obj[path[i]];
+		}
+		return obj;
+	},
+
+	_setProperty: function(path, value) {
+		var dest = this._getProperty(path, 1);
+		if (dest) {
+			dest[path[path.length - 1]] = value;
+		}
+	}
+});
+
 var Http = {
-	request: function(method, url, callback, async) {
-		async = (async === undefined) ? true : async;
-		var xhr = new (window.ActiveXObject || XMLHttpRequest)(
-					'Microsoft.XMLHTTP');
-		xhr.open(method.toUpperCase(), url, async);
-		if ('overrideMimeType' in xhr)
-			xhr.overrideMimeType('text/plain');
-		xhr.onreadystatechange = function() {
-			if (xhr.readyState === 4) {
-				var status = xhr.status;
-				if (status === 0 || status === 200) {
-					callback.call(xhr, xhr.responseText);
-				} else {
-					throw new Error('Could not load ' + url + ' (Error '
-							+ status + ')');
-				}
+	request: function(options) {
+		var xhr = new self.XMLHttpRequest();
+		xhr.open((options.method || 'get').toUpperCase(), options.url,
+				Base.pick(options.async, true));
+		if (options.mimeType)
+			xhr.overrideMimeType(options.mimeType);
+		xhr.onload = function() {
+			var status = xhr.status;
+			if (status === 0 || status === 200) {
+				if (options.onLoad) {
+					options.onLoad.call(xhr, xhr.responseText);
+				}
+			} else {
+				xhr.onerror();
+			}
+		};
+		xhr.onerror = function() {
+			var status = xhr.status,
+				message = 'Could not load "' + options.url + '" (Status: '
+						+ status + ')';
+			if (options.onError) {
+				options.onError(message, status);
+			} else {
+				throw new Error(message);
 			}
 		};
 		return xhr.send(null);
@@ -11686,6 +13973,8 @@
 	canvases: [],
 
 	getCanvas: function(width, height) {
+		if (!window)
+			return null;
 		var canvas,
 			clear = true;
 		if (typeof width === 'object') {
@@ -11696,8 +13985,13 @@
 			canvas = this.canvases.pop();
 		} else {
 			canvas = document.createElement('canvas');
+			clear = false;
 		}
 		var ctx = canvas.getContext('2d');
+		if (!ctx) {
+			throw new Error('Canvas ' + canvas +
+					' is unable to provide a 2D context.');
+		}
 		if (canvas.width === width && canvas.height === height) {
 			if (clear)
 				ctx.clearRect(0, 0, width + 1, height + 1);
@@ -11710,13 +14004,16 @@
 	},
 
 	getContext: function(width, height) {
-		return this.getCanvas(width, height).getContext('2d');
+		var canvas = this.getCanvas(width, height);
+		return canvas ? canvas.getContext('2d') : null;
 	},
 
 	release: function(obj) {
-		var canvas = obj.canvas ? obj.canvas : obj;
-		canvas.getContext('2d').restore();
-		this.canvases.push(canvas);
+		var canvas = obj && obj.canvas ? obj.canvas : obj;
+		if (canvas && canvas.getContext) {
+			canvas.getContext('2d').restore();
+			this.canvases.push(canvas);
+		}
 	}
 };
 
@@ -11907,24 +14204,27 @@
 	}, {});
 
 	var ctx = CanvasProvider.getContext(1, 1);
-	Base.each(modes, function(func, mode) {
-		var darken = mode === 'darken',
-			ok = false;
-		ctx.save();
-		try {
-			ctx.fillStyle = darken ? '#300' : '#a00';
-			ctx.fillRect(0, 0, 1, 1);
-			ctx.globalCompositeOperation = mode;
-			if (ctx.globalCompositeOperation === mode) {
-				ctx.fillStyle = darken ? '#a00' : '#300';
+	if (ctx) {
+		Base.each(modes, function(func, mode) {
+			var darken = mode === 'darken',
+				ok = false;
+			ctx.save();
+			try {
+				ctx.fillStyle = darken ? '#300' : '#a00';
 				ctx.fillRect(0, 0, 1, 1);
-				ok = ctx.getImageData(0, 0, 1, 1).data[0] !== darken ? 170 : 51;
-			}
-		} catch (e) {}
-		ctx.restore();
-		nativeModes[mode] = ok;
-	});
-	CanvasProvider.release(ctx);
+				ctx.globalCompositeOperation = mode;
+				if (ctx.globalCompositeOperation === mode) {
+					ctx.fillStyle = darken ? '#a00' : '#300';
+					ctx.fillRect(0, 0, 1, 1);
+					ok = ctx.getImageData(0, 0, 1, 1).data[0] !== darken
+							? 170 : 51;
+				}
+			} catch (e) {}
+			ctx.restore();
+			nativeModes[mode] = ok;
+		});
+		CanvasProvider.release(ctx);
+	}
 
 	this.process = function(mode, srcContext, dstContext, alpha, offset) {
 		var srcCanvas = srcContext.canvas,
@@ -11968,8 +14268,58 @@
 	};
 };
 
-var SVGStyles = Base.each({
+var SvgElement = new function() {
+	var svg = 'http://www.w3.org/2000/svg',
+		xmlns = 'http://www.w3.org/2000/xmlns',
+		xlink = 'http://www.w3.org/1999/xlink',
+		attributeNamespace = {
+			href: xlink,
+			xlink: xmlns,
+			xmlns: xmlns + '/',
+			'xmlns:xlink': xmlns + '/'
+		};
+
+	function create(tag, attributes, formatter) {
+		return set(document.createElementNS(svg, tag), attributes, formatter);
+	}
+
+	function get(node, name) {
+		var namespace = attributeNamespace[name],
+			value = namespace
+				? node.getAttributeNS(namespace, name)
+				: node.getAttribute(name);
+		return value === 'null' ? null : value;
+	}
+
+	function set(node, attributes, formatter) {
+		for (var name in attributes) {
+			var value = attributes[name],
+				namespace = attributeNamespace[name];
+			if (typeof value === 'number' && formatter)
+				value = formatter.number(value);
+			if (namespace) {
+				node.setAttributeNS(namespace, name, value);
+			} else {
+				node.setAttribute(name, value);
+			}
+		}
+		return node;
+	}
+
+	return {
+		svg: svg,
+		xmlns: xmlns,
+		xlink: xlink,
+
+		create: create,
+		get: get,
+		set: set
+	};
+};
+
+var SvgStyles = Base.each({
 	fillColor: ['fill', 'color'],
+	fillRule: ['fill-rule', 'string'],
 	strokeColor: ['stroke', 'color'],
 	strokeWidth: ['stroke-width', 'number'],
 	strokeCap: ['stroke-linecap', 'string'],
@@ -11995,7 +14345,7 @@
 		right: 'end'
 	}],
 	opacity: ['opacity', 'number'],
-	blendMode: ['mix-blend-mode', 'string']
+	blendMode: ['mix-blend-mode', 'style']
 }, function(entry, key) {
 	var part = Base.capitalize(key),
 		lookup = entry[2];
@@ -12013,57 +14363,42 @@
 	};
 }, {});
 
-var SVGNamespaces = {
-	href: 'http://www.w3.org/1999/xlink',
-	xlink: 'http://www.w3.org/2000/xmlns'
-};
-
 new function() {
 	var formatter;
 
-	function setAttributes(node, attrs) {
-		for (var key in attrs) {
-			var val = attrs[key],
-				namespace = SVGNamespaces[key];
-			if (typeof val === 'number')
-				val = formatter.number(val);
-			if (namespace) {
-				node.setAttributeNS(namespace, key, val);
-			} else {
-				node.setAttribute(key, val);
-			}
-		}
-		return node;
-	}
-
-	function createElement(tag, attrs) {
-		return setAttributes(
-			document.createElementNS('http://www.w3.org/2000/svg', tag), attrs);
-	}
-
 	function getTransform(matrix, coordinates, center) {
 		var attrs = new Base(),
 			trans = matrix.getTranslation();
 		if (coordinates) {
-			matrix = matrix.shiftless();
-			var point = matrix._inverseTransform(trans);
+			var point;
+			if (matrix.isInvertible()) {
+				matrix = matrix._shiftless();
+				point = matrix._inverseTransform(trans);
+				trans = null;
+			} else {
+				point = new Point();
+			}
 			attrs[center ? 'cx' : 'x'] = point.x;
 			attrs[center ? 'cy' : 'y'] = point.y;
-			trans = null;
 		}
 		if (!matrix.isIdentity()) {
 			var decomposed = matrix.decompose();
-			if (decomposed && !decomposed.shearing) {
+			if (decomposed) {
 				var parts = [],
 					angle = decomposed.rotation,
-					scale = decomposed.scaling;
+					scale = decomposed.scaling,
+					skew = decomposed.skewing;
 				if (trans && !trans.isZero())
 					parts.push('translate(' + formatter.point(trans) + ')');
+				if (angle)
+					parts.push('rotate(' + formatter.number(angle) + ')');
 				if (!Numerical.isZero(scale.x - 1)
 						|| !Numerical.isZero(scale.y - 1))
 					parts.push('scale(' + formatter.point(scale) +')');
-				if (angle)
-					parts.push('rotate(' + formatter.number(angle) + ')');
+				if (skew.x)
+					parts.push('skewX(' + formatter.number(skew.x) + ')');
+				if (skew.y)
+					parts.push('skewY(' + formatter.number(skew.y) + ')');
 				attrs.transform = parts.join(' ');
 			} else {
 				attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';
@@ -12075,16 +14410,16 @@
 	function exportGroup(item, options) {
 		var attrs = getTransform(item._matrix),
 			children = item._children;
-		var node = createElement('g', attrs);
+		var node = SvgElement.create('g', attrs, formatter);
 		for (var i = 0, l = children.length; i < l; i++) {
 			var child = children[i];
 			var childNode = exportSVG(child, options);
 			if (childNode) {
 				if (child.isClipMask()) {
-					var clip = createElement('clipPath');
+					var clip = SvgElement.create('clipPath');
 					clip.appendChild(childNode);
 					setDefinition(child, clip, 'clip');
-					setAttributes(node, {
+					SvgElement.set(node, {
 						'clip-path': 'url(#' + clip.id + ')'
 					});
 				} else {
@@ -12103,9 +14438,9 @@
 		attrs.y -= size.height / 2;
 		attrs.width = size.width;
 		attrs.height = size.height;
-		attrs.href = options.embedImages === false && image && image.src
+		attrs.href = options.embedImages == false && image && image.src
 				|| item.toDataURL();
-		return createElement('image', attrs);
+		return SvgElement.create('image', attrs, formatter);
 	}
 
 	function exportPath(item, options) {
@@ -12116,33 +14451,33 @@
 				return exportShape(shape, options);
 		}
 		var segments = item._segments,
+			length = segments.length,
 			type,
 			attrs = getTransform(item._matrix);
-		if (segments.length === 0)
-			return null;
-		if (matchShapes && !item.hasHandles()) {
-			if (segments.length >= 3) {
+		if (matchShapes && length >= 2 && !item.hasHandles()) {
+			if (length > 2) {
 				type = item._closed ? 'polygon' : 'polyline';
 				var parts = [];
-				for(var i = 0, l = segments.length; i < l; i++)
+				for (var i = 0; i < length; i++) {
 					parts.push(formatter.point(segments[i]._point));
+				}
 				attrs.points = parts.join(' ');
 			} else {
 				type = 'line';
-				var first = segments[0]._point,
-					last = segments[segments.length - 1]._point;
+				var start = segments[0]._point,
+					end = segments[1]._point;
 				attrs.set({
-					x1: first.x,
-					y1: first.y,
-					x2: last.x,
-					y2: last.y
+					x1: start.x,
+					y1: start.y,
+					x2: end.x,
+					y2: end.y
 				});
 			}
 		} else {
 			type = 'path';
 			attrs.d = item.getPathData(null, options.precision);
 		}
-		return createElement(type, attrs);
+		return SvgElement.create(type, attrs, formatter);
 	}
 
 	function exportShape(item) {
@@ -12169,7 +14504,7 @@
 				attrs.ry = radius.height;
 			}
 		}
-		return createElement(type, attrs);
+		return SvgElement.create(type, attrs, formatter);
 	}
 
 	function exportCompoundPath(item, options) {
@@ -12177,29 +14512,29 @@
 		var data = item.getPathData(null, options.precision);
 		if (data)
 			attrs.d = data;
-		return createElement('path', attrs);
-	}
-
-	function exportPlacedSymbol(item, options) {
+		return SvgElement.create('path', attrs, formatter);
+	}
+
+	function exportSymbolItem(item, options) {
 		var attrs = getTransform(item._matrix, true),
-			symbol = item.getSymbol(),
-			symbolNode = getDefinition(symbol, 'symbol'),
-			definition = symbol.getDefinition(),
-			bounds = definition.getBounds();
-		if (!symbolNode) {
-			symbolNode = createElement('symbol', {
+			definition = item._definition,
+			node = getDefinition(definition, 'symbol'),
+			definitionItem = definition._item,
+			bounds = definitionItem.getStrokeBounds();
+		if (!node) {
+			node = SvgElement.create('symbol', {
 				viewBox: formatter.rectangle(bounds)
 			});
-			symbolNode.appendChild(exportSVG(definition, options));
-			setDefinition(symbol, symbolNode, 'symbol');
-		}
-		attrs.href = '#' + symbolNode.id;
+			node.appendChild(exportSVG(definitionItem, options));
+			setDefinition(definition, node, 'symbol');
+		}
+		attrs.href = '#' + node.id;
 		attrs.x += bounds.x;
 		attrs.y += bounds.y;
-		attrs.width = formatter.number(bounds.width);
-		attrs.height = formatter.number(bounds.height);
+		attrs.width = bounds.width;
+		attrs.height = bounds.height;
 		attrs.overflow = 'visible';
-		return createElement('use', attrs);
+		return SvgElement.create('use', attrs, formatter);
 	}
 
 	function exportGradient(color) {
@@ -12207,8 +14542,8 @@
 		if (!gradientNode) {
 			var gradient = color.getGradient(),
 				radial = gradient._radial,
-				origin = color.getOrigin().transform(),
-				destination = color.getDestination().transform(),
+				origin = color.getOrigin(),
+				destination = color.getDestination(),
 				attrs;
 			if (radial) {
 				attrs = {
@@ -12218,7 +14553,6 @@
 				};
 				var highlight = color.getHighlight();
 				if (highlight) {
-					highlight = highlight.transform();
 					attrs.fx = highlight.x;
 					attrs.fy = highlight.y;
 				}
@@ -12231,20 +14565,23 @@
 				};
 			}
 			attrs.gradientUnits = 'userSpaceOnUse';
-			gradientNode = createElement(
-					(radial ? 'radial' : 'linear') + 'Gradient', attrs);
+			gradientNode = SvgElement.create((radial ? 'radial' : 'linear')
+					+ 'Gradient', attrs, formatter);
 			var stops = gradient._stops;
 			for (var i = 0, l = stops.length; i < l; i++) {
 				var stop = stops[i],
 					stopColor = stop._color,
-					alpha = stopColor.getAlpha();
+					alpha = stopColor.getAlpha(),
+					offset = stop._offset;
 				attrs = {
-					offset: stop._rampPoint,
-					'stop-color': stopColor.toCSS(true)
+					offset: offset == null ? i / (l - 1) : offset
 				};
+				if (stopColor)
+					attrs['stop-color'] = stopColor.toCSS(true);
 				if (alpha < 1)
 					attrs['stop-opacity'] = alpha;
-				gradientNode.appendChild(createElement('stop', attrs));
+				gradientNode.appendChild(
+						SvgElement.create('stop', attrs, formatter));
 			}
 			setDefinition(color, gradientNode, 'color');
 		}
@@ -12252,7 +14589,8 @@
 	}
 
 	function exportText(item) {
-		var node = createElement('text', getTransform(item._matrix, true));
+		var node = SvgElement.create('text', getTransform(item._matrix, true),
+				formatter);
 		node.textContent = item._content;
 		return node;
 	}
@@ -12264,18 +14602,19 @@
 		Path: exportPath,
 		Shape: exportShape,
 		CompoundPath: exportCompoundPath,
-		PlacedSymbol: exportPlacedSymbol,
+		SymbolItem: exportSymbolItem,
 		PointText: exportText
 	};
 
 	function applyStyle(item, node, isRoot) {
 		var attrs = {},
-			parent = !isRoot && item.getParent();
+			parent = !isRoot && item.getParent(),
+			style = [];
 
 		if (item._name != null)
 			attrs.id = item._name;
 
-		Base.each(SVGStyles, function(entry) {
+		Base.each(SvgStyles, function(entry) {
 			var get = entry.get,
 				type = entry.type,
 				value = item[get]();
@@ -12287,44 +14626,46 @@
 					if (alpha < 1)
 						attrs[entry.attribute + '-opacity'] = alpha;
 				}
-				attrs[entry.attribute] = value == null
-					? 'none'
-					: type === 'number'
-						? formatter.number(value)
-						: type === 'color'
-							? value.gradient
+				if (type === 'style') {
+					style.push(entry.attribute + ': ' + value);
+				} else {
+					attrs[entry.attribute] = value == null ? 'none'
+							: type === 'color' ? value.gradient
 								? exportGradient(value, item)
 								: value.toCSS(true)
-							: type === 'array'
-								? value.join(',')
-								: type === 'lookup'
-									? entry.toSVG[value]
-									: value;
+							: type === 'array' ? value.join(',')
+							: type === 'lookup' ? entry.toSVG[value]
+							: value;
+				}
 			}
 		});
 
+		if (style.length)
+			attrs.style = style.join(';');
+
 		if (attrs.opacity === 1)
 			delete attrs.opacity;
 
 		if (!item._visible)
 			attrs.visibility = 'hidden';
 
-		return setAttributes(node, attrs);
+		return SvgElement.set(node, attrs, formatter);
 	}
 
 	var definitions;
 	function getDefinition(item, type) {
 		if (!definitions)
 			definitions = { ids: {}, svgs: {} };
-		return item && definitions.svgs[type + '-' + item._id];
+		return item && definitions.svgs[type + '-'
+				+ (item._id || item.__id || (item.__id = UID.get('svg')))];
 	}
 
 	function setDefinition(item, node, type) {
 		if (!definitions)
 			getDefinition();
-		var id = definitions.ids[type] = (definitions.ids[type] || 0) + 1;
-		node.id = type + '-' + id;
-		definitions.svgs[type + '-' + item._id] = node;
+		var typeId = definitions.ids[type] = (definitions.ids[type] || 0) + 1;
+		node.id = type + '-' + typeId;
+		definitions.svgs[type + '-' + (item._id || item.__id)] = node;
 	}
 
 	function exportDefinitions(node, options) {
@@ -12335,10 +14676,10 @@
 			for (var i in definitions.svgs) {
 				if (!defs) {
 					if (!svg) {
-						svg = createElement('svg');
+						svg = SvgElement.create('svg');
 						svg.appendChild(node);
 					}
-					defs = svg.insertBefore(createElement('defs'),
+					defs = svg.insertBefore(SvgElement.create('defs'),
 							svg.firstChild);
 				}
 				defs.appendChild(definitions.svgs[i]);
@@ -12346,7 +14687,7 @@
 			definitions = null;
 		}
 		return options.asString
-				? new XMLSerializer().serializeToString(svg)
+				? new self.XMLSerializer().serializeToString(svg)
 				: svg;
 	}
 
@@ -12381,25 +14722,37 @@
 	Project.inject({
 		exportSVG: function(options) {
 			options = setOptions(options);
-			var layers = this.layers,
+			var children = this._children,
 				view = this.getView(),
-				size = view.getViewSize(),
-				node = createElement('svg', {
-					x: 0,
-					y: 0,
-					width: size.width,
-					height: size.height,
+				bounds = Base.pick(options.bounds, 'view'),
+				mx = options.matrix || bounds === 'view' && view._matrix,
+				matrix = mx && Matrix.read([mx]),
+				rect = bounds === 'view'
+					? new Rectangle([0, 0], view.getViewSize())
+					: bounds === 'content'
+						? Item._getBounds(children, matrix, { stroke: true })
+							.rect
+						: Rectangle.read([bounds], 0, { readNull: true }),
+				attrs = {
 					version: '1.1',
-					xmlns: 'http://www.w3.org/2000/svg',
-					'xmlns:xlink': 'http://www.w3.org/1999/xlink'
-				}),
-				parent = node,
-				matrix = view._matrix;
-			if (!matrix.isIdentity())
-				parent = node.appendChild(
-						createElement('g', getTransform(matrix)));
-			for (var i = 0, l = layers.length; i < l; i++)
-				parent.appendChild(exportSVG(layers[i], options, true));
+					xmlns: SvgElement.svg,
+					'xmlns:xlink': SvgElement.xlink,
+				};
+			if (rect) {
+				attrs.width = rect.width;
+				attrs.height = rect.height;
+				if (rect.x || rect.x === 0 || rect.y || rect.y === 0)
+					attrs.viewBox = formatter.rectangle(rect);
+			}
+			var node = SvgElement.create('svg', attrs, formatter),
+				parent = node;
+			if (matrix && !matrix.isIdentity()) {
+				parent = node.appendChild(SvgElement.create('g',
+						getTransform(matrix), formatter));
+			}
+			for (var i = 0, l = children.length; i < l; i++) {
+				parent.appendChild(exportSVG(children[i], options, true));
+			}
 			return exportDefinitions(node, options);
 		}
 	});
@@ -12407,83 +14760,81 @@
 
 new function() {
 
-	function getValue(node, name, isString, allowNull) {
-		var namespace = SVGNamespaces[name],
-			value = namespace
-				? node.getAttributeNS(namespace, name)
-				: node.getAttribute(name);
-		if (value === 'null')
-			value = null;
-		return value == null
+	var definitions = {},
+		rootSize;
+
+	function getValue(node, name, isString, allowNull, allowPercent,
+			defaultValue) {
+		var value = SvgElement.get(node, name) || defaultValue,
+			res = value == null
 				? allowNull
 					? null
-					: isString
-						? ''
-						: 0
+					: isString ? '' : 0
 				: isString
 					? value
 					: parseFloat(value);
-	}
-
-	function getPoint(node, x, y, allowNull) {
-		x = getValue(node, x, false, allowNull);
-		y = getValue(node, y, false, allowNull);
+		return /%\s*$/.test(value)
+			? (res / 100) * (allowPercent ? 1
+				: rootSize[/x|^width/.test(name) ? 'width' : 'height'])
+			: res;
+	}
+
+	function getPoint(node, x, y, allowNull, allowPercent, defaultX, defaultY) {
+		x = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX);
+		y = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY);
 		return allowNull && (x == null || y == null) ? null
 				: new Point(x, y);
 	}
 
-	function getSize(node, w, h, allowNull) {
-		w = getValue(node, w, false, allowNull);
-		h = getValue(node, h, false, allowNull);
+	function getSize(node, w, h, allowNull, allowPercent) {
+		w = getValue(node, w || 'width', false, allowNull, allowPercent);
+		h = getValue(node, h || 'height', false, allowNull, allowPercent);
 		return allowNull && (w == null || h == null) ? null
 				: new Size(w, h);
 	}
 
 	function convertValue(value, type, lookup) {
-		return value === 'none'
-				? null
-				: type === 'number'
-					? parseFloat(value)
-					: type === 'array'
-						? value ? value.split(/[\s,]+/g).map(parseFloat) : []
-						: type === 'color'
-							? getDefinition(value) || value
-							: type === 'lookup'
-								? lookup[value]
-								: value;
+		return value === 'none' ? null
+				: type === 'number' ? parseFloat(value)
+				: type === 'array' ?
+					value ? value.split(/[\s,]+/g).map(parseFloat) : []
+				: type === 'color' ? getDefinition(value) || value
+				: type === 'lookup' ? lookup[value]
+				: value;
 	}
 
 	function importGroup(node, type, options, isRoot) {
 		var nodes = node.childNodes,
 			isClip = type === 'clippath',
+			isDefs = type === 'defs',
 			item = new Group(),
 			project = item._project,
 			currentStyle = project._currentStyle,
 			children = [];
-		if (!isClip) {
+		if (!isClip && !isDefs) {
 			item = applyAttributes(item, node, isRoot);
 			project._currentStyle = item._style.clone();
 		}
 		if (isRoot) {
 			var defs = node.querySelectorAll('defs');
 			for (var i = 0, l = defs.length; i < l; i++) {
-				importSVG(defs[i], options, false);
+				importNode(defs[i], options, false);
 			}
 		}
 		for (var i = 0, l = nodes.length; i < l; i++) {
 			var childNode = nodes[i],
 				child;
 			if (childNode.nodeType === 1
-					&& childNode.nodeName.toLowerCase() !== 'defs'
-					&& (child = importSVG(childNode, options, false))
-					&& !(child instanceof Symbol))
+					&& !/^defs$/i.test(childNode.nodeName)
+					&& (child = importNode(childNode, options, false))
+					&& !(child instanceof SymbolDefinition))
 				children.push(child);
 		}
 		item.addChildren(children);
 		if (isClip)
 			item = applyAttributes(item.reduce(), node, isRoot);
 		project._currentStyle = currentStyle;
-		if (isClip || type === 'defs') {
+		if (isClip || isDefs) {
 			item.remove();
 			item = null;
 		}
@@ -12505,19 +14856,19 @@
 	}
 
 	function importPath(node) {
-		var data = node.getAttribute('d'),
-			param = { pathData: data };
-		return (data.match(/m/gi) || []).length > 1 || /z\S+/i.test(data)
-				? new CompoundPath(param)
-				: new Path(param);
+		return PathItem.create(node.getAttribute('d'));
 	}
 
 	function importGradient(node, type) {
 		var id = (getValue(node, 'href', true) || '').substring(1),
-			isRadial = type === 'radialgradient',
+			radial = type === 'radialgradient',
 			gradient;
 		if (id) {
 			gradient = definitions[id].getGradient();
+			if (gradient._radial ^ radial) {
+				gradient = gradient.clone();
+				gradient._radial = radial;
+			}
 		} else {
 			var nodes = node.childNodes,
 				stops = [];
@@ -12526,19 +14877,26 @@
 				if (child.nodeType === 1)
 					stops.push(applyAttributes(new GradientStop(), child));
 			}
-			gradient = new Gradient(stops, isRadial);
-		}
-		var origin, destination, highlight;
-		if (isRadial) {
-			origin = getPoint(node, 'cx', 'cy');
-			destination = origin.add(getValue(node, 'r'), 0);
-			highlight = getPoint(node, 'fx', 'fy', true);
+			gradient = new Gradient(stops, radial);
+		}
+		var origin, destination, highlight,
+			scaleToBounds = getValue(node, 'gradientUnits', true) !==
+				'userSpaceOnUse';
+		if (radial) {
+			origin = getPoint(node, 'cx', 'cy', false, scaleToBounds,
+				'50%', '50%');
+			destination = origin.add(
+				getValue(node, 'r', false, false, scaleToBounds, '50%'), 0);
+			highlight = getPoint(node, 'fx', 'fy', true, scaleToBounds);
 		} else {
-			origin = getPoint(node, 'x1', 'y1');
-			destination = getPoint(node, 'x2', 'y2');
-		}
-		applyAttributes(
-			new Color(gradient, origin, destination, highlight), node);
+			origin = getPoint(node, 'x1', 'y1', false, scaleToBounds,
+				'0%', '0%');
+			destination = getPoint(node, 'x2', 'y2', false, scaleToBounds,
+				'100%', '0%');
+		}
+		var color = applyAttributes(
+				new Color(gradient, origin, destination, highlight), node);
+		color._scaleToBounds = scaleToBounds;
 		return null;
 	}
 
@@ -12547,17 +14905,8 @@
 			var nodes = node.childNodes;
 			for (var i = 0, l = nodes.length; i < l; i++) {
 				var child = nodes[i];
-				if (child.nodeType === 1) {
-					var next = child.nextSibling;
-					document.body.appendChild(child);
-					var item = importSVG(child, options, isRoot);
-					if (next) {
-						node.insertBefore(child, next);
-					} else {
-						node.appendChild(child);
-					}
-					return item;
-				}
+				if (child.nodeType === 1)
+					return importNode(child, options, isRoot);
 			}
 		},
 		g: importGroup,
@@ -12572,17 +14921,17 @@
 		image: function (node) {
 			var raster = new Raster(getValue(node, 'href', true));
 			raster.on('load', function() {
-				var size = getSize(node, 'width', 'height');
+				var size = getSize(node);
 				this.setSize(size);
-				var center = this._matrix._transformPoint(
-						getPoint(node, 'x', 'y').add(size.divide(2)));
-				this.translate(center);
+				var center = getPoint(node).add(size.divide(2));
+				this._matrix.append(new Matrix().translate(center));
 			});
 			return raster;
 		},
 
 		symbol: function(node, type, options, isRoot) {
-			return new Symbol(importGroup(node, type, options, isRoot), true);
+			return new SymbolDefinition(
+					importGroup(node, type, options, isRoot), true);
 		},
 
 		defs: importGroup,
@@ -12590,16 +14939,17 @@
 		use: function(node) {
 			var id = (getValue(node, 'href', true) || '').substring(1),
 				definition = definitions[id],
-				point = getPoint(node, 'x', 'y');
+				point = getPoint(node);
 			return definition
-					? definition instanceof Symbol
+					? definition instanceof SymbolDefinition
 						? definition.place(point)
 						: definition.clone().translate(point)
 					: null;
 		},
 
 		circle: function(node) {
-			return new Shape.Circle(getPoint(node, 'cx', 'cy'),
+			return new Shape.Circle(
+					getPoint(node, 'cx', 'cy'),
 					getValue(node, 'r'));
 		},
 
@@ -12611,77 +14961,89 @@
 		},
 
 		rect: function(node) {
-			var point = getPoint(node, 'x', 'y'),
-				size = getSize(node, 'width', 'height'),
-				radius = getSize(node, 'rx', 'ry');
-			return new Shape.Rectangle(new Rectangle(point, size), radius);
-		},
+			return new Shape.Rectangle(new Rectangle(
+						getPoint(node),
+						getSize(node)
+					), getSize(node, 'rx', 'ry'));
+			},
 
 		line: function(node) {
-			return new Path.Line(getPoint(node, 'x1', 'y1'),
+			return new Path.Line(
+					getPoint(node, 'x1', 'y1'),
 					getPoint(node, 'x2', 'y2'));
 		},
 
 		text: function(node) {
-			var text = new PointText(getPoint(node, 'x', 'y')
-					.add(getPoint(node, 'dx', 'dy')));
+			var text = new PointText(getPoint(node).add(
+					getPoint(node, 'dx', 'dy')));
 			text.setContent(node.textContent.trim() || '');
 			return text;
-		}
+		},
+
+		switch: importGroup
 	};
 
 	function applyTransform(item, value, name, node) {
-		var transforms = (node.getAttribute(name) || '').split(/\)\s*/g),
-			matrix = new Matrix();
-		for (var i = 0, l = transforms.length; i < l; i++) {
-			var transform = transforms[i];
-			if (!transform)
-				break;
-			var parts = transform.split(/\(\s*/),
-				command = parts[0],
-				v = parts[1].split(/[\s,]+/g);
-			for (var j = 0, m = v.length; j < m; j++)
-				v[j] = parseFloat(v[j]);
-			switch (command) {
-			case 'matrix':
-				matrix.concatenate(
-						new Matrix(v[0], v[1], v[2], v[3], v[4], v[5]));
-				break;
-			case 'rotate':
-				matrix.rotate(v[0], v[1], v[2]);
-				break;
-			case 'translate':
-				matrix.translate(v[0], v[1]);
-				break;
-			case 'scale':
-				matrix.scale(v);
-				break;
-			case 'skewX':
-				matrix.skew(v[0], 0);
-				break;
-			case 'skewY':
-				matrix.skew(0, v[0]);
-				break;
-			}
-		}
-		item.transform(matrix);
+		if (item.transform) {
+			var transforms = (node.getAttribute(name) || '').split(/\)\s*/g),
+				matrix = new Matrix();
+			for (var i = 0, l = transforms.length; i < l; i++) {
+				var transform = transforms[i];
+				if (!transform)
+					break;
+				var parts = transform.split(/\(\s*/),
+					command = parts[0],
+					v = parts[1].split(/[\s,]+/g);
+				for (var j = 0, m = v.length; j < m; j++)
+					v[j] = parseFloat(v[j]);
+				switch (command) {
+				case 'matrix':
+					matrix.append(
+							new Matrix(v[0], v[1], v[2], v[3], v[4], v[5]));
+					break;
+				case 'rotate':
+					matrix.rotate(v[0], v[1] || 0, v[2] || 0);
+					break;
+				case 'translate':
+					matrix.translate(v[0], v[1] || 0);
+					break;
+				case 'scale':
+					matrix.scale(v);
+					break;
+				case 'skewX':
+					matrix.skew(v[0], 0);
+					break;
+				case 'skewY':
+					matrix.skew(0, v[0]);
+					break;
+				}
+			}
+			item.transform(matrix);
+		}
 	}
 
 	function applyOpacity(item, value, name) {
-		var color = item[name === 'fill-opacity' ? 'getFillColor'
-				: 'getStrokeColor']();
+		var key = name === 'fill-opacity' ? 'getFillColor' : 'getStrokeColor',
+			color = item[key] && item[key]();
 		if (color)
 			color.setAlpha(parseFloat(value));
 	}
 
-	var attributes = Base.set(Base.each(SVGStyles, function(entry) {
+	var attributes = Base.set(Base.each(SvgStyles, function(entry) {
 		this[entry.attribute] = function(item, value) {
-			item[entry.set](convertValue(value, entry.type, entry.fromSVG));
-			if (entry.type === 'color' && item instanceof Shape) {
-				var color = item[entry.get]();
-				if (color)
-					color.transform(new Matrix().translate(
-							item.getPosition(true).negate()));
+			if (item[entry.set]) {
+				item[entry.set](convertValue(value, entry.type, entry.fromSVG));
+				if (entry.type === 'color') {
+					var color = item[entry.get]();
+					if (color) {
+						if (color._scaleToBounds) {
+							var bounds = item.getBounds();
+							color.transform(new Matrix()
+								.translate(bounds.getPoint())
+								.scale(bounds.getSize()));
+						}
+					}
+				}
 			}
 		};
 	}, {}), {
@@ -12711,11 +15073,13 @@
 		'stroke-opacity': applyOpacity,
 
 		visibility: function(item, value) {
-			item.setVisible(value === 'visible');
+			if (item.setVisible)
+				item.setVisible(value === 'visible');
 		},
 
 		display: function(item, value) {
-			item.setVisible(value !== null);
+			if (item.setVisible)
+				item.setVisible(value !== null);
 		},
 
 		'stop-color': function(item, value) {
@@ -12729,29 +15093,35 @@
 		},
 
 		offset: function(item, value) {
-			var percentage = value.match(/(.*)%$/);
-			item.setRampPoint(percentage
-					? percentage[1] / 100
-					: parseFloat(value));
+			if (item.setOffset) {
+				var percent = value.match(/(.*)%$/);
+				item.setOffset(percent ? percent[1] / 100 : parseFloat(value));
+			}
 		},
 
 		viewBox: function(item, value, name, node, styles) {
 			var rect = new Rectangle(convertValue(value, 'array')),
-				size = getSize(node, 'width', 'height', true);
+				size = getSize(node, null, null, true),
+				group,
+				matrix;
 			if (item instanceof Group) {
-				var scale = size ? rect.getSize().divide(size) : 1,
-					matrix = new Matrix().translate(rect.getPoint()).scale(scale);
-				item.transform(matrix.inverted());
-			} else if (item instanceof Symbol) {
+				var scale = size ? size.divide(rect.getSize()) : 1,
+				matrix = new Matrix().scale(scale)
+						.translate(rect.getPoint().negate());
+				group = item;
+			} else if (item instanceof SymbolDefinition) {
 				if (size)
 					rect.setSize(size);
-				var clip = getAttribute(node, 'overflow', styles) != 'visible',
-					group = item._definition;
-				if (clip && !rect.contains(group.getBounds())) {
-					clip = new Shape.Rectangle(rect).transform(group._matrix);
+				group = item._item;
+			}
+			if (group)  {
+				if (getAttribute(node, 'overflow', styles) !== 'visible') {
+					var clip = new Shape.Rectangle(rect);
 					clip.setClipMask(true);
 					group.addChild(clip);
 				}
+				if (matrix)
+					group.transform(matrix);
 			}
 		}
 	});
@@ -12759,94 +15129,77 @@
 	function getAttribute(node, name, styles) {
 		var attr = node.attributes[name],
 			value = attr && attr.value;
-		if (!value) {
+		if (!value && node.style) {
 			var style = Base.camelize(name);
 			value = node.style[style];
 			if (!value && styles.node[style] !== styles.parent[style])
 				value = styles.node[style];
 		}
-		return !value
-				? undefined
-				: value === 'none'
-					? null
-					: value;
+		return !value ? undefined
+				: value === 'none' ? null
+				: value;
 	}
 
 	function applyAttributes(item, node, isRoot) {
-		var styles = {
-			node: DomElement.getStyles(node) || {},
-			parent: !isRoot && DomElement.getStyles(node.parentNode) || {}
-		};
+		var parent = node.parentNode,
+			styles = {
+				node: DomElement.getStyles(node) || {},
+				parent: !isRoot && !/^defs$/i.test(parent.tagName)
+						&& DomElement.getStyles(parent) || {}
+			};
 		Base.each(attributes, function(apply, name) {
 			var value = getAttribute(node, name, styles);
-			if (value !== undefined)
-				item = Base.pick(apply(item, value, name, node, styles), item);
+			item = value !== undefined
+					&& apply(item, value, name, node, styles) || item;
 		});
 		return item;
 	}
 
-	var definitions = {};
 	function getDefinition(value) {
-		var match = value && value.match(/\((?:#|)([^)']+)/);
-		return match && definitions[match[1]];
-	}
-
-	function importSVG(source, options, isRoot) {
-		if (!source)
-			return null;
-		if (!options) {
-			options = {};
-		} else if (typeof options === 'function') {
-			options = { onLoad: options };
-		}
-
-		var node = source,
-			scope = paper;
-
-		function onLoadCallback(svg) {
-			paper = scope;
-			var item = importSVG(svg, options, isRoot),
-				onLoad = options.onLoad,
-				view = scope.project && scope.getView();
-			if (onLoad)
-				onLoad.call(this, item);
-			view.update();
-		}
-
-		if (isRoot) {
-			if (typeof source === 'string' && !/^.*</.test(source)) {
-				node = document.getElementById(source);
-				if (node) {
-					source = null;
-				} else {
-					return Http.request('get', source, onLoadCallback);
-				}
-			} else if (typeof File !== 'undefined' && source instanceof File) {
-				var reader = new FileReader();
-				reader.onload = function() {
-					onLoadCallback(reader.result);
-				};
-				return reader.readAsText(source);
-			}
-		}
-
-		if (typeof source === 'string')
-			node = new DOMParser().parseFromString(source, 'image/svg+xml');
-		if (!node.nodeName)
-			throw new Error('Unsupported SVG source: ' + source);
+		var match = value && value.match(/\((?:["'#]*)([^"')]+)/),
+			name = match && match[1],
+			res = name && definitions[window
+					? name.replace(window.location.href.split('#')[0] + '#', '')
+					: name];
+		if (res && res._scaleToBounds) {
+			res = res.clone();
+			res._scaleToBounds = true;
+		}
+		return res;
+	}
+
+	function importNode(node, options, isRoot) {
 		var type = node.nodeName.toLowerCase(),
-			importer = importers[type],
-			item,
-			data = node.getAttribute && node.getAttribute('data-paper-data'),
-			settings = scope.settings,
-			applyMatrix = settings.applyMatrix;
+			isElement = type !== '#document',
+			body = document.body,
+			container,
+			parent,
+			next;
+		if (isRoot && isElement) {
+			rootSize = paper.getView().getSize();
+			rootSize = getSize(node, null, null, true) || rootSize;
+			container = SvgElement.create('svg', {
+				style: 'stroke-width: 1px; stroke-miterlimit: 10'
+			});
+			parent = node.parentNode;
+			next = node.nextSibling;
+			container.appendChild(node);
+			body.appendChild(container);
+		}
+		var settings = paper.settings,
+			applyMatrix = settings.applyMatrix,
+			insertItems = settings.insertItems;
 		settings.applyMatrix = false;
-		item = importer && importer(node, type, options, isRoot) || null;
+		settings.insertItems = false;
+		var importer = importers[type],
+			item = importer && importer(node, type, options, isRoot) || null;
+		settings.insertItems = insertItems;
 		settings.applyMatrix = applyMatrix;
 		if (item) {
-			if (type !== '#document' && !(item instanceof Group))
+			if (isElement && !(item instanceof Group))
 				item = applyAttributes(item, node, isRoot);
-			var onImport = options.onImport;
+			var onImport = options.onImport,
+				data = isElement && node.getAttribute('data-paper-data');
 			if (onImport)
 				item = onImport(node, item, options) || item;
 			if (options.expandShapes && item instanceof Shape) {
@@ -12856,6 +15209,16 @@
 			if (data)
 				item._data = JSON.parse(data);
 		}
+		if (container) {
+			body.removeChild(container);
+			if (parent) {
+				if (next) {
+					parent.insertBefore(node, next);
+				} else {
+					parent.appendChild(node);
+				}
+			}
+		}
 		if (isRoot) {
 			definitions = {};
 			if (item && Base.pick(options.applyMatrix, applyMatrix))
@@ -12864,24 +15227,1412 @@
 		return item;
 	}
 
+	function importSVG(source, options, owner) {
+		if (!source)
+			return null;
+		options = typeof options === 'function' ? { onLoad: options }
+				: options || {};
+		var scope = paper,
+			item = null;
+
+		function onLoad(svg) {
+			try {
+				var node = typeof svg === 'object' ? svg : new self.DOMParser()
+						.parseFromString(svg, 'image/svg+xml');
+				if (!node.nodeName) {
+					node = null;
+					throw new Error('Unsupported SVG source: ' + source);
+				}
+				paper = scope;
+				item = importNode(node, options, true);
+				if (!options || options.insert !== false) {
+					owner._insertItem(undefined, item);
+				}
+				var onLoad = options.onLoad;
+				if (onLoad)
+					onLoad(item, svg);
+			} catch (e) {
+				onError(e);
+			}
+		}
+
+		function onError(message, status) {
+			var onError = options.onError;
+			if (onError) {
+				onError(message, status);
+			} else {
+				throw new Error(message);
+			}
+		}
+
+		if (typeof source === 'string' && !/^.*</.test(source)) {
+			var node = document.getElementById(source);
+			if (node) {
+				onLoad(node);
+			} else {
+				Http.request({
+					url: source,
+					async: true,
+					onLoad: onLoad,
+					onError: onError
+				});
+			}
+		} else if (typeof File !== 'undefined' && source instanceof File) {
+			var reader = new FileReader();
+			reader.onload = function() {
+				onLoad(reader.result);
+			};
+			reader.onerror = function() {
+				onError(reader.error);
+			};
+			return reader.readAsText(source);
+		} else {
+			onLoad(source);
+		}
+
+		return item;
+	}
+
 	Item.inject({
 		importSVG: function(node, options) {
-			return this.addChild(importSVG(node, options, true));
+			return importSVG(node, options, this);
 		}
 	});
 
 	Project.inject({
 		importSVG: function(node, options) {
 			this.activate();
-			return importSVG(node, options, true);
+			return importSVG(node, options, this);
 		}
 	});
 };
 
-Base.exports.PaperScript = (function() {
-	var exports, define,
-		scope = this;
-!function(e,r){return"object"==typeof exports&&"object"==typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):(r(e.acorn||(e.acorn={})),void 0)}(this,function(e){"use strict";function r(e){fr=e||{};for(var r in mr)Object.prototype.hasOwnProperty.call(fr,r)||(fr[r]=mr[r]);hr=fr.sourceFile||null}function t(e,r){var t=vr(dr,e);r+=" ("+t.line+":"+t.column+")";var n=new SyntaxError(r);throw n.pos=e,n.loc=t,n.raisedAt=br,n}function n(e){function r(e){if(1==e.length)return t+="return str === "+JSON.stringify(e[0])+";";t+="switch(str){";for(var r=0;r<e.length;++r)t+="case "+JSON.stringify(e[r])+":";t+="return true}return false;"}e=e.split(" ");var t="",n=[];e:for(var a=0;a<e.length;++a){for(var o=0;o<n.length;++o)if(n[o][0].length==e[a].length){n[o].push(e[a]);continue e}n.push([e[a]])}if(n.length>3){n.sort(function(e,r){return r.length-e.length}),t+="switch(str.length){";for(var a=0;a<n.length;++a){var i=n[a];t+="case "+i[0].length+":",r(i)}t+="}"}else r(e);return new Function("str",t)}function a(){this.line=Ar,this.column=br-Sr}function o(){Ar=1,br=Sr=0,Er=!0,u()}function i(e,r){gr=br,fr.locations&&(kr=new a),wr=e,u(),Cr=r,Er=e.beforeExpr}function s(){var e=fr.onComment&&fr.locations&&new a,r=br,n=dr.indexOf("*/",br+=2);if(-1===n&&t(br-2,"Unterminated comment"),br=n+2,fr.locations){Kt.lastIndex=r;for(var o;(o=Kt.exec(dr))&&o.index<br;)++Ar,Sr=o.index+o[0].length}fr.onComment&&fr.onComment(!0,dr.slice(r+2,n),r,br,e,fr.locations&&new a)}function c(){for(var e=br,r=fr.onComment&&fr.locations&&new a,t=dr.charCodeAt(br+=2);pr>br&&10!==t&&13!==t&&8232!==t&&8233!==t;)++br,t=dr.charCodeAt(br);fr.onComment&&fr.onComment(!1,dr.slice(e+2,br),e,br,r,fr.locations&&new a)}function u(){for(;pr>br;){var e=dr.charCodeAt(br);if(32===e)++br;else if(13===e){++br;var r=dr.charCodeAt(br);10===r&&++br,fr.locations&&(++Ar,Sr=br)}else if(10===e||8232===e||8233===e)++br,fr.locations&&(++Ar,Sr=br);else if(e>8&&14>e)++br;else if(47===e){var r=dr.charCodeAt(br+1);if(42===r)s();else{if(47!==r)break;c()}}else if(160===e)++br;else{if(!(e>=5760&&Jt.test(String.fromCharCode(e))))break;++br}}}function l(){var e=dr.charCodeAt(br+1);return e>=48&&57>=e?E(!0):(++br,i(xt))}function f(){var e=dr.charCodeAt(br+1);return Er?(++br,k()):61===e?x(Et,2):x(wt,1)}function d(){var e=dr.charCodeAt(br+1);return 61===e?x(Et,2):x(Dt,1)}function p(e){var r=dr.charCodeAt(br+1);return r===e?x(124===e?Lt:Ut,2):61===r?x(Et,2):x(124===e?Rt:Tt,1)}function h(){var e=dr.charCodeAt(br+1);return 61===e?x(Et,2):x(Vt,1)}function m(e){var r=dr.charCodeAt(br+1);return r===e?45==r&&62==dr.charCodeAt(br+2)&&Gt.test(dr.slice(Lr,br))?(br+=3,c(),u(),g()):x(St,2):61===r?x(Et,2):x(At,1)}function v(e){var r=dr.charCodeAt(br+1),t=1;return r===e?(t=62===e&&62===dr.charCodeAt(br+2)?3:2,61===dr.charCodeAt(br+t)?x(Et,t+1):x(jt,t)):33==r&&60==e&&45==dr.charCodeAt(br+2)&&45==dr.charCodeAt(br+3)?(br+=4,c(),u(),g()):(61===r&&(t=61===dr.charCodeAt(br+2)?3:2),x(Ot,t))}function b(e){var r=dr.charCodeAt(br+1);return 61===r?x(qt,61===dr.charCodeAt(br+2)?3:2):x(61===e?Ct:It,1)}function y(e){switch(e){case 46:return l();case 40:return++br,i(mt);case 41:return++br,i(vt);case 59:return++br,i(yt);case 44:return++br,i(bt);case 91:return++br,i(ft);case 93:return++br,i(dt);case 123:return++br,i(pt);case 125:return++br,i(ht);case 58:return++br,i(gt);case 63:return++br,i(kt);case 48:var r=dr.charCodeAt(br+1);if(120===r||88===r)return C();case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return E(!1);case 34:case 39:return A(e);case 47:return f(e);case 37:case 42:return d();case 124:case 38:return p(e);case 94:return h();case 43:case 45:return m(e);case 60:case 62:return v(e);case 61:case 33:return b(e);case 126:return x(It,1)}return!1}function g(e){if(e?br=yr+1:yr=br,fr.locations&&(xr=new a),e)return k();if(br>=pr)return i(Br);var r=dr.charCodeAt(br);if(Qt(r)||92===r)return L();var n=y(r);if(n===!1){var o=String.fromCharCode(r);if("\\"===o||$t.test(o))return L();t(br,"Unexpected character '"+o+"'")}return n}function x(e,r){var t=dr.slice(br,br+r);br+=r,i(e,t)}function k(){for(var e,r,n="",a=br;;){br>=pr&&t(a,"Unterminated regular expression");var o=dr.charAt(br);if(Gt.test(o)&&t(a,"Unterminated regular expression"),e)e=!1;else{if("["===o)r=!0;else if("]"===o&&r)r=!1;else if("/"===o&&!r)break;e="\\"===o}++br}var n=dr.slice(a,br);++br;var s=I();return s&&!/^[gmsiy]*$/.test(s)&&t(a,"Invalid regexp flag"),i(jr,new RegExp(n,s))}function w(e,r){for(var t=br,n=0,a=0,o=null==r?1/0:r;o>a;++a){var i,s=dr.charCodeAt(br);if(i=s>=97?s-97+10:s>=65?s-65+10:s>=48&&57>=s?s-48:1/0,i>=e)break;++br,n=n*e+i}return br===t||null!=r&&br-t!==r?null:n}function C(){br+=2;var e=w(16);return null==e&&t(yr+2,"Expected hexadecimal number"),Qt(dr.charCodeAt(br))&&t(br,"Identifier directly after number"),i(Or,e)}function E(e){var r=br,n=!1,a=48===dr.charCodeAt(br);e||null!==w(10)||t(r,"Invalid number"),46===dr.charCodeAt(br)&&(++br,w(10),n=!0);var o=dr.charCodeAt(br);(69===o||101===o)&&(o=dr.charCodeAt(++br),(43===o||45===o)&&++br,null===w(10)&&t(r,"Invalid number"),n=!0),Qt(dr.charCodeAt(br))&&t(br,"Identifier directly after number");var s,c=dr.slice(r,br);return n?s=parseFloat(c):a&&1!==c.length?/[89]/.test(c)||Tr?t(r,"Invalid number"):s=parseInt(c,8):s=parseInt(c,10),i(Or,s)}function A(e){br++;for(var r="";;){br>=pr&&t(yr,"Unterminated string constant");var n=dr.charCodeAt(br);if(n===e)return++br,i(Dr,r);if(92===n){n=dr.charCodeAt(++br);var a=/^[0-7]+/.exec(dr.slice(br,br+3));for(a&&(a=a[0]);a&&parseInt(a,8)>255;)a=a.slice(0,a.length-1);if("0"===a&&(a=null),++br,a)Tr&&t(br-2,"Octal literal in strict mode"),r+=String.fromCharCode(parseInt(a,8)),br+=a.length-1;else switch(n){case 110:r+="\n";break;case 114:r+="\r";break;case 120:r+=String.fromCharCode(S(2));break;case 117:r+=String.fromCharCode(S(4));break;case 85:r+=String.fromCharCode(S(8));break;case 116:r+="	";break;case 98:r+="\b";break;case 118:r+="";break;case 102:r+="\f";break;case 48:r+="\0";break;case 13:10===dr.charCodeAt(br)&&++br;case 10:fr.locations&&(Sr=br,++Ar);break;default:r+=String.fromCharCode(n)}}else(13===n||10===n||8232===n||8233===n)&&t(yr,"Unterminated string constant"),r+=String.fromCharCode(n),++br}}function S(e){var r=w(16,e);return null===r&&t(yr,"Bad character escape sequence"),r}function I(){Bt=!1;for(var e,r=!0,n=br;;){var a=dr.charCodeAt(br);if(Yt(a))Bt&&(e+=dr.charAt(br)),++br;else{if(92!==a)break;Bt||(e=dr.slice(n,br)),Bt=!0,117!=dr.charCodeAt(++br)&&t(br,"Expecting Unicode escape sequence \\uXXXX"),++br;var o=S(4),i=String.fromCharCode(o);i||t(br-1,"Invalid Unicode escape"),(r?Qt(o):Yt(o))||t(br-4,"Invalid Unicode escape"),e+=i}r=!1}return Bt?e:dr.slice(n,br)}function L(){var e=I(),r=Fr;return Bt||(Wt(e)?r=lt[e]:(fr.forbidReserved&&(3===fr.ecmaVersion?Mt:zt)(e)||Tr&&Xt(e))&&t(yr,"The keyword '"+e+"' is reserved")),i(r,e)}function U(){Ir=yr,Lr=gr,Ur=kr,g()}function R(e){if(Tr=e,br=Lr,fr.locations)for(;Sr>br;)Sr=dr.lastIndexOf("\n",Sr-2)+1,--Ar;u(),g()}function V(){this.type=null,this.start=yr,this.end=null}function T(){this.start=xr,this.end=null,null!==hr&&(this.source=hr)}function q(){var e=new V;return fr.locations&&(e.loc=new T),fr.ranges&&(e.range=[yr,0]),e}function O(e){var r=new V;return r.start=e.start,fr.locations&&(r.loc=new T,r.loc.start=e.loc.start),fr.ranges&&(r.range=[e.range[0],0]),r}function j(e,r){return e.type=r,e.end=Lr,fr.locations&&(e.loc.end=Ur),fr.ranges&&(e.range[1]=Lr),e}function D(e){return fr.ecmaVersion>=5&&"ExpressionStatement"===e.type&&"Literal"===e.expression.type&&"use strict"===e.expression.value}function F(e){return wr===e?(U(),!0):void 0}function B(){return!fr.strictSemicolons&&(wr===Br||wr===ht||Gt.test(dr.slice(Lr,yr)))}function M(){F(yt)||B()||X()}function z(e){wr===e?U():X()}function X(){t(yr,"Unexpected token")}function N(e){"Identifier"!==e.type&&"MemberExpression"!==e.type&&t(e.start,"Assigning to rvalue"),Tr&&"Identifier"===e.type&&Nt(e.name)&&t(e.start,"Assigning to "+e.name+" in strict mode")}function W(e){Ir=Lr=br,fr.locations&&(Ur=new a),Rr=Tr=null,Vr=[],g();var r=e||q(),t=!0;for(e||(r.body=[]);wr!==Br;){var n=J();r.body.push(n),t&&D(n)&&R(!0),t=!1}return j(r,"Program")}function J(){(wr===wt||wr===Et&&"/="==Cr)&&g(!0);var e=wr,r=q();switch(e){case Mr:case Nr:U();var n=e===Mr;F(yt)||B()?r.label=null:wr!==Fr?X():(r.label=lr(),M());for(var a=0;a<Vr.length;++a){var o=Vr[a];if(null==r.label||o.name===r.label.name){if(null!=o.kind&&(n||"loop"===o.kind))break;if(r.label&&n)break}}return a===Vr.length&&t(r.start,"Unsyntactic "+e.keyword),j(r,n?"BreakStatement":"ContinueStatement");case Wr:return U(),M(),j(r,"DebuggerStatement");case Pr:return U(),Vr.push(Zt),r.body=J(),Vr.pop(),z(tt),r.test=P(),M(),j(r,"DoWhileStatement");case _r:if(U(),Vr.push(Zt),z(mt),wr===yt)return $(r,null);if(wr===rt){var i=q();return U(),G(i,!0),j(i,"VariableDeclaration"),1===i.declarations.length&&F(ut)?_(r,i):$(r,i)}var i=K(!1,!0);return F(ut)?(N(i),_(r,i)):$(r,i);case Gr:return U(),cr(r,!0);case Kr:return U(),r.test=P(),r.consequent=J(),r.alternate=F(Hr)?J():null,j(r,"IfStatement");case Qr:return Rr||t(yr,"'return' outside of function"),U(),F(yt)||B()?r.argument=null:(r.argument=K(),M()),j(r,"ReturnStatement");case Yr:U(),r.discriminant=P(),r.cases=[],z(pt),Vr.push(en);for(var s,c;wr!=ht;)if(wr===zr||wr===Jr){var u=wr===zr;s&&j(s,"SwitchCase"),r.cases.push(s=q()),s.consequent=[],U(),u?s.test=K():(c&&t(Ir,"Multiple default clauses"),c=!0,s.test=null),z(gt)}else s||X(),s.consequent.push(J());return s&&j(s,"SwitchCase"),U(),Vr.pop(),j(r,"SwitchStatement");case Zr:return U(),Gt.test(dr.slice(Lr,yr))&&t(Lr,"Illegal newline after throw"),r.argument=K(),M(),j(r,"ThrowStatement");case et:if(U(),r.block=H(),r.handler=null,wr===Xr){var l=q();U(),z(mt),l.param=lr(),Tr&&Nt(l.param.name)&&t(l.param.start,"Binding "+l.param.name+" in strict mode"),z(vt),l.guard=null,l.body=H(),r.handler=j(l,"CatchClause")}return r.guardedHandlers=qr,r.finalizer=F($r)?H():null,r.handler||r.finalizer||t(r.start,"Missing catch or finally clause"),j(r,"TryStatement");case rt:return U(),G(r),M(),j(r,"VariableDeclaration");case tt:return U(),r.test=P(),Vr.push(Zt),r.body=J(),Vr.pop(),j(r,"WhileStatement");case nt:return Tr&&t(yr,"'with' in strict mode"),U(),r.object=P(),r.body=J(),j(r,"WithStatement");case pt:return H();case yt:return U(),j(r,"EmptyStatement");default:var f=Cr,d=K();if(e===Fr&&"Identifier"===d.type&&F(gt)){for(var a=0;a<Vr.length;++a)Vr[a].name===f&&t(d.start,"Label '"+f+"' is already declared");var p=wr.isLoop?"loop":wr===Yr?"switch":null;return Vr.push({name:f,kind:p}),r.body=J(),Vr.pop(),r.label=d,j(r,"LabeledStatement")}return r.expression=d,M(),j(r,"ExpressionStatement")}}function P(){z(mt);var e=K();return z(vt),e}function H(e){var r,t=q(),n=!0,a=!1;for(t.body=[],z(pt);!F(ht);){var o=J();t.body.push(o),n&&e&&D(o)&&(r=a,R(a=!0)),n=!1}return a&&!r&&R(!1),j(t,"BlockStatement")}function $(e,r){return e.init=r,z(yt),e.test=wr===yt?null:K(),z(yt),e.update=wr===vt?null:K(),z(vt),e.body=J(),Vr.pop(),j(e,"ForStatement")}function _(e,r){return e.left=r,e.right=K(),z(vt),e.body=J(),Vr.pop(),j(e,"ForInStatement")}function G(e,r){for(e.declarations=[],e.kind="var";;){var n=q();if(n.id=lr(),Tr&&Nt(n.id.name)&&t(n.id.start,"Binding "+n.id.name+" in strict mode"),n.init=F(Ct)?K(!0,r):null,e.declarations.push(j(n,"VariableDeclarator")),!F(bt))break}return e}function K(e,r){var t=Q(r);if(!e&&wr===bt){var n=O(t);for(n.expressions=[t];F(bt);)n.expressions.push(Q(r));return j(n,"SequenceExpression")}return t}function Q(e){var r=Y(e);if(wr.isAssign){var t=O(r);return t.operator=Cr,t.left=r,U(),t.right=Q(e),N(r),j(t,"AssignmentExpression")}return r}function Y(e){var r=Z(e);if(F(kt)){var t=O(r);return t.test=r,t.consequent=K(!0),z(gt),t.alternate=K(!0,e),j(t,"ConditionalExpression")}return r}function Z(e){return er(rr(),-1,e)}function er(e,r,t){var n=wr.binop;if(null!=n&&(!t||wr!==ut)&&n>r){var a=O(e);a.left=e,a.operator=Cr,U(),a.right=er(rr(),n,t);var o=j(a,/&&|\|\|/.test(a.operator)?"LogicalExpression":"BinaryExpression");return er(o,r,t)}return e}function rr(){if(wr.prefix){var e=q(),r=wr.isUpdate;return e.operator=Cr,e.prefix=!0,Er=!0,U(),e.argument=rr(),r?N(e.argument):Tr&&"delete"===e.operator&&"Identifier"===e.argument.type&&t(e.start,"Deleting local variable in strict mode"),j(e,r?"UpdateExpression":"UnaryExpression")}for(var n=tr();wr.postfix&&!B();){var e=O(n);e.operator=Cr,e.prefix=!1,e.argument=n,N(n),U(),n=j(e,"UpdateExpression")}return n}function tr(){return nr(ar())}function nr(e,r){if(F(xt)){var t=O(e);return t.object=e,t.property=lr(!0),t.computed=!1,nr(j(t,"MemberExpression"),r)}if(F(ft)){var t=O(e);return t.object=e,t.property=K(),t.computed=!0,z(dt),nr(j(t,"MemberExpression"),r)}if(!r&&F(mt)){var t=O(e);return t.callee=e,t.arguments=ur(vt,!1),nr(j(t,"CallExpression"),r)}return e}function ar(){switch(wr){case ot:var e=q();return U(),j(e,"ThisExpression");case Fr:return lr();case Or:case Dr:case jr:var e=q();return e.value=Cr,e.raw=dr.slice(yr,gr),U(),j(e,"Literal");case it:case st:case ct:var e=q();return e.value=wr.atomValue,e.raw=wr.keyword,U(),j(e,"Literal");case mt:var r=xr,t=yr;U();var n=K();return n.start=t,n.end=gr,fr.locations&&(n.loc.start=r,n.loc.end=kr),fr.ranges&&(n.range=[t,gr]),z(vt),n;case ft:var e=q();return U(),e.elements=ur(dt,!0,!0),j(e,"ArrayExpression");case pt:return ir();case Gr:var e=q();return U(),cr(e,!1);case at:return or();default:X()}}function or(){var e=q();return U(),e.callee=nr(ar(),!0),e.arguments=F(mt)?ur(vt,!1):qr,j(e,"NewExpression")}function ir(){var e=q(),r=!0,n=!1;for(e.properties=[],U();!F(ht);){if(r)r=!1;else if(z(bt),fr.allowTrailingCommas&&F(ht))break;var a,o={key:sr()},i=!1;if(F(gt)?(o.value=K(!0),a=o.kind="init"):fr.ecmaVersion>=5&&"Identifier"===o.key.type&&("get"===o.key.name||"set"===o.key.name)?(i=n=!0,a=o.kind=o.key.name,o.key=sr(),wr!==mt&&X(),o.value=cr(q(),!1)):X(),"Identifier"===o.key.type&&(Tr||n))for(var s=0;s<e.properties.length;++s){var c=e.properties[s];if(c.key.name===o.key.name){var u=a==c.kind||i&&"init"===c.kind||"init"===a&&("get"===c.kind||"set"===c.kind);u&&!Tr&&"init"===a&&"init"===c.kind&&(u=!1),u&&t(o.key.start,"Redefinition of property")}}e.properties.push(o)}return j(e,"ObjectExpression")}function sr(){return wr===Or||wr===Dr?ar():lr(!0)}function cr(e,r){wr===Fr?e.id=lr():r?X():e.id=null,e.params=[];var n=!0;for(z(mt);!F(vt);)n?n=!1:z(bt),e.params.push(lr());var a=Rr,o=Vr;if(Rr=!0,Vr=[],e.body=H(!0),Rr=a,Vr=o,Tr||e.body.body.length&&D(e.body.body[0]))for(var i=e.id?-1:0;i<e.params.length;++i){var s=0>i?e.id:e.params[i];if((Xt(s.name)||Nt(s.name))&&t(s.start,"Defining '"+s.name+"' in strict mode"),i>=0)for(var c=0;i>c;++c)s.name===e.params[c].name&&t(s.start,"Argument name clash in strict mode")}return j(e,r?"FunctionDeclaration":"FunctionExpression")}function ur(e,r,t){for(var n=[],a=!0;!F(e);){if(a)a=!1;else if(z(bt),r&&fr.allowTrailingCommas&&F(e))break;t&&wr===bt?n.push(null):n.push(K(!0))}return n}function lr(e){var r=q();return r.name=wr===Fr?Cr:e&&!fr.forbidReserved&&wr.keyword||X(),Er=!1,U(),j(r,"Identifier")}e.version="0.4.0";var fr,dr,pr,hr;e.parse=function(e,t){return dr=String(e),pr=dr.length,r(t),o(),W(fr.program)};var mr=e.defaultOptions={ecmaVersion:5,strictSemicolons:!1,allowTrailingCommas:!0,forbidReserved:!1,locations:!1,onComment:null,ranges:!1,program:null,sourceFile:null},vr=e.getLineInfo=function(e,r){for(var t=1,n=0;;){Kt.lastIndex=n;var a=Kt.exec(e);if(!(a&&a.index<r))break;++t,n=a.index+a[0].length}return{line:t,column:r-n}};e.tokenize=function(e,t){function n(e){return g(e),a.start=yr,a.end=gr,a.startLoc=xr,a.endLoc=kr,a.type=wr,a.value=Cr,a}dr=String(e),pr=dr.length,r(t),o();var a={};return n.jumpTo=function(e,r){if(br=e,fr.locations){Ar=1,Sr=Kt.lastIndex=0;for(var t;(t=Kt.exec(dr))&&t.index<e;)++Ar,Sr=t.index+t[0].length}Er=r,u()},n};var br,yr,gr,xr,kr,wr,Cr,Er,Ar,Sr,Ir,Lr,Ur,Rr,Vr,Tr,qr=[],Or={type:"num"},jr={type:"regexp"},Dr={type:"string"},Fr={type:"name"},Br={type:"eof"},Mr={keyword:"break"},zr={keyword:"case",beforeExpr:!0},Xr={keyword:"catch"},Nr={keyword:"continue"},Wr={keyword:"debugger"},Jr={keyword:"default"},Pr={keyword:"do",isLoop:!0},Hr={keyword:"else",beforeExpr:!0},$r={keyword:"finally"},_r={keyword:"for",isLoop:!0},Gr={keyword:"function"},Kr={keyword:"if"},Qr={keyword:"return",beforeExpr:!0},Yr={keyword:"switch"},Zr={keyword:"throw",beforeExpr:!0},et={keyword:"try"},rt={keyword:"var"},tt={keyword:"while",isLoop:!0},nt={keyword:"with"},at={keyword:"new",beforeExpr:!0},ot={keyword:"this"},it={keyword:"null",atomValue:null},st={keyword:"true",atomValue:!0},ct={keyword:"false",atomValue:!1},ut={keyword:"in",binop:7,beforeExpr:!0},lt={"break":Mr,"case":zr,"catch":Xr,"continue":Nr,"debugger":Wr,"default":Jr,"do":Pr,"else":Hr,"finally":$r,"for":_r,"function":Gr,"if":Kr,"return":Qr,"switch":Yr,"throw":Zr,"try":et,"var":rt,"while":tt,"with":nt,"null":it,"true":st,"false":ct,"new":at,"in":ut,"instanceof":{keyword:"instanceof",binop:7,beforeExpr:!0},"this":ot,"typeof":{keyword:"typeof",prefix:!0,beforeExpr:!0},"void":{keyword:"void",prefix:!0,beforeExpr:!0},"delete":{keyword:"delete",prefix:!0,beforeExpr:!0}},ft={type:"[",beforeExpr:!0},dt={type:"]"},pt={type:"{",beforeExpr:!0},ht={type:"}"},mt={type:"(",beforeExpr:!0},vt={type:")"},bt={type:",",beforeExpr:!0},yt={type:";",beforeExpr:!0},gt={type:":",beforeExpr:!0},xt={type:"."},kt={type:"?",beforeExpr:!0},wt={binop:10,beforeExpr:!0},Ct={isAssign:!0,beforeExpr:!0},Et={isAssign:!0,beforeExpr:!0},At={binop:9,prefix:!0,beforeExpr:!0},St={postfix:!0,prefix:!0,isUpdate:!0},It={prefix:!0,beforeExpr:!0},Lt={binop:1,beforeExpr:!0},Ut={binop:2,beforeExpr:!0},Rt={binop:3,beforeExpr:!0},Vt={binop:4,beforeExpr:!0},Tt={binop:5,beforeExpr:!0},qt={binop:6,beforeExpr:!0},Ot={binop:7,beforeExpr:!0},jt={binop:8,beforeExpr:!0},Dt={binop:10,beforeExpr:!0};e.tokTypes={bracketL:ft,bracketR:dt,braceL:pt,braceR:ht,parenL:mt,parenR:vt,comma:bt,semi:yt,colon:gt,dot:xt,question:kt,slash:wt,eq:Ct,name:Fr,eof:Br,num:Or,regexp:jr,string:Dr};for(var Ft in lt)e.tokTypes["_"+Ft]=lt[Ft];var Bt,Mt=n("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"),zt=n("class enum extends super const export import"),Xt=n("implements interface let package private protected public static yield"),Nt=n("eval arguments"),Wt=n("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"),Jt=/[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/,Pt="\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc",Ht="\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f",$t=new RegExp("["+Pt+"]"),_t=new RegExp("["+Pt+Ht+"]"),Gt=/[\n\r\u2028\u2029]/,Kt=/\r\n|[\n\r\u2028\u2029]/g,Qt=e.isIdentifierStart=function(e){return 65>e?36===e:91>e?!0:97>e?95===e:123>e?!0:e>=170&&$t.test(String.fromCharCode(e))},Yt=e.isIdentifierChar=function(e){return 48>e?36===e:58>e?!0:65>e?!1:91>e?!0:97>e?95===e:123>e?!0:e>=170&&_t.test(String.fromCharCode(e))},Zt={kind:"loop"},en={kind:"switch"}});
+Base.exports.PaperScript = function() {
+	var global = this,
+		acorn = global.acorn;
+	if (!acorn && typeof require !== 'undefined') {
+		try { acorn = require('acorn'); } catch(e) {}
+	}
+	if (!acorn) {
+		var exports, module;
+		acorn = exports = module = {};
+
+(function(root, mod) {
+  if (typeof exports == "object" && typeof module == "object") return mod(exports);
+  if (typeof define == "function" && define.amd) return define(["exports"], mod);
+  mod(root.acorn || (root.acorn = {}));
+})(this, function(exports) {
+  "use strict";
+
+  exports.version = "0.5.0";
+
+  var options, input, inputLen, sourceFile;
+
+  exports.parse = function(inpt, opts) {
+	input = String(inpt); inputLen = input.length;
+	setOptions(opts);
+	initTokenState();
+	return parseTopLevel(options.program);
+  };
+
+  var defaultOptions = exports.defaultOptions = {
+	ecmaVersion: 5,
+	strictSemicolons: false,
+	allowTrailingCommas: true,
+	forbidReserved: false,
+	allowReturnOutsideFunction: false,
+	locations: false,
+	onComment: null,
+	ranges: false,
+	program: null,
+	sourceFile: null,
+	directSourceFile: null
+  };
+
+  function setOptions(opts) {
+	options = opts || {};
+	for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt))
+	  options[opt] = defaultOptions[opt];
+	sourceFile = options.sourceFile || null;
+  }
+
+  var getLineInfo = exports.getLineInfo = function(input, offset) {
+	for (var line = 1, cur = 0;;) {
+	  lineBreak.lastIndex = cur;
+	  var match = lineBreak.exec(input);
+	  if (match && match.index < offset) {
+		++line;
+		cur = match.index + match[0].length;
+	  } else break;
+	}
+	return {line: line, column: offset - cur};
+  };
+
+  exports.tokenize = function(inpt, opts) {
+	input = String(inpt); inputLen = input.length;
+	setOptions(opts);
+	initTokenState();
+
+	var t = {};
+	function getToken(forceRegexp) {
+	  lastEnd = tokEnd;
+	  readToken(forceRegexp);
+	  t.start = tokStart; t.end = tokEnd;
+	  t.startLoc = tokStartLoc; t.endLoc = tokEndLoc;
+	  t.type = tokType; t.value = tokVal;
+	  return t;
+	}
+	getToken.jumpTo = function(pos, reAllowed) {
+	  tokPos = pos;
+	  if (options.locations) {
+		tokCurLine = 1;
+		tokLineStart = lineBreak.lastIndex = 0;
+		var match;
+		while ((match = lineBreak.exec(input)) && match.index < pos) {
+		  ++tokCurLine;
+		  tokLineStart = match.index + match[0].length;
+		}
+	  }
+	  tokRegexpAllowed = reAllowed;
+	  skipSpace();
+	};
+	return getToken;
+  };
+
+  var tokPos;
+
+  var tokStart, tokEnd;
+
+  var tokStartLoc, tokEndLoc;
+
+  var tokType, tokVal;
+
+  var tokRegexpAllowed;
+
+  var tokCurLine, tokLineStart;
+
+  var lastStart, lastEnd, lastEndLoc;
+
+  var inFunction, labels, strict;
+
+  function raise(pos, message) {
+	var loc = getLineInfo(input, pos);
+	message += " (" + loc.line + ":" + loc.column + ")";
+	var err = new SyntaxError(message);
+	err.pos = pos; err.loc = loc; err.raisedAt = tokPos;
+	throw err;
+  }
+
+  var empty = [];
+
+  var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
+  var _name = {type: "name"}, _eof = {type: "eof"};
+
+  var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
+  var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
+  var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
+  var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
+  var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
+  var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
+  var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
+  var _this = {keyword: "this"};
+
+  var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
+  var _false = {keyword: "false", atomValue: false};
+
+  var _in = {keyword: "in", binop: 7, beforeExpr: true};
+
+  var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
+					  "continue": _continue, "debugger": _debugger, "default": _default,
+					  "do": _do, "else": _else, "finally": _finally, "for": _for,
+					  "function": _function, "if": _if, "return": _return, "switch": _switch,
+					  "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with,
+					  "null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
+					  "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
+					  "typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
+					  "void": {keyword: "void", prefix: true, beforeExpr: true},
+					  "delete": {keyword: "delete", prefix: true, beforeExpr: true}};
+
+  var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
+  var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
+  var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
+  var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
+
+  var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
+  var _assign = {isAssign: true, beforeExpr: true};
+  var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
+  var _logicalOR = {binop: 1, beforeExpr: true};
+  var _logicalAND = {binop: 2, beforeExpr: true};
+  var _bitwiseOR = {binop: 3, beforeExpr: true};
+  var _bitwiseXOR = {binop: 4, beforeExpr: true};
+  var _bitwiseAND = {binop: 5, beforeExpr: true};
+  var _equality = {binop: 6, beforeExpr: true};
+  var _relational = {binop: 7, beforeExpr: true};
+  var _bitShift = {binop: 8, beforeExpr: true};
+  var _plusMin = {binop: 9, prefix: true, beforeExpr: true};
+  var _multiplyModulo = {binop: 10, beforeExpr: true};
+
+  exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR,
+					  parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
+					  dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof,
+					  num: _num, regexp: _regexp, string: _string};
+  for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw];
+
+  function makePredicate(words) {
+	words = words.split(" ");
+	var f = "", cats = [];
+	out: for (var i = 0; i < words.length; ++i) {
+	  for (var j = 0; j < cats.length; ++j)
+		if (cats[j][0].length == words[i].length) {
+		  cats[j].push(words[i]);
+		  continue out;
+		}
+	  cats.push([words[i]]);
+	}
+	function compareTo(arr) {
+	  if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
+	  f += "switch(str){";
+	  for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
+	  f += "return true}return false;";
+	}
+
+	if (cats.length > 3) {
+	  cats.sort(function(a, b) {return b.length - a.length;});
+	  f += "switch(str.length){";
+	  for (var i = 0; i < cats.length; ++i) {
+		var cat = cats[i];
+		f += "case " + cat[0].length + ":";
+		compareTo(cat);
+	  }
+	  f += "}";
+
+	} else {
+	  compareTo(words);
+	}
+	return new Function("str", f);
+  }
+
+  var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile");
+
+  var isReservedWord5 = makePredicate("class enum extends super const export import");
+
+  var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield");
+
+  var isStrictBadIdWord = makePredicate("eval arguments");
+
+  var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this");
+
+  var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
+  var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
+  var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
+  var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
+  var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
+
+  var newline = /[\n\r\u2028\u2029]/;
+
+  var lineBreak = /\r\n|[\n\r\u2028\u2029]/g;
+
+  var isIdentifierStart = exports.isIdentifierStart = function(code) {
+	if (code < 65) return code === 36;
+	if (code < 91) return true;
+	if (code < 97) return code === 95;
+	if (code < 123)return true;
+	return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
+  };
+
+  var isIdentifierChar = exports.isIdentifierChar = function(code) {
+	if (code < 48) return code === 36;
+	if (code < 58) return true;
+	if (code < 65) return false;
+	if (code < 91) return true;
+	if (code < 97) return code === 95;
+	if (code < 123)return true;
+	return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
+  };
+
+  function line_loc_t() {
+	this.line = tokCurLine;
+	this.column = tokPos - tokLineStart;
+  }
+
+  function initTokenState() {
+	tokCurLine = 1;
+	tokPos = tokLineStart = 0;
+	tokRegexpAllowed = true;
+	skipSpace();
+  }
+
+  function finishToken(type, val) {
+	tokEnd = tokPos;
+	if (options.locations) tokEndLoc = new line_loc_t;
+	tokType = type;
+	skipSpace();
+	tokVal = val;
+	tokRegexpAllowed = type.beforeExpr;
+  }
+
+  function skipBlockComment() {
+	var startLoc = options.onComment && options.locations && new line_loc_t;
+	var start = tokPos, end = input.indexOf("*/", tokPos += 2);
+	if (end === -1) raise(tokPos - 2, "Unterminated comment");
+	tokPos = end + 2;
+	if (options.locations) {
+	  lineBreak.lastIndex = start;
+	  var match;
+	  while ((match = lineBreak.exec(input)) && match.index < tokPos) {
+		++tokCurLine;
+		tokLineStart = match.index + match[0].length;
+	  }
+	}
+	if (options.onComment)
+	  options.onComment(true, input.slice(start + 2, end), start, tokPos,
+						startLoc, options.locations && new line_loc_t);
+  }
+
+  function skipLineComment() {
+	var start = tokPos;
+	var startLoc = options.onComment && options.locations && new line_loc_t;
+	var ch = input.charCodeAt(tokPos+=2);
+	while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
+	  ++tokPos;
+	  ch = input.charCodeAt(tokPos);
+	}
+	if (options.onComment)
+	  options.onComment(false, input.slice(start + 2, tokPos), start, tokPos,
+						startLoc, options.locations && new line_loc_t);
+  }
+
+  function skipSpace() {
+	while (tokPos < inputLen) {
+	  var ch = input.charCodeAt(tokPos);
+	  if (ch === 32) {
+		++tokPos;
+	  } else if (ch === 13) {
+		++tokPos;
+		var next = input.charCodeAt(tokPos);
+		if (next === 10) {
+		  ++tokPos;
+		}
+		if (options.locations) {
+		  ++tokCurLine;
+		  tokLineStart = tokPos;
+		}
+	  } else if (ch === 10 || ch === 8232 || ch === 8233) {
+		++tokPos;
+		if (options.locations) {
+		  ++tokCurLine;
+		  tokLineStart = tokPos;
+		}
+	  } else if (ch > 8 && ch < 14) {
+		++tokPos;
+	  } else if (ch === 47) {
+		var next = input.charCodeAt(tokPos + 1);
+		if (next === 42) {
+		  skipBlockComment();
+		} else if (next === 47) {
+		  skipLineComment();
+		} else break;
+	  } else if (ch === 160) {
+		++tokPos;
+	  } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
+		++tokPos;
+	  } else {
+		break;
+	  }
+	}
+  }
+
+  function readToken_dot() {
+	var next = input.charCodeAt(tokPos + 1);
+	if (next >= 48 && next <= 57) return readNumber(true);
+	++tokPos;
+	return finishToken(_dot);
+  }
+
+  function readToken_slash() {
+	var next = input.charCodeAt(tokPos + 1);
+	if (tokRegexpAllowed) {++tokPos; return readRegexp();}
+	if (next === 61) return finishOp(_assign, 2);
+	return finishOp(_slash, 1);
+  }
+
+  function readToken_mult_modulo() {
+	var next = input.charCodeAt(tokPos + 1);
+	if (next === 61) return finishOp(_assign, 2);
+	return finishOp(_multiplyModulo, 1);
+  }
+
+  function readToken_pipe_amp(code) {
+	var next = input.charCodeAt(tokPos + 1);
+	if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);
+	if (next === 61) return finishOp(_assign, 2);
+	return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);
+  }
+
+  function readToken_caret() {
+	var next = input.charCodeAt(tokPos + 1);
+	if (next === 61) return finishOp(_assign, 2);
+	return finishOp(_bitwiseXOR, 1);
+  }
+
+  function readToken_plus_min(code) {
+	var next = input.charCodeAt(tokPos + 1);
+	if (next === code) {
+	  if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&
+		  newline.test(input.slice(lastEnd, tokPos))) {
+		tokPos += 3;
+		skipLineComment();
+		skipSpace();
+		return readToken();
+	  }
+	  return finishOp(_incDec, 2);
+	}
+	if (next === 61) return finishOp(_assign, 2);
+	return finishOp(_plusMin, 1);
+  }
+
+  function readToken_lt_gt(code) {
+	var next = input.charCodeAt(tokPos + 1);
+	var size = 1;
+	if (next === code) {
+	  size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;
+	  if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
+	  return finishOp(_bitShift, size);
+	}
+	if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&
+		input.charCodeAt(tokPos + 3) == 45) {
+	  tokPos += 4;
+	  skipLineComment();
+	  skipSpace();
+	  return readToken();
+	}
+	if (next === 61)
+	  size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
+	return finishOp(_relational, size);
+  }
+
+  function readToken_eq_excl(code) {
+	var next = input.charCodeAt(tokPos + 1);
+	if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);
+	return finishOp(code === 61 ? _eq : _prefix, 1);
+  }
+
+  function getTokenFromCode(code) {
+	switch(code) {
+	case 46:
+	  return readToken_dot();
+
+	case 40: ++tokPos; return finishToken(_parenL);
+	case 41: ++tokPos; return finishToken(_parenR);
+	case 59: ++tokPos; return finishToken(_semi);
+	case 44: ++tokPos; return finishToken(_comma);
+	case 91: ++tokPos; return finishToken(_bracketL);
+	case 93: ++tokPos; return finishToken(_bracketR);
+	case 123: ++tokPos; return finishToken(_braceL);
+	case 125: ++tokPos; return finishToken(_braceR);
+	case 58: ++tokPos; return finishToken(_colon);
+	case 63: ++tokPos; return finishToken(_question);
+
+	case 48:
+	  var next = input.charCodeAt(tokPos + 1);
+	  if (next === 120 || next === 88) return readHexNumber();
+	case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57:
+	  return readNumber(false);
+
+	case 34: case 39:
+	  return readString(code);
+
+	case 47:
+	  return readToken_slash(code);
+
+	case 37: case 42:
+	  return readToken_mult_modulo();
+
+	case 124: case 38:
+	  return readToken_pipe_amp(code);
+
+	case 94:
+	  return readToken_caret();
+
+	case 43: case 45:
+	  return readToken_plus_min(code);
+
+	case 60: case 62:
+	  return readToken_lt_gt(code);
+
+	case 61: case 33:
+	  return readToken_eq_excl(code);
+
+	case 126:
+	  return finishOp(_prefix, 1);
+	}
+
+	return false;
+  }
+
+  function readToken(forceRegexp) {
+	if (!forceRegexp) tokStart = tokPos;
+	else tokPos = tokStart + 1;
+	if (options.locations) tokStartLoc = new line_loc_t;
+	if (forceRegexp) return readRegexp();
+	if (tokPos >= inputLen) return finishToken(_eof);
+
+	var code = input.charCodeAt(tokPos);
+	if (isIdentifierStart(code) || code === 92 ) return readWord();
+
+	var tok = getTokenFromCode(code);
+
+	if (tok === false) {
+	  var ch = String.fromCharCode(code);
+	  if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
+	  raise(tokPos, "Unexpected character '" + ch + "'");
+	}
+	return tok;
+  }
+
+  function finishOp(type, size) {
+	var str = input.slice(tokPos, tokPos + size);
+	tokPos += size;
+	finishToken(type, str);
+  }
+
+  function readRegexp() {
+	var content = "", escaped, inClass, start = tokPos;
+	for (;;) {
+	  if (tokPos >= inputLen) raise(start, "Unterminated regular expression");
+	  var ch = input.charAt(tokPos);
+	  if (newline.test(ch)) raise(start, "Unterminated regular expression");
+	  if (!escaped) {
+		if (ch === "[") inClass = true;
+		else if (ch === "]" && inClass) inClass = false;
+		else if (ch === "/" && !inClass) break;
+		escaped = ch === "\\";
+	  } else escaped = false;
+	  ++tokPos;
+	}
+	var content = input.slice(start, tokPos);
+	++tokPos;
+	var mods = readWord1();
+	if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag");
+	try {
+	  var value = new RegExp(content, mods);
+	} catch (e) {
+	  if (e instanceof SyntaxError) raise(start, e.message);
+	  raise(e);
+	}
+	return finishToken(_regexp, value);
+  }
+
+  function readInt(radix, len) {
+	var start = tokPos, total = 0;
+	for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
+	  var code = input.charCodeAt(tokPos), val;
+	  if (code >= 97) val = code - 97 + 10;
+	  else if (code >= 65) val = code - 65 + 10;
+	  else if (code >= 48 && code <= 57) val = code - 48;
+	  else val = Infinity;
+	  if (val >= radix) break;
+	  ++tokPos;
+	  total = total * radix + val;
+	}
+	if (tokPos === start || len != null && tokPos - start !== len) return null;
+
+	return total;
+  }
+
+  function readHexNumber() {
+	tokPos += 2;
+	var val = readInt(16);
+	if (val == null) raise(tokStart + 2, "Expected hexadecimal number");
+	if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
+	return finishToken(_num, val);
+  }
+
+  function readNumber(startsWithDot) {
+	var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
+	if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
+	if (input.charCodeAt(tokPos) === 46) {
+	  ++tokPos;
+	  readInt(10);
+	  isFloat = true;
+	}
+	var next = input.charCodeAt(tokPos);
+	if (next === 69 || next === 101) {
+	  next = input.charCodeAt(++tokPos);
+	  if (next === 43 || next === 45) ++tokPos;
+	  if (readInt(10) === null) raise(start, "Invalid number");
+	  isFloat = true;
+	}
+	if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
+
+	var str = input.slice(start, tokPos), val;
+	if (isFloat) val = parseFloat(str);
+	else if (!octal || str.length === 1) val = parseInt(str, 10);
+	else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
+	else val = parseInt(str, 8);
+	return finishToken(_num, val);
+  }
+
+  function readString(quote) {
+	tokPos++;
+	var out = "";
+	for (;;) {
+	  if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
+	  var ch = input.charCodeAt(tokPos);
+	  if (ch === quote) {
+		++tokPos;
+		return finishToken(_string, out);
+	  }
+	  if (ch === 92) {
+		ch = input.charCodeAt(++tokPos);
+		var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
+		if (octal) octal = octal[0];
+		while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);
+		if (octal === "0") octal = null;
+		++tokPos;
+		if (octal) {
+		  if (strict) raise(tokPos - 2, "Octal literal in strict mode");
+		  out += String.fromCharCode(parseInt(octal, 8));
+		  tokPos += octal.length - 1;
+		} else {
+		  switch (ch) {
+		  case 110: out += "\n"; break;
+		  case 114: out += "\r"; break;
+		  case 120: out += String.fromCharCode(readHexChar(2)); break;
+		  case 117: out += String.fromCharCode(readHexChar(4)); break;
+		  case 85: out += String.fromCharCode(readHexChar(8)); break;
+		  case 116: out += "\t"; break;
+		  case 98: out += "\b"; break;
+		  case 118: out += "\u000b"; break;
+		  case 102: out += "\f"; break;
+		  case 48: out += "\0"; break;
+		  case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos;
+		  case 10:
+			if (options.locations) { tokLineStart = tokPos; ++tokCurLine; }
+			break;
+		  default: out += String.fromCharCode(ch); break;
+		  }
+		}
+	  } else {
+		if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant");
+		out += String.fromCharCode(ch);
+		++tokPos;
+	  }
+	}
+  }
+
+  function readHexChar(len) {
+	var n = readInt(16, len);
+	if (n === null) raise(tokStart, "Bad character escape sequence");
+	return n;
+  }
+
+  var containsEsc;
+
+  function readWord1() {
+	containsEsc = false;
+	var word, first = true, start = tokPos;
+	for (;;) {
+	  var ch = input.charCodeAt(tokPos);
+	  if (isIdentifierChar(ch)) {
+		if (containsEsc) word += input.charAt(tokPos);
+		++tokPos;
+	  } else if (ch === 92) {
+		if (!containsEsc) word = input.slice(start, tokPos);
+		containsEsc = true;
+		if (input.charCodeAt(++tokPos) != 117)
+		  raise(tokPos, "Expecting Unicode escape sequence \\uXXXX");
+		++tokPos;
+		var esc = readHexChar(4);
+		var escStr = String.fromCharCode(esc);
+		if (!escStr) raise(tokPos - 1, "Invalid Unicode escape");
+		if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc)))
+		  raise(tokPos - 4, "Invalid Unicode escape");
+		word += escStr;
+	  } else {
+		break;
+	  }
+	  first = false;
+	}
+	return containsEsc ? word : input.slice(start, tokPos);
+  }
+
+  function readWord() {
+	var word = readWord1();
+	var type = _name;
+	if (!containsEsc && isKeyword(word))
+	  type = keywordTypes[word];
+	return finishToken(type, word);
+  }
+
+  function next() {
+	lastStart = tokStart;
+	lastEnd = tokEnd;
+	lastEndLoc = tokEndLoc;
+	readToken();
+  }
+
+  function setStrict(strct) {
+	strict = strct;
+	tokPos = tokStart;
+	if (options.locations) {
+	  while (tokPos < tokLineStart) {
+		tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
+		--tokCurLine;
+	  }
+	}
+	skipSpace();
+	readToken();
+  }
+
+  function node_t() {
+	this.type = null;
+	this.start = tokStart;
+	this.end = null;
+  }
+
+  function node_loc_t() {
+	this.start = tokStartLoc;
+	this.end = null;
+	if (sourceFile !== null) this.source = sourceFile;
+  }
+
+  function startNode() {
+	var node = new node_t();
+	if (options.locations)
+	  node.loc = new node_loc_t();
+	if (options.directSourceFile)
+	  node.sourceFile = options.directSourceFile;
+	if (options.ranges)
+	  node.range = [tokStart, 0];
+	return node;
+  }
+
+  function startNodeFrom(other) {
+	var node = new node_t();
+	node.start = other.start;
+	if (options.locations) {
+	  node.loc = new node_loc_t();
+	  node.loc.start = other.loc.start;
+	}
+	if (options.ranges)
+	  node.range = [other.range[0], 0];
+
+	return node;
+  }
+
+  function finishNode(node, type) {
+	node.type = type;
+	node.end = lastEnd;
+	if (options.locations)
+	  node.loc.end = lastEndLoc;
+	if (options.ranges)
+	  node.range[1] = lastEnd;
+	return node;
+  }
+
+  function isUseStrict(stmt) {
+	return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
+	  stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
+  }
+
+  function eat(type) {
+	if (tokType === type) {
+	  next();
+	  return true;
+	}
+  }
+
+  function canInsertSemicolon() {
+	return !options.strictSemicolons &&
+	  (tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart)));
+  }
+
+  function semicolon() {
+	if (!eat(_semi) && !canInsertSemicolon()) unexpected();
+  }
+
+  function expect(type) {
+	if (tokType === type) next();
+	else unexpected();
+  }
+
+  function unexpected() {
+	raise(tokStart, "Unexpected token");
+  }
+
+  function checkLVal(expr) {
+	if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
+	  raise(expr.start, "Assigning to rvalue");
+	if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
+	  raise(expr.start, "Assigning to " + expr.name + " in strict mode");
+  }
+
+  function parseTopLevel(program) {
+	lastStart = lastEnd = tokPos;
+	if (options.locations) lastEndLoc = new line_loc_t;
+	inFunction = strict = null;
+	labels = [];
+	readToken();
+
+	var node = program || startNode(), first = true;
+	if (!program) node.body = [];
+	while (tokType !== _eof) {
+	  var stmt = parseStatement();
+	  node.body.push(stmt);
+	  if (first && isUseStrict(stmt)) setStrict(true);
+	  first = false;
+	}
+	return finishNode(node, "Program");
+  }
+
+  var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"};
+
+  function parseStatement() {
+	if (tokType === _slash || tokType === _assign && tokVal == "/=")
+	  readToken(true);
+
+	var starttype = tokType, node = startNode();
+
+	switch (starttype) {
+	case _break: case _continue:
+	  next();
+	  var isBreak = starttype === _break;
+	  if (eat(_semi) || canInsertSemicolon()) node.label = null;
+	  else if (tokType !== _name) unexpected();
+	  else {
+		node.label = parseIdent();
+		semicolon();
+	  }
+
+	  for (var i = 0; i < labels.length; ++i) {
+		var lab = labels[i];
+		if (node.label == null || lab.name === node.label.name) {
+		  if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
+		  if (node.label && isBreak) break;
+		}
+	  }
+	  if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword);
+	  return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+
+	case _debugger:
+	  next();
+	  semicolon();
+	  return finishNode(node, "DebuggerStatement");
+
+	case _do:
+	  next();
+	  labels.push(loopLabel);
+	  node.body = parseStatement();
+	  labels.pop();
+	  expect(_while);
+	  node.test = parseParenExpression();
+	  semicolon();
+	  return finishNode(node, "DoWhileStatement");
+
+	case _for:
+	  next();
+	  labels.push(loopLabel);
+	  expect(_parenL);
+	  if (tokType === _semi) return parseFor(node, null);
+	  if (tokType === _var) {
+		var init = startNode();
+		next();
+		parseVar(init, true);
+		finishNode(init, "VariableDeclaration");
+		if (init.declarations.length === 1 && eat(_in))
+		  return parseForIn(node, init);
+		return parseFor(node, init);
+	  }
+	  var init = parseExpression(false, true);
+	  if (eat(_in)) {checkLVal(init); return parseForIn(node, init);}
+	  return parseFor(node, init);
+
+	case _function:
+	  next();
+	  return parseFunction(node, true);
+
+	case _if:
+	  next();
+	  node.test = parseParenExpression();
+	  node.consequent = parseStatement();
+	  node.alternate = eat(_else) ? parseStatement() : null;
+	  return finishNode(node, "IfStatement");
+
+	case _return:
+	  if (!inFunction && !options.allowReturnOutsideFunction)
+		raise(tokStart, "'return' outside of function");
+	  next();
+
+	  if (eat(_semi) || canInsertSemicolon()) node.argument = null;
+	  else { node.argument = parseExpression(); semicolon(); }
+	  return finishNode(node, "ReturnStatement");
+
+	case _switch:
+	  next();
+	  node.discriminant = parseParenExpression();
+	  node.cases = [];
+	  expect(_braceL);
+	  labels.push(switchLabel);
+
+	  for (var cur, sawDefault; tokType != _braceR;) {
+		if (tokType === _case || tokType === _default) {
+		  var isCase = tokType === _case;
+		  if (cur) finishNode(cur, "SwitchCase");
+		  node.cases.push(cur = startNode());
+		  cur.consequent = [];
+		  next();
+		  if (isCase) cur.test = parseExpression();
+		  else {
+			if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true;
+			cur.test = null;
+		  }
+		  expect(_colon);
+		} else {
+		  if (!cur) unexpected();
+		  cur.consequent.push(parseStatement());
+		}
+	  }
+	  if (cur) finishNode(cur, "SwitchCase");
+	  next();
+	  labels.pop();
+	  return finishNode(node, "SwitchStatement");
+
+	case _throw:
+	  next();
+	  if (newline.test(input.slice(lastEnd, tokStart)))
+		raise(lastEnd, "Illegal newline after throw");
+	  node.argument = parseExpression();
+	  semicolon();
+	  return finishNode(node, "ThrowStatement");
+
+	case _try:
+	  next();
+	  node.block = parseBlock();
+	  node.handler = null;
+	  if (tokType === _catch) {
+		var clause = startNode();
+		next();
+		expect(_parenL);
+		clause.param = parseIdent();
+		if (strict && isStrictBadIdWord(clause.param.name))
+		  raise(clause.param.start, "Binding " + clause.param.name + " in strict mode");
+		expect(_parenR);
+		clause.guard = null;
+		clause.body = parseBlock();
+		node.handler = finishNode(clause, "CatchClause");
+	  }
+	  node.guardedHandlers = empty;
+	  node.finalizer = eat(_finally) ? parseBlock() : null;
+	  if (!node.handler && !node.finalizer)
+		raise(node.start, "Missing catch or finally clause");
+	  return finishNode(node, "TryStatement");
+
+	case _var:
+	  next();
+	  parseVar(node);
+	  semicolon();
+	  return finishNode(node, "VariableDeclaration");
+
+	case _while:
+	  next();
+	  node.test = parseParenExpression();
+	  labels.push(loopLabel);
+	  node.body = parseStatement();
+	  labels.pop();
+	  return finishNode(node, "WhileStatement");
+
+	case _with:
+	  if (strict) raise(tokStart, "'with' in strict mode");
+	  next();
+	  node.object = parseParenExpression();
+	  node.body = parseStatement();
+	  return finishNode(node, "WithStatement");
+
+	case _braceL:
+	  return parseBlock();
+
+	case _semi:
+	  next();
+	  return finishNode(node, "EmptyStatement");
+
+	default:
+	  var maybeName = tokVal, expr = parseExpression();
+	  if (starttype === _name && expr.type === "Identifier" && eat(_colon)) {
+		for (var i = 0; i < labels.length; ++i)
+		  if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared");
+		var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
+		labels.push({name: maybeName, kind: kind});
+		node.body = parseStatement();
+		labels.pop();
+		node.label = expr;
+		return finishNode(node, "LabeledStatement");
+	  } else {
+		node.expression = expr;
+		semicolon();
+		return finishNode(node, "ExpressionStatement");
+	  }
+	}
+  }
+
+  function parseParenExpression() {
+	expect(_parenL);
+	var val = parseExpression();
+	expect(_parenR);
+	return val;
+  }
+
+  function parseBlock(allowStrict) {
+	var node = startNode(), first = true, strict = false, oldStrict;
+	node.body = [];
+	expect(_braceL);
+	while (!eat(_braceR)) {
+	  var stmt = parseStatement();
+	  node.body.push(stmt);
+	  if (first && allowStrict && isUseStrict(stmt)) {
+		oldStrict = strict;
+		setStrict(strict = true);
+	  }
+	  first = false;
+	}
+	if (strict && !oldStrict) setStrict(false);
+	return finishNode(node, "BlockStatement");
+  }
+
+  function parseFor(node, init) {
+	node.init = init;
+	expect(_semi);
+	node.test = tokType === _semi ? null : parseExpression();
+	expect(_semi);
+	node.update = tokType === _parenR ? null : parseExpression();
+	expect(_parenR);
+	node.body = parseStatement();
+	labels.pop();
+	return finishNode(node, "ForStatement");
+  }
+
+  function parseForIn(node, init) {
+	node.left = init;
+	node.right = parseExpression();
+	expect(_parenR);
+	node.body = parseStatement();
+	labels.pop();
+	return finishNode(node, "ForInStatement");
+  }
+
+  function parseVar(node, noIn) {
+	node.declarations = [];
+	node.kind = "var";
+	for (;;) {
+	  var decl = startNode();
+	  decl.id = parseIdent();
+	  if (strict && isStrictBadIdWord(decl.id.name))
+		raise(decl.id.start, "Binding " + decl.id.name + " in strict mode");
+	  decl.init = eat(_eq) ? parseExpression(true, noIn) : null;
+	  node.declarations.push(finishNode(decl, "VariableDeclarator"));
+	  if (!eat(_comma)) break;
+	}
+	return node;
+  }
+
+  function parseExpression(noComma, noIn) {
+	var expr = parseMaybeAssign(noIn);
+	if (!noComma && tokType === _comma) {
+	  var node = startNodeFrom(expr);
+	  node.expressions = [expr];
+	  while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
+	  return finishNode(node, "SequenceExpression");
+	}
+	return expr;
+  }
+
+  function parseMaybeAssign(noIn) {
+	var left = parseMaybeConditional(noIn);
+	if (tokType.isAssign) {
+	  var node = startNodeFrom(left);
+	  node.operator = tokVal;
+	  node.left = left;
+	  next();
+	  node.right = parseMaybeAssign(noIn);
+	  checkLVal(left);
+	  return finishNode(node, "AssignmentExpression");
+	}
+	return left;
+  }
+
+  function parseMaybeConditional(noIn) {
+	var expr = parseExprOps(noIn);
+	if (eat(_question)) {
+	  var node = startNodeFrom(expr);
+	  node.test = expr;
+	  node.consequent = parseExpression(true);
+	  expect(_colon);
+	  node.alternate = parseExpression(true, noIn);
+	  return finishNode(node, "ConditionalExpression");
+	}
+	return expr;
+  }
+
+  function parseExprOps(noIn) {
+	return parseExprOp(parseMaybeUnary(), -1, noIn);
+  }
+
+  function parseExprOp(left, minPrec, noIn) {
+	var prec = tokType.binop;
+	if (prec != null && (!noIn || tokType !== _in)) {
+	  if (prec > minPrec) {
+		var node = startNodeFrom(left);
+		node.left = left;
+		node.operator = tokVal;
+		var op = tokType;
+		next();
+		node.right = parseExprOp(parseMaybeUnary(), prec, noIn);
+		var exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression");
+		return parseExprOp(exprNode, minPrec, noIn);
+	  }
+	}
+	return left;
+  }
+
+  function parseMaybeUnary() {
+	if (tokType.prefix) {
+	  var node = startNode(), update = tokType.isUpdate;
+	  node.operator = tokVal;
+	  node.prefix = true;
+	  tokRegexpAllowed = true;
+	  next();
+	  node.argument = parseMaybeUnary();
+	  if (update) checkLVal(node.argument);
+	  else if (strict && node.operator === "delete" &&
+			   node.argument.type === "Identifier")
+		raise(node.start, "Deleting local variable in strict mode");
+	  return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
+	}
+	var expr = parseExprSubscripts();
+	while (tokType.postfix && !canInsertSemicolon()) {
+	  var node = startNodeFrom(expr);
+	  node.operator = tokVal;
+	  node.prefix = false;
+	  node.argument = expr;
+	  checkLVal(expr);
+	  next();
+	  expr = finishNode(node, "UpdateExpression");
+	}
+	return expr;
+  }
+
+  function parseExprSubscripts() {
+	return parseSubscripts(parseExprAtom());
+  }
+
+  function parseSubscripts(base, noCalls) {
+	if (eat(_dot)) {
+	  var node = startNodeFrom(base);
+	  node.object = base;
+	  node.property = parseIdent(true);
+	  node.computed = false;
+	  return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
+	} else if (eat(_bracketL)) {
+	  var node = startNodeFrom(base);
+	  node.object = base;
+	  node.property = parseExpression();
+	  node.computed = true;
+	  expect(_bracketR);
+	  return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
+	} else if (!noCalls && eat(_parenL)) {
+	  var node = startNodeFrom(base);
+	  node.callee = base;
+	  node.arguments = parseExprList(_parenR, false);
+	  return parseSubscripts(finishNode(node, "CallExpression"), noCalls);
+	} else return base;
+  }
+
+  function parseExprAtom() {
+	switch (tokType) {
+	case _this:
+	  var node = startNode();
+	  next();
+	  return finishNode(node, "ThisExpression");
+	case _name:
+	  return parseIdent();
+	case _num: case _string: case _regexp:
+	  var node = startNode();
+	  node.value = tokVal;
+	  node.raw = input.slice(tokStart, tokEnd);
+	  next();
+	  return finishNode(node, "Literal");
+
+	case _null: case _true: case _false:
+	  var node = startNode();
+	  node.value = tokType.atomValue;
+	  node.raw = tokType.keyword;
+	  next();
+	  return finishNode(node, "Literal");
+
+	case _parenL:
+	  var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart;
+	  next();
+	  var val = parseExpression();
+	  val.start = tokStart1;
+	  val.end = tokEnd;
+	  if (options.locations) {
+		val.loc.start = tokStartLoc1;
+		val.loc.end = tokEndLoc;
+	  }
+	  if (options.ranges)
+		val.range = [tokStart1, tokEnd];
+	  expect(_parenR);
+	  return val;
+
+	case _bracketL:
+	  var node = startNode();
+	  next();
+	  node.elements = parseExprList(_bracketR, true, true);
+	  return finishNode(node, "ArrayExpression");
+
+	case _braceL:
+	  return parseObj();
+
+	case _function:
+	  var node = startNode();
+	  next();
+	  return parseFunction(node, false);
+
+	case _new:
+	  return parseNew();
+
+	default:
+	  unexpected();
+	}
+  }
+
+  function parseNew() {
+	var node = startNode();
+	next();
+	node.callee = parseSubscripts(parseExprAtom(), true);
+	if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
+	else node.arguments = empty;
+	return finishNode(node, "NewExpression");
+  }
+
+  function parseObj() {
+	var node = startNode(), first = true, sawGetSet = false;
+	node.properties = [];
+	next();
+	while (!eat(_braceR)) {
+	  if (!first) {
+		expect(_comma);
+		if (options.allowTrailingCommas && eat(_braceR)) break;
+	  } else first = false;
+
+	  var prop = {key: parsePropertyName()}, isGetSet = false, kind;
+	  if (eat(_colon)) {
+		prop.value = parseExpression(true);
+		kind = prop.kind = "init";
+	  } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
+				 (prop.key.name === "get" || prop.key.name === "set")) {
+		isGetSet = sawGetSet = true;
+		kind = prop.kind = prop.key.name;
+		prop.key = parsePropertyName();
+		if (tokType !== _parenL) unexpected();
+		prop.value = parseFunction(startNode(), false);
+	  } else unexpected();
+
+	  if (prop.key.type === "Identifier" && (strict || sawGetSet)) {
+		for (var i = 0; i < node.properties.length; ++i) {
+		  var other = node.properties[i];
+		  if (other.key.name === prop.key.name) {
+			var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
+			  kind === "init" && (other.kind === "get" || other.kind === "set");
+			if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
+			if (conflict) raise(prop.key.start, "Redefinition of property");
+		  }
+		}
+	  }
+	  node.properties.push(prop);
+	}
+	return finishNode(node, "ObjectExpression");
+  }
+
+  function parsePropertyName() {
+	if (tokType === _num || tokType === _string) return parseExprAtom();
+	return parseIdent(true);
+  }
+
+  function parseFunction(node, isStatement) {
+	if (tokType === _name) node.id = parseIdent();
+	else if (isStatement) unexpected();
+	else node.id = null;
+	node.params = [];
+	var first = true;
+	expect(_parenL);
+	while (!eat(_parenR)) {
+	  if (!first) expect(_comma); else first = false;
+	  node.params.push(parseIdent());
+	}
+
+	var oldInFunc = inFunction, oldLabels = labels;
+	inFunction = true; labels = [];
+	node.body = parseBlock(true);
+	inFunction = oldInFunc; labels = oldLabels;
+
+	if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
+	  for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {
+		var id = i < 0 ? node.id : node.params[i];
+		if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))
+		  raise(id.start, "Defining '" + id.name + "' in strict mode");
+		if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)
+		  raise(id.start, "Argument name clash in strict mode");
+	  }
+	}
+
+	return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
+  }
+
+  function parseExprList(close, allowTrailingComma, allowEmpty) {
+	var elts = [], first = true;
+	while (!eat(close)) {
+	  if (!first) {
+		expect(_comma);
+		if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
+	  } else first = false;
+
+	  if (allowEmpty && tokType === _comma) elts.push(null);
+	  else elts.push(parseExpression(true));
+	}
+	return elts;
+  }
+
+  function parseIdent(liberal) {
+	var node = startNode();
+	if (liberal && options.forbidReserved == "everywhere") liberal = false;
+	if (tokType === _name) {
+	  if (!liberal &&
+		  (options.forbidReserved &&
+		   (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(tokVal) ||
+		   strict && isStrictReservedWord(tokVal)) &&
+		  input.slice(tokStart, tokEnd).indexOf("\\") == -1)
+		raise(tokStart, "The keyword '" + tokVal + "' is reserved");
+	  node.name = tokVal;
+	} else if (liberal && tokType.keyword) {
+	  node.name = tokType.keyword;
+	} else {
+	  unexpected();
+	}
+	tokRegexpAllowed = false;
+	next();
+	return finishNode(node, "Identifier");
+  }
+
+});
+
+		if (!acorn.version)
+			acorn = null;
+	}
+
+	function parse(code, options) {
+		return (global.acorn || acorn).parse(code, options);
+	}
 
 	var binaryOperators = {
 		'+': '__add',
@@ -12889,21 +16640,25 @@
 		'*': '__multiply',
 		'/': '__divide',
 		'%': '__modulo',
-		'==': 'equals',
-		'!=': 'equals'
+		'==': '__equals',
+		'!=': '__equals'
 	};
 
 	var unaryOperators = {
 		'-': '__negate',
-		'+': null
+		'+': '__self'
 	};
 
 	var fields = Base.each(
-		['add', 'subtract', 'multiply', 'divide', 'modulo', 'negate'],
+		['add', 'subtract', 'multiply', 'divide', 'modulo', 'equals', 'negate'],
 		function(name) {
 			this['__' + name] = '#' + name;
 		},
-		{}
+		{
+			__self: function() {
+				return this;
+			}
+		}
 	);
 	Point.inject(fields);
 	Size.inject(fields);
@@ -12928,7 +16683,7 @@
 
 	function $__(operator, value) {
 		var handler = unaryOperators[operator];
-		if (handler && value && value[handler])
+		if (value && value[handler])
 			return value[handler]();
 		switch (operator) {
 		case '+': return +value;
@@ -12936,15 +16691,10 @@
 		}
 	}
 
-	function parse(code, options) {
-		return scope.acorn.parse(code, options);
-	}
-
-	function compile(code, url, options) {
+	function compile(code, options) {
 		if (!code)
 			return '';
 		options = options || {};
-		url = url || '';
 
 		var insertions = [];
 
@@ -13032,9 +16782,13 @@
 							exp = '__$__(' + arg + ', "' + node.operator[0]
 									+ '", 1)',
 							str = arg + ' = ' + exp;
-						if (!node.prefix
-								&& (parentType === 'AssignmentExpression'
-									|| parentType === 'VariableDeclarator')) {
+						if (node.prefix) {
+							str = '(' + str + ')';
+						} else if (
+							parentType === 'AssignmentExpression' ||
+							parentType === 'VariableDeclarator' ||
+							parentType === 'BinaryExpression'
+						) {
 							if (getCode(parent.left || parent.id) === arg)
 								str = exp;
 							str = arg + '; ' + str;
@@ -13044,65 +16798,138 @@
 						if (/^.=$/.test(node.operator)
 								&& node.left.type !== 'Literal') {
 							var left = getCode(node.left),
-								right = getCode(node.right);
-							replaceCode(node, left + ' = __$__(' + left + ', "'
-									+ node.operator[0] + '", ' + right + ')');
+								right = getCode(node.right),
+								exp = left + ' = __$__(' + left + ', "'
+									+ node.operator[0] + '", ' + right + ')';
+							replaceCode(node, /^\(.*\)$/.test(getCode(node))
+									? '(' + exp + ')' : exp);
 						}
 					}
 				}
 				break;
-			}
-		}
-		var sourceMap = null,
-			browser = paper.browser,
-			version = browser.versionNumber,
-			lineBreaks = /\r\n|\n|\r/mg;
-		if (browser.chrome && version >= 30
-				|| browser.webkit && version >= 537.76
-				|| browser.firefox && version >= 23) {
-			var offset = 0;
-			if (window.location.href.indexOf(url) === 0) {
+			case 'ExportDefaultDeclaration':
+				replaceCode({
+					range: [node.start, node.declaration.start]
+				}, 'module.exports = ');
+				break;
+			case 'ExportNamedDeclaration':
+				var declaration = node.declaration;
+				var specifiers = node.specifiers;
+				if (declaration) {
+					var declarations = declaration.declarations;
+					if (declarations) {
+						declarations.forEach(function(dec) {
+							replaceCode(dec, 'module.exports.' + getCode(dec));
+						});
+						replaceCode({
+							range: [
+								node.start,
+								declaration.start + declaration.kind.length
+							]
+						}, '');
+					}
+				} else if (specifiers) {
+					var exports = specifiers.map(function(specifier) {
+						var name = getCode(specifier);
+						return 'module.exports.' + name + ' = ' + name + '; ';
+					}).join('');
+					if (exports) {
+						replaceCode(node, exports);
+					}
+				}
+				break;
+			}
+		}
+
+		function encodeVLQ(value) {
+			var res = '',
+				base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+			value = (Math.abs(value) << 1) + (value < 0 ? 1 : 0);
+			while (value || !res) {
+				var next = value & (32 - 1);
+				value >>= 5;
+				if (value)
+					next |= 32;
+				res += base64[next];
+			}
+			return res;
+		}
+
+		var url = options.url || '',
+			agent = paper.agent,
+			version = agent.versionNumber,
+			offsetCode = false,
+			sourceMaps = options.sourceMaps,
+			source = options.source || code,
+			lineBreaks = /\r\n|\n|\r/mg,
+			offset = options.offset || 0,
+			map;
+		if (sourceMaps && (agent.chrome && version >= 30
+				|| agent.webkit && version >= 537.76
+				|| agent.firefox && version >= 23
+				|| agent.node)) {
+			if (agent.node) {
+				offset -= 2;
+			} else if (window && url && !window.location.href.indexOf(url)) {
 				var html = document.getElementsByTagName('html')[0].innerHTML;
 				offset = html.substr(0, html.indexOf(code) + 1).match(
 						lineBreaks).length + 1;
 			}
-			var mappings = ['AAAA'];
-			mappings.length = (code.match(lineBreaks) || []).length + 1 + offset;
-			sourceMap = {
+			offsetCode = offset > 0 && !(
+					agent.chrome && version >= 36 ||
+					agent.safari && version >= 600 ||
+					agent.firefox && version >= 40 ||
+					agent.node);
+			var mappings = ['AA' + encodeVLQ(offsetCode ? 0 : offset) + 'A'];
+			mappings.length = (code.match(lineBreaks) || []).length + 1
+					+ (offsetCode ? offset : 0);
+			map = {
 				version: 3,
 				file: url,
 				names:[],
 				mappings: mappings.join(';AACA'),
 				sourceRoot: '',
-				sources: [url]
+				sources: [url],
+				sourcesContent: [source]
 			};
-			var source = options.source || !url && code;
-			if (source)
-				sourceMap.sourcesContent = [source];
-		}
-		walkAST(parse(code, { ranges: true }));
-		if (sourceMap) {
-			code = new Array(offset + 1).join('\n') + code
-					+ "\n//# sourceMappingURL=data:application/json;base64,"
-					+ (btoa(unescape(encodeURIComponent(
-						JSON.stringify(sourceMap)))))
-					+ "\n//# sourceURL=" + (url || 'paperscript');
-		}
-		return code;
-	}
-
-	function execute(code, scope, url, options) {
+		}
+		walkAST(parse(code, {
+			ranges: true,
+			preserveParens: true,
+			sourceType: 'module'
+		}));
+		if (map) {
+			if (offsetCode) {
+				code = new Array(offset + 1).join('\n') + code;
+			}
+			if (/^(inline|both)$/.test(sourceMaps)) {
+				code += "\n//# sourceMappingURL=data:application/json;base64,"
+						+ self.btoa(unescape(encodeURIComponent(
+							JSON.stringify(map))));
+			}
+			code += "\n//# sourceURL=" + (url || 'paperscript');
+		}
+		return {
+			url: url,
+			source: source,
+			code: code,
+			map: map
+		};
+	}
+
+	function execute(code, scope, options) {
 		paper = scope;
 		var view = scope.getView(),
-			tool = /\s+on(?:Key|Mouse)(?:Up|Down|Move|Drag)\b/.test(code)
-					? new Tool()
-					: null,
+			tool = /\btool\.\w+|\s+on(?:Key|Mouse)(?:Up|Down|Move|Drag)\b/
+					.test(code) && !/\bnew\s+Tool\b/.test(code)
+						? new Tool() : null,
 			toolHandlers = tool ? tool._events : [],
 			handlers = ['onFrame', 'onResize'].concat(toolHandlers),
 			params = [],
 			args = [],
-			func;
-		code = compile(code, url, options);
+			func,
+			compiled = typeof code === 'object' ? code : compile(code, options);
+		code = compiled.code;
 		function expose(scope, hidden) {
 			for (var key in scope) {
 				if ((hidden || !/^_/.test(key)) && new RegExp('([\\b\\s\\W]|^)'
@@ -13112,50 +16939,58 @@
 				}
 			}
 		}
-		expose({ __$__: __$__, $__: $__, paper: scope, view: view, tool: tool },
+		expose({ __$__: __$__, $__: $__, paper: scope, tool: tool },
 				true);
 		expose(scope);
-		handlers = Base.each(handlers, function(key) {
+		code = 'var module = { exports: {} }; ' + code;
+		var exports = Base.each(handlers, function(key) {
 			if (new RegExp('\\s+' + key + '\\b').test(code)) {
 				params.push(key);
-				this.push(key + ': ' + key);
-			}
-		}, []).join(', ');
-		if (handlers)
-			code += '\nreturn { ' + handlers + ' };';
-		var browser = paper.browser;
-		if (browser.chrome || browser.firefox) {
+				this.push('module.exports.' + key + ' = ' + key + ';');
+			}
+		}, []).join('\n');
+		if (exports) {
+			code += '\n' + exports;
+		}
+		code += '\nreturn module.exports;';
+		var agent = paper.agent;
+		if (document && (agent.chrome
+				|| agent.firefox && agent.versionNumber < 40)) {
 			var script = document.createElement('script'),
 				head = document.head || document.getElementsByTagName('head')[0];
-			if (browser.firefox)
+			if (agent.firefox)
 				code = '\n' + code;
 			script.appendChild(document.createTextNode(
-				'paper._execute = function(' + params + ') {' + code + '\n}'
+				'document.__paperscript__ = function(' + params + ') {' +
+					code +
+				'\n}'
 			));
 			head.appendChild(script);
-			func = paper._execute;
-			delete paper._execute;
+			func = document.__paperscript__;
+			delete document.__paperscript__;
 			head.removeChild(script);
 		} else {
 			func = Function(params, code);
 		}
-		var res = func.apply(scope, args) || {};
+		var exports = func && func.apply(scope, args);
+		var obj = exports || {};
 		Base.each(toolHandlers, function(key) {
-			var value = res[key];
+			var value = obj[key];
 			if (value)
 				tool[key] = value;
 		});
 		if (view) {
-			if (res.onResize)
-				view.setOnResize(res.onResize);
+			if (obj.onResize)
+				view.setOnResize(obj.onResize);
 			view.emit('resize', {
 				size: view.size,
 				delta: new Point()
 			});
-			if (res.onFrame)
-				view.setOnFrame(res.onFrame);
-			view.update();
-		}
+			if (obj.onFrame)
+				view.setOnFrame(obj.onFrame);
+			view.requestUpdate();
+		}
+		return exports;
 	}
 
 	function loadScript(script) {
@@ -13173,9 +17008,14 @@
 						|| new PaperScope().setup(canvas);
 			canvas.setAttribute(scopeAttribute, scope._id);
 			if (src) {
-				Http.request('get', src, function(code) {
-					execute(code, scope, src);
-				}, async);
+				Http.request({
+					url: src,
+					async: async,
+					mimeType: 'text/plain',
+					onLoad: function(code) {
+						execute(code, scope, src);
+					}
+				});
 			} else {
 				execute(script.innerHTML, scope, script.baseURI);
 			}
@@ -13185,35 +17025,49 @@
 	}
 
 	function loadAll() {
-		Base.each(document.getElementsByTagName('script'), loadScript);
+		Base.each(document && document.getElementsByTagName('script'),
+				loadScript);
 	}
 
 	function load(script) {
 		return script ? loadScript(script) : loadAll();
 	}
 
-	if (document.readyState === 'complete') {
-		setTimeout(loadAll);
-	} else {
-		DomEvent.add(window, { load: loadAll });
+	if (window) {
+		if (document.readyState === 'complete') {
+			setTimeout(loadAll);
+		} else {
+			DomEvent.add(window, { load: loadAll });
+		}
 	}
 
 	return {
 		compile: compile,
 		execute: execute,
 		load: load,
-		parse: parse
+		parse: parse,
+		calculateBinary: __$__,
+		calculateUnary: $__
 	};
 
-}).call(this);
-
-paper = new (PaperScope.inject(Base.exports, {
-	enumerable: true,
+}.call(this);
+
+var paper = new (PaperScope.inject(Base.exports, {
 	Base: Base,
 	Numerical: Numerical,
-	Key: Key
+	Key: Key,
+	DomEvent: DomEvent,
+	DomElement: DomElement,
+	document: document,
+	window: window,
+	Symbol: SymbolDefinition,
+	PlacedSymbol: SymbolItem
 }))();
 
+if (paper.agent.node) {
+	require('./node/extend.js')(paper);
+}
+
 if (typeof define === 'function' && define.amd) {
 	define('paper', paper);
 } else if (typeof module === 'object' && module) {
@@ -13221,4 +17075,4 @@
 }
 
 return paper;
-};
+}.call(this, typeof self === 'object' ? self : null);