server/php/basic/public_html/static/lib/paper/paper-full.js
changeset 495 444b80998255
parent 442 adb907bba956
child 598 eb4f4eceada0
--- a/server/php/basic/public_html/static/lib/paper/paper-full.js	Fri Aug 28 23:04:21 2015 +0200
+++ b/server/php/basic/public_html/static/lib/paper/paper-full.js	Mon Sep 07 16:34:30 2015 +0200
@@ -1,5 +1,5 @@
 /*!
- * Paper.js v0.9.22 - The Swiss Army Knife of Vector Graphics Scripting.
+ * Paper.js v0.9.24 - The Swiss Army Knife of Vector Graphics Scripting.
  * http://paperjs.org/
  *
  * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
@@ -9,7 +9,7 @@
  *
  * All rights reserved.
  *
- * Date: Sat Feb 28 19:20:48 2015 +0100
+ * Date: Fri Aug 21 16:39:41 2015 +0200
  *
  ***
  *
@@ -88,7 +88,7 @@
 				val = dest[val.substring(1)] || val;
 			var isFunc = typeof val === 'function',
 				res = val,
-				prev = preserve || isFunc
+				prev = preserve || isFunc && !val.base
 						? (val && val.get ? name in dest : dest[name])
 						: null,
 				bean;
@@ -161,19 +161,22 @@
 
 		extend: function() {
 			var base = this,
-				ctor;
+				ctor,
+				proto;
 			for (var i = 0, l = arguments.length; i < l; i++)
 				if (ctor = arguments[i].initialize)
 					break;
 			ctor = ctor || function() {
 				base.apply(this, arguments);
 			};
-			ctor.prototype = create(this.prototype);
-			ctor.base = base;
-			define(ctor.prototype, 'constructor',
+			proto = ctor.prototype = create(this.prototype);
+			define(proto, 'constructor',
 					{ value: ctor, writable: true, configurable: true });
 			inject(ctor, this, true);
-			return arguments.length ? this.inject.apply(ctor, arguments) : ctor;
+			if (arguments.length)
+				this.inject.apply(ctor, arguments);
+			ctor.base = base;
+			return ctor;
 		}
 	}, true).inject({
 		inject: function() {
@@ -259,9 +262,10 @@
 
 	_set: function(props, exclude, dontCheck) {
 		if (props && (dontCheck || Base.isPlainObject(props))) {
-			var orig = props._filtering || props;
-			for (var key in orig) {
-				if (orig.hasOwnProperty(key) && !(exclude && exclude[key])) {
+			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;
@@ -286,35 +290,33 @@
 		},
 
 		equals: function(obj1, obj2) {
-			function checkKeys(o1, o2) {
-				for (var i in o1)
-					if (o1.hasOwnProperty(i) && !o2.hasOwnProperty(i))
-						return false;
-				return true;
-			}
 			if (obj1 === obj2)
 				return true;
 			if (obj1 && obj1.equals)
 				return obj1.equals(obj2);
 			if (obj2 && obj2.equals)
 				return obj2.equals(obj1);
-			if (Array.isArray(obj1) && Array.isArray(obj2)) {
-				if (obj1.length !== obj2.length)
-					return false;
-				for (var i = 0, l = obj1.length; i < l; i++) {
-					if (!Base.equals(obj1[i], obj2[i]))
+			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;
-				}
-				return true;
-			}
-			if (obj1 && typeof obj1 === 'object'
-					&& obj2 && typeof obj2 === 'object') {
-				if (!checkKeys(obj1, obj2) || !checkKeys(obj2, obj1))
-					return false;
-				for (var i in obj1) {
-					if (obj1.hasOwnProperty(i)
-							&& !Base.equals(obj1[i], obj2[i]))
+					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;
 			}
@@ -443,10 +445,12 @@
 					res._compact = true;
 			} else if (Base.isPlainObject(obj)) {
 				res = {};
-				for (var i in obj)
-					if (obj.hasOwnProperty(i))
-						res[i] = Base.serialize(obj[i], options, compact,
-								dictionary);
+				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 {
@@ -457,24 +461,23 @@
 					: res;
 		},
 
-		deserialize: function(json, create, _data) {
+		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 (!isDictionary) {
-					if (_data.dictionary && json.length == 1 && /^#/.test(type))
-						return _data.dictionary[type];
-					type = Base.exports[type];
-				}
+				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));
-				if (isDictionary) {
-					_data.dictionary = res[0];
-				} else if (type) {
+					res.push(Base.deserialize(json[i], create, _data,
+							isDictionary));
+				if (type) {
 					var args = res;
 					if (create) {
 						res = create(type, args);
@@ -485,6 +488,8 @@
 				}
 			} else if (Base.isPlainObject(json)) {
 				res = {};
+				if (_isDictionary)
+					_data.dictionary = res;
 				for (var key in json)
 					res[key] = Base.deserialize(json[key], create, _data);
 			}
@@ -570,15 +575,14 @@
 				this.on(key, value);
 			}, this);
 		} else {
-			var entry = this._eventTypes[type];
-			if (entry) {
-				var handlers = this._callbacks = this._callbacks || {};
-				handlers = handlers[type] = handlers[type] || [];
-				if (handlers.indexOf(func) === -1) {
-					handlers.push(func);
-					if (entry.install && handlers.length == 1)
-						entry.install.call(this, type);
-				}
+			var types = this._eventTypes,
+				entry = types && types[type],
+				handlers = this._callbacks = this._callbacks || {};
+			handlers = handlers[type] = handlers[type] || [];
+			if (handlers.indexOf(func) === -1) {
+				handlers.push(func);
+				if (entry && entry.install && handlers.length == 1)
+					entry.install.call(this, type);
 			}
 		}
 		return this;
@@ -591,13 +595,14 @@
 			}, this);
 			return;
 		}
-		var entry = this._eventTypes[type],
+		var types = this._eventTypes,
+			entry = types && types[type],
 			handlers = this._callbacks && this._callbacks[type],
 			index;
-		if (entry && handlers) {
+		if (handlers) {
 			if (!func || (index = handlers.indexOf(func)) !== -1
 					&& handlers.length === 1) {
-				if (entry.uninstall)
+				if (entry && entry.uninstall)
 					entry.uninstall.call(this, type);
 				delete this._callbacks[type];
 			} else if (index !== -1) {
@@ -619,10 +624,11 @@
 		if (!handlers)
 			return false;
 		var args = [].slice.call(arguments, 1);
+		handlers = handlers.slice();
 		for (var i = 0, l = handlers.length; i < l; i++) {
-			if (handlers[i].apply(this, args) === false
-					&& event && event.stop) {
-				event.stop();
+			if (handlers[i].apply(this, args) === false) {
+				if (event && event.stop)
+					event.stop();
 				break;
 			}
 		}
@@ -642,8 +648,9 @@
 			key = install ? 'install' : 'uninstall';
 		for (var type in handlers) {
 			if (handlers[type].length > 0) {
-				var entry = this._eventTypes[type],
-					func = entry[key];
+				var types = this._eventTypes,
+					entry = types && types[type],
+					func = entry && entry[key];
 				if (func)
 					func.call(this, type);
 			}
@@ -708,8 +715,15 @@
 		}
 
 		if (!this.browser) {
-			var browser = proto.browser = {};
-			navigator.userAgent.toLowerCase().replace(
+			var agent = navigator.userAgent.toLowerCase(),
+				platform = (/(win)/.exec(agent)
+						|| /(mac)/.exec(agent)
+						|| /(linux)/.exec(agent)
+						|| [])[0],
+				browser = 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) {
@@ -732,7 +746,7 @@
 		}
 	},
 
-	version: '0.9.22',
+	version: '0.9.24',
 
 	getView: function() {
 		return this.project && this.project.getView();
@@ -969,16 +983,9 @@
 				D;
 			b /= 2;
 			D = b * b - a * c;
-			if (abs(D) < MACHINE_EPSILON) {
-				var pow = Math.pow,
-					gmC = pow(abs(a*b*c), 1/3);
+			if (D !== 0 && abs(D) < MACHINE_EPSILON) {
+				var gmC = pow(abs(a * b * c), 1 / 3);
 				if (gmC < 1e-8) {
-					/*
-					 * we multiply with a factor to normalize the
-					 * coefficients. The factor is just the nearest exponent
-					 * of 10, big enough to raise all the coefficients to
-					 * nearly [-1, +1] range.
-					 */
 					var mult = pow(10, abs(
 						Math.floor(Math.log(gmC) * Math.LOG10E)));
 					if (!isFinite(mult))
@@ -989,9 +996,9 @@
 					D = b * b - a * c;
 				}
 			}
-			if (abs(a) < MACHINE_EPSILON) {
-				if (abs(B) < MACHINE_EPSILON)
-					return abs(c) < MACHINE_EPSILON ? -1 : 0;
+			if (abs(a) < EPSILON) {
+				if (abs(B) < EPSILON)
+					return abs(c) < EPSILON ? -1 : 0;
 				x1 = -c / B;
 			} else {
 				if (D >= -MACHINE_EPSILON) {
@@ -1016,13 +1023,14 @@
 		},
 
 		solveCubic: function(a, b, c, d, roots, min, max) {
-			var x, b1, c2, count = 0;
-			if (a === 0) {
+			var count = 0,
+				x, b1, c2;
+			if (abs(a) < EPSILON) {
 				a = b;
 				b1 = c;
 				c2 = d;
 				x = Infinity;
-			} else if (d === 0) {
+			} else if (abs(d) < EPSILON) {
 				b1 = b;
 				c2 = c;
 				x = 0;
@@ -1070,6 +1078,23 @@
 	};
 };
 
+var UID = {
+	_id: 1,
+	_pools: {},
+
+	get: function(ctor) {
+		if (ctor) {
+			var name = ctor._class,
+				pool = this._pools[name];
+			if (!pool)
+				pool = this._pools[name] = { _id: 1 };
+			return pool._id++;
+		} else {
+			return this._id++;
+		}
+	}
+};
+
 var Point = Base.extend({
 	_class: 'Point',
 	_readIndex: true,
@@ -1284,12 +1309,14 @@
 		return this.getDistance(point) < tolerance;
 	},
 
-	isColinear: function(point) {
-		return Math.abs(this.cross(point)) < 1e-12;
-	},
+	isCollinear: function(point) {
+		return Math.abs(this.cross(point)) < 0.000001;
+	},
+
+	isColinear: '#isCollinear',
 
 	isOrthogonal: function(point) {
-		return Math.abs(this.dot(point)) < 1e-12;
+		return Math.abs(this.dot(point)) < 0.000001;
 	},
 
 	isZero: function() {
@@ -2638,7 +2665,7 @@
 	_class: 'Symbol',
 
 	initialize: function Symbol(item, dontCenter) {
-		this._id = Symbol._id = (Symbol._id || 0) + 1;
+		this._id = UID.get();
 		this.project = paper.project;
 		this.project.symbols.push(this);
 		if (item)
@@ -2735,7 +2762,7 @@
 			matrix = this._matrix = new Matrix(),
 			project = hasProps && props.project || paper.project;
 		if (!internal)
-			this._id = Item._id = (Item._id || 0) + 1;
+			this._id = UID.get();
 		this._applyMatrix = this._canApplyMatrix && paper.settings.applyMatrix;
 		if (point)
 			matrix.translate(point);
@@ -2751,7 +2778,8 @@
 			}
 		}
 		if (hasProps && props !== Item.NO_INSERT)
-			this._set(props, { insert: true, parent: true }, true);
+			this._set(props, { insert: true, project: true, parent: true },
+					true);
 		return hasProps;
 	},
 
@@ -3046,14 +3074,11 @@
 	},
 
 	setPivot: function() {
-		this._pivot = Point.read(arguments);
+		this._pivot = Point.read(arguments, 0, { clone: true, readNull: true });
 		this._position = undefined;
 	},
 
 	_pivot: null,
-
-	getRegistration: '#getPivot',
-	setRegistration: '#setPivot'
 }, Base.each(['bounds', 'strokeBounds', 'handleBounds', 'roughBounds',
 		'internalBounds', 'internalRoughBounds'],
 	function(key) {
@@ -3080,6 +3105,7 @@
 		var children = this._children;
 		if (!children || children.length == 0)
 			return new Rectangle();
+		Item._updateBoundsCache(this, cacheItem);
 		var x1 = Infinity,
 			x2 = -x1,
 			y1 = x1,
@@ -3120,18 +3146,7 @@
 		matrix = matrix && matrix.orNullIfIdentity();
 		var _matrix = internalGetter ? null : this._matrix.orNullIfIdentity(),
 			cache = (!matrix || matrix.equals(_matrix)) && getter;
-		var cacheParent = this._parent || this._parentSymbol;
-		if (cacheParent) {
-			var id = cacheItem._id,
-				ref = cacheParent._boundsCache = cacheParent._boundsCache || {
-					ids: {},
-					list: []
-				};
-			if (!ref.ids[id]) {
-				ref.list.push(cacheItem);
-				ref.ids[id] = cacheItem;
-			}
-		}
+		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,
@@ -3146,11 +3161,25 @@
 	},
 
 	statics: {
+		_updateBoundsCache: function(parent, item) {
+			if (parent) {
+				var id = item._id,
+					ref = parent._boundsCache = parent._boundsCache || {
+						ids: {},
+						list: []
+					};
+				if (!ref.ids[id]) {
+					ref.list.push(item);
+					ref.ids[id] = item;
+				}
+			}
+		},
+
 		_clearBoundsCache: function(item) {
 			var cache = item._boundsCache;
 			if (cache) {
 				item._bounds = item._position = item._boundsCache = undefined;
-				for (var i = 0, list = cache.list, l = list.length; i < l; i++) {
+				for (var i = 0, list = cache.list, l = list.length; i < l; i++){
 					var other = list[i];
 					if (other !== item) {
 						other._bounds = other._position = undefined;
@@ -3206,8 +3235,9 @@
 		return this._matrix;
 	},
 
-	setMatrix: function(matrix) {
-		this._matrix.initialize(matrix);
+	setMatrix: function() {
+		var matrix = this._matrix;
+		matrix.initialize.apply(matrix, arguments);
 		if (this._applyMatrix) {
 			this.transform(null, true);
 		} else {
@@ -3340,24 +3370,27 @@
 		return this._clone(new this.constructor(Item.NO_INSERT), insert);
 	},
 
-	_clone: function(copy, insert) {
+	_clone: function(copy, insert, includeMatrix) {
+		var keys = ['_locked', '_visible', '_blendMode', '_opacity',
+				'_clipMask', '_guide'],
+			children = this._children;
 		copy.setStyle(this._style);
-		if (this._children) {
-			for (var i = 0, l = this._children.length; i < l; i++)
-				copy.addChild(this._children[i].clone(false), true);
-		}
-		if (insert || insert === undefined)
-			copy.insertAbove(this);
-		var keys = ['_locked', '_visible', '_blendMode', '_opacity',
-				'_clipMask', '_guide', '_applyMatrix'];
+		for (var i = 0, l = children && children.length; i < l; i++) {
+			copy.addChild(children[i].clone(false), true);
+		}
 		for (var i = 0, l = keys.length; i < l; i++) {
 			var key = keys[i];
 			if (this.hasOwnProperty(key))
 				copy[key] = this[key];
 		}
-		copy._matrix.initialize(this._matrix);
+		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;
-		copy.setSelected(this._selected);
+		if (insert || insert === undefined)
+			copy.insertAbove(this);
 		if (this._name)
 			copy.setName(this._name, true);
 		return copy;
@@ -3517,11 +3550,14 @@
 			}
 			return true;
 		}
-		if (typeof name === 'object') {
+		var type = typeof name;
+		if (type === 'object') {
 			for (var key in name) {
 				if (name.hasOwnProperty(key) && !this.matches(key, name[key]))
 					return false;
 			}
+		} else if (type === 'function') {
+			return name(this);
 		} else {
 			var value = /^(empty|editable)$/.test(name)
 					? this['is' + Base.capitalize(name)]()
@@ -3559,15 +3595,17 @@
 	statics: {
 		_getItems: function _getItems(children, match, matrix, param,
 				firstOnly) {
-			if (!param) {
+			if (!param && typeof match === 'object') {
 				var overlapping = match.overlapping,
 					inside = match.inside,
 					bounds = overlapping || inside,
 					rect =	bounds && Rectangle.read([bounds]);
 				param = {
 					items: [],
-					inside: rect,
-					overlapping: overlapping && new Path.Rectangle({
+					inside: !!inside,
+					overlapping: !!overlapping,
+					rect: rect,
+					path: overlapping && new Path.Rectangle({
 						rectangle: rect,
 						insert: false
 					})
@@ -3576,20 +3614,20 @@
 					match = Base.set({}, match,
 							{ inside: true, overlapping: true });
 			}
-			var items = param.items,
-				inside = param.inside,
-				overlapping = param.overlapping;
-			matrix = inside && (matrix || new Matrix());
+			var items = param && param.items,
+				rect = param && 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),
 					add = true;
-				if (inside) {
+				if (rect) {
 					var bounds = child.getBounds(childMatrix);
-					if (!inside.intersects(bounds))
+					if (!rect.intersects(bounds))
 						continue;
-					if (!(inside && inside.contains(bounds)) && !(overlapping
-							&& overlapping.intersects(child, childMatrix)))
+					if (!(param.inside && rect.contains(bounds))
+							&& !(param.overlapping && (bounds.contains(rect)
+								|| param.path.intersects(child, childMatrix))))
 						add = false;
 				}
 				if (add && child.matches(match)) {
@@ -4066,7 +4104,7 @@
 					|| param.clip
 					|| (nativeBlend || normalBlend && opacity < 1)
 						&& this._canComposite(),
-			pixelRatio = param.pixelRatio,
+			pixelRatio = param.pixelRatio || 1,
 			mainCtx, itemOffset, prevOffset;
 		if (!direct) {
 			var bounds = this.getStrokeBounds(getViewMatrix(globalMatrix));
@@ -4422,16 +4460,14 @@
 	},
 
 	toPath: function(insert) {
-		var path = new Path[Base.capitalize(this._type)]({
+		var path = this._clone(new Path[Base.capitalize(this._type)]({
 			center: new Point(),
 			size: this._size,
 			radius: this._radius,
 			insert: false
-		});
-		path.setStyle(this._style);
-		path.transform(this._matrix);
-		if (insert || insert === undefined)
-			path.insertAbove(this);
+		}), insert);
+		if (paper.settings.applyMatrix)
+			path.setApplyMatrix(true);
 		return path;
 	},
 
@@ -4455,7 +4491,7 @@
 					size = this._size,
 					width = size.width,
 					height = size.height;
-				if (untransformed && type === 'rect' && rx === 0 && ry === 0) {
+				if (untransformed && type === 'rectangle' && rx === 0 && ry === 0) {
 					ctx.rect(-width / 2, -height / 2, width, height);
 				} else {
 					var x = width / 2,
@@ -4647,6 +4683,7 @@
 	_boundsGetter: 'getBounds',
 	_boundsSelected: true,
 	_serializeFields: {
+		crossOrigin: null,
 		source: null
 	},
 
@@ -4680,6 +4717,7 @@
 			copyCanvas.getContext('2d').drawImage(canvas, 0, 0);
 			copy.setImage(copyCanvas);
 		}
+		copy._crossOrigin = this._crossOrigin;
 		return this._clone(copy, insert);
 	},
 
@@ -4799,6 +4837,7 @@
 
 	setSource: function(src) {
 		var that = this,
+			crossOrigin = this._crossOrigin,
 			image;
 
 		function loaded() {
@@ -4811,20 +4850,29 @@
 			}
 		}
 
-			image = document.getElementById(src) || new Image();
-
+		image = document.getElementById(src) || new Image();
+		if (crossOrigin)
+			image.crossOrigin = crossOrigin;
 		if (image.naturalWidth && image.naturalHeight) {
 			setTimeout(loaded, 0);
 		} else {
-			DomEvent.add(image, {
-				load: loaded
-			});
+			DomEvent.add(image, { load: loaded });
 			if (!image.src)
 				image.src = src;
 		}
 		this.setImage(image);
 	},
 
+	getCrossOrigin: function() {
+		return this._image && this._image.crossOrigin || this._crossOrigin || '';
+	},
+
+	setCrossOrigin: function(crossOrigin) {
+		this._crossOrigin = crossOrigin;
+		if (this._image)
+			this._image.crossOrigin = crossOrigin;
+	},
+
 	getElement: function() {
 		return this._canvas || this._loaded && this._image;
 	}
@@ -5109,7 +5157,7 @@
 	},
 
 	_serialize: function(options) {
-		return Base.serialize(this.isLinear() ? this._point
+		return Base.serialize(this.isStraight() ? this._point
 				: [this._point, this._handleIn, this._handleOut],
 				options, true);
 	},
@@ -5160,53 +5208,34 @@
 		this._handleOut.set(point.x, point.y);
 	},
 
-	isLinear: function() {
+	hasHandles: function() {
+		return !this.isStraight();
+	},
+
+	isStraight: function() {
 		return this._handleIn.isZero() && this._handleOut.isZero();
 	},
 
-	setLinear: function(linear) {
-		if (linear) {
-			this._handleIn.set(0, 0);
-			this._handleOut.set(0, 0);
-		} else {
-		}
-	},
-
-	isColinear: function(segment) {
-		var next1 = this.getNext(),
-			next2 = segment.getNext();
-		return this._handleOut.isZero() && next1._handleIn.isZero()
-				&& segment._handleOut.isZero() && next2._handleIn.isZero()
-				&& next1._point.subtract(this._point).isColinear(
-					next2._point.subtract(segment._point));
-	},
+	isLinear: function() {
+		return Segment.isLinear(this, this.getNext());
+	},
+
+	isCollinear: function(segment) {
+		return Segment.isCollinear(this, this.getNext(),
+				segment, segment.getNext());
+	},
+
+	isColinear: '#isCollinear',
 
 	isOrthogonal: function() {
-		var prev = this.getPrevious(),
-			next = this.getNext();
-		return prev._handleOut.isZero() && this._handleIn.isZero()
-			&& this._handleOut.isZero() && next._handleIn.isZero()
-			&& this._point.subtract(prev._point).isOrthogonal(
-					next._point.subtract(this._point));
-	},
-
-	isArc: function() {
-		var next = this.getNext(),
-			handle1 = this._handleOut,
-			handle2 = next._handleIn,
-			kappa = 0.5522847498307936;
-		if (handle1.isOrthogonal(handle2)) {
-			var from = this._point,
-				to = next._point,
-				corner = new Line(from, handle1, true).intersect(
-						new Line(to, handle2, true), true);
-			return corner && Numerical.isZero(handle1.getLength() /
-					corner.subtract(from).getLength() - kappa)
-				&& Numerical.isZero(handle2.getLength() /
-					corner.subtract(to).getLength() - kappa);
-		}
-		return false;
-	},
+		return Segment.isOrthogonal(this.getPrevious(), this, this.getNext());
+	},
+
+	isOrthogonalArc: function() {
+		return Segment.isOrthogonalArc(this, this.getNext());
+	},
+
+	isArc: '#isOrthogonalArc',
 
 	_selectionState: 0,
 
@@ -5361,6 +5390,46 @@
 			}
 		}
 		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;
+		},
 	}
 });
 
@@ -5586,9 +5655,22 @@
 		return Curve.getLength(this.getValues(), from, to);
 	},
 
+	hasHandles: function() {
+		return !this._segment1._handleOut.isZero()
+				|| !this._segment2._handleIn.isZero();
+	},
+
 	isLinear: function() {
-		return this._segment1._handleOut.isZero()
-				&& this._segment2._handleIn.isZero();
+		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) {
@@ -5698,54 +5780,6 @@
 		return values;
 	},
 
-	evaluate: function(v, t, type) {
-		var p1x = v[0], p1y = v[1],
-			c1x = v[2], c1y = v[3],
-			c2x = v[4], c2y = v[5],
-			p2x = v[6], p2y = v[7],
-			tolerance = 0.000001,
-			x, y;
-
-		if (type === 0 && (t < tolerance || t > 1 - tolerance)) {
-			var isZero = t < tolerance;
-			x = isZero ? p1x : p2x;
-			y = isZero ? p1y : p2y;
-		} 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 && c1x === p1x && c1y === p1y
-						|| t > 1 - tolerance && c2x === p2x && c2y === p2y) {
-					x = c2x - c1x;
-					y = c2y - c1y;
-				} 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 (type === 3) {
-					var x2 = 6 * ax * t + 2 * bx,
-						y2 = 6 * ay * t + 2 * by;
-					return (x * y2 - y * x2) / Math.pow(x * x + y * y, 3 / 2);
-				}
-			}
-		}
-		return type === 2 ? new Point(y, -x) : new Point(x, y);
-	},
-
 	subdivide: function(v, t) {
 		var p1x = v[0], p1y = v[1],
 			c1x = v[2], c1y = v[3],
@@ -5773,10 +5807,7 @@
 			p2 = v[coord + 6],
 			c = 3 * (c1 - p1),
 			b = 3 * (c2 - c1) - c,
-			a = p2 - p1 - c - b,
-			isZero = Numerical.isZero;
-		if (isZero(a) && isZero(b))
-			a = b = 0;
+			a = p2 - p1 - c - b;
 		return Numerical.solveCubic(a, b, c, p1 - val, roots, min, max);
 	},
 
@@ -5791,17 +5822,20 @@
 			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;
+		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)
+				if (sx === -1)
 					break;
 			}
 		}
@@ -5816,10 +5850,18 @@
 		return v;
 	},
 
-	isLinear: function(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]);
+		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) {
@@ -5891,7 +5933,8 @@
 					padding);
 		}
 	}
-}}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
+}}, Base.each(
+	['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
 	function(name) {
 		this[name] = function() {
 			if (!this._bounds)
@@ -5906,18 +5949,7 @@
 	},
 {
 
-}), Base.each(['getPoint', 'getTangent', 'getNormal', 'getCurvature'],
-	function(name, index) {
-		this[name + 'At'] = function(offset, isParameter) {
-			var values = this.getValues();
-			return Curve.evaluate(values, isParameter
-					? offset : Curve.getParameterAt(values, offset, 0), index);
-		};
-		this[name] = function(parameter) {
-			return Curve.evaluate(this.getValues(), parameter, index);
-		};
-	},
-{
+}), {
 	beans: false,
 
 	getParameterAt: function(offset, start) {
@@ -5930,9 +5962,10 @@
 	},
 
 	getLocationAt: function(offset, isParameter) {
-		if (!isParameter)
-			offset = this.getParameterAt(offset);
-		return offset >= 0 && offset <= 1 && new CurveLocation(this, offset);
+		var t = isParameter ? offset : this.getParameterAt(offset);
+		return t != null && t >= 0 && t <= 1
+				? new CurveLocation(this, t)
+				: null;
 	},
 
 	getLocationOf: function() {
@@ -5954,8 +5987,7 @@
 
 		function refine(t) {
 			if (t >= 0 && t <= 1) {
-				var dist = point.getDistance(
-						Curve.evaluate(values, t, 0), true);
+				var dist = point.getDistance(Curve.getPoint(values, t), true);
 				if (dist < minDist) {
 					minDist = dist;
 					minT = t;
@@ -5972,7 +6004,7 @@
 			if (!refine(minT - step) && !refine(minT + step))
 				step /= 2;
 		}
-		var pt = Curve.evaluate(values, minT, 0);
+		var pt = Curve.getPoint(values, minT);
 		return new CurveLocation(this, minT, pt, null, null, null,
 				point.getDistance(pt));
 	},
@@ -5981,7 +6013,23 @@
 		return this.getNearestLocation.apply(this, arguments).getPoint();
 	}
 
-}),
+},
+new function() {
+	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
+		}
+	})
+},
 new function() {
 
 	function getLengthIntegrand(v) {
@@ -6009,6 +6057,64 @@
 		return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));
 	}
 
+	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,
+			x, y;
+
+		if (type === 0 && (t < tolerance || t > 1 - tolerance)) {
+			var isZero = t < tolerance;
+			x = isZero ? p1x : p2x;
+			y = isZero ? p1y : p2y;
+		} 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);
+					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;
+				}
+			}
+		}
+		return type === 2 ? new Point(y, -x) : new Point(x, y);
+	}
+
 	return {
 		statics: true,
 
@@ -6034,14 +6140,19 @@
 				start = offset < 0 ? 1 : 0
 			if (offset === 0)
 				return start;
-			var forward = offset > 0,
+			var tolerance = 0.000001,
+				abs = Math.abs,
+				forward = offset > 0,
 				a = forward ? start : 0,
 				b = forward ? 1 : start,
 				ds = getLengthIntegrand(v),
 				rangeLength = Numerical.integrate(ds, a, b,
 						getIterations(a, b));
-			if (Math.abs(offset) >= rangeLength)
+			if (abs(offset - rangeLength) < tolerance) {
 				return forward ? b : a;
+			} else if (abs(offset) > rangeLength) {
+				return null;
+			}
 			var guess = offset / rangeLength,
 				length = 0;
 			function f(t) {
@@ -6051,7 +6162,31 @@
 				return length - offset;
 			}
 			return Numerical.findRoot(f, ds, start + guess, a, b, 16,
-					0.000001);
+					tolerance);
+		},
+
+		getPoint: function(v, t) {
+			return evaluate(v, t, 0, false);
+		},
+
+		getTangent: function(v, t) {
+			return evaluate(v, t, 1, true);
+		},
+
+		getWeightedTangent: function(v, t) {
+			return evaluate(v, t, 1, false);
+		},
+
+		getNormal: function(v, t) {
+			return evaluate(v, t, 2, true);
+		},
+
+		getWeightedNormal: function(v, t) {
+			return evaluate(v, t, 2, false);
+		},
+
+		getCurvature: function(v, t) {
+			return evaluate(v, t, 3, false).x;
 		}
 	};
 }, new function() {
@@ -6079,7 +6214,7 @@
 			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) {
+		if (q0x === q3x && uMax - uMin < tolerance && recursion > 3) {
 			tMaxNew = tMinNew = (tMax + tMin) / 2;
 			tDiff = 0;
 		} else {
@@ -6123,12 +6258,12 @@
 				t2 = uMin + (uMax - uMin) / 2;
 			if (reverse) {
 				addLocation(locations, include,
-						curve2, t2, Curve.evaluate(v2, t2, 0),
-						curve1, t1, Curve.evaluate(v1, t1, 0));
+						curve2, t2, Curve.getPoint(v2, t2),
+						curve1, t1, Curve.getPoint(v1, t1));
 			} else {
 				addLocation(locations, include,
-						curve1, t1, Curve.evaluate(v1, t1, 0),
-						curve2, t2, Curve.evaluate(v2, t2, 0));
+						curve1, t1, Curve.getPoint(v1, t1),
+						curve2, t2, Curve.getPoint(v2, t2));
 			}
 		} else if (tDiff > 0) {
 			addCurveIntersections(v2, v1, curve2, curve1, locations, include,
@@ -6219,14 +6354,14 @@
 			count = Curve.solveCubic(rvc, 1, 0, roots, 0, 1);
 		for (var i = 0; i < count; i++) {
 			var tc = roots[i],
-				x = Curve.evaluate(rvc, tc, 0).x;
+				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;
 				addLocation(locations, include,
-						curve1, t1, Curve.evaluate(v1, t1, 0),
-						curve2, t2, Curve.evaluate(v2, t2, 0));
+						curve1, t1, Curve.getPoint(v1, t1),
+						curve2, t2, Curve.getPoint(v2, t2));
 			}
 		}
 	}
@@ -6322,16 +6457,18 @@
 
 	initialize: function CurveLocation(curve, parameter, point, _curve2,
 			_parameter2, _point2, _distance) {
-		this._id = CurveLocation._id = (CurveLocation._id || 0) + 1;
+		this._id = UID.get(CurveLocation);
+		var path = curve._path;
+		this._version = path ? path._version : 0;
 		this._curve = curve;
-		this._segment1 = curve._segment1;
-		this._segment2 = curve._segment2;
 		this._parameter = parameter;
-		this._point = point;
+		this._point = point || curve.getPointAt(parameter, true);
 		this._curve2 = _curve2;
 		this._parameter2 = _parameter2;
 		this._point2 = _point2;
 		this._distance = _distance;
+		this._segment1 = curve._segment1;
+		this._segment2 = curve._segment2;
 	},
 
 	getSegment: function(_preferFirst) {
@@ -6354,24 +6491,22 @@
 		return this._segment;
 	},
 
-	getCurve: function(_uncached) {
-		if (!this._curve || _uncached) {
-			this._curve = this._segment1.getCurve();
-			if (this._curve.getParameterOf(this._point) == null)
-				this._curve = this._segment2.getPrevious().getCurve();
-		}
-		return this._curve;
-	},
-
-	getIntersection: function() {
-		var intersection = this._intersection;
-		if (!intersection && this._curve2) {
-			var param = this._parameter2;
-			this._intersection = intersection = new CurveLocation(
-					this._curve2, param, this._point2 || this._point, this);
-			intersection._intersection = this;
-		}
-		return intersection;
+	getCurve: function() {
+		var curve = this._curve,
+			path = curve && curve._path;
+		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;
 	},
 
 	getPath: function() {
@@ -6384,6 +6519,18 @@
 		return curve && curve.getIndex();
 	},
 
+	getParameter: function() {
+		var curve = this.getCurve(),
+			parameter = this._parameter;
+		return curve && parameter == null
+			? this._parameter = curve.getParameterOf(this._point)
+			: parameter;
+	},
+
+	getPoint: function() {
+		return this._point;
+	},
+
 	getOffset: function() {
 		var path = this.getPath();
 		return path ? path._getOffset(this) : this.getCurveOffset();
@@ -6395,20 +6542,14 @@
 		return parameter != null && curve && curve.getPartLength(0, parameter);
 	},
 
-	getParameter: function(_uncached) {
-		if ((this._parameter == null || _uncached) && this._point) {
-			var curve = this.getCurve(_uncached);
-			this._parameter = curve && curve.getParameterOf(this._point);
-		}
-		return this._parameter;
-	},
-
-	getPoint: function(_uncached) {
-		if ((!this._point || _uncached) && this._parameter != null) {
-			var curve = this.getCurve(_uncached);
-			this._point = curve && curve.getPointAt(this._parameter, true);
-		}
-		return this._point;
+	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;
 	},
 
 	getDistance: function() {
@@ -6416,24 +6557,24 @@
 	},
 
 	divide: function() {
-		var curve = this.getCurve(true);
-		return curve && curve.divide(this.getParameter(true), true);
+		var curve = this.getCurve();
+		return curve && curve.divide(this.getParameter(), true);
 	},
 
 	split: function() {
-		var curve = this.getCurve(true);
-		return curve && curve.split(this.getParameter(true), true);
+		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
-					&& this._curve === loc._curve
+				|| loc instanceof CurveLocation
+					&& this.getCurve() === loc.getCurve()
+					&& abs(this.getParameter() - loc.getParameter()) < tolerance
 					&& this._curve2 === loc._curve2
-					&& abs(this._parameter - loc._parameter) <= tolerance
-					&& abs(this._parameter2 - loc._parameter2) <= tolerance
+					&& abs(this._parameter2 - loc._parameter2) < tolerance
 				|| false;
 	},
 
@@ -6453,13 +6594,15 @@
 			parts.push('distance: ' + f.number(this._distance));
 		return '{ ' + parts.join(', ') + ' }';
 	}
-}, Base.each(['getTangent', 'getNormal', 'getCurvature'], function(name) {
-	var get = name + 'At';
-	this[name] = function() {
-		var parameter = this.getParameter(),
-			curve = this.getCurve();
-		return parameter != null && curve && curve[get](parameter, true);
-	};
+}, 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);
+		};
+	}
 }, {}));
 
 var PathItem = Item.extend({
@@ -6567,8 +6710,6 @@
 			case 'm':
 			case 'l':
 				var move = lower === 'm';
-				if (move && previous && previous !== 'z')
-					this.closePath(true);
 				for (var j = 0; j < length; j += 2)
 					this[j === 0 && move ? 'moveTo' : 'lineTo'](
 							current = getPoint(j));
@@ -6657,6 +6798,7 @@
 	initialize: function Path(arg) {
 		this._closed = false;
 		this._segments = [];
+		this._version = 0;
 		var segments = Array.isArray(arg)
 			? typeof arg[0] === 'object'
 				? arg
@@ -6699,8 +6841,10 @@
 			if (parent)
 				parent._currentPath = undefined;
 			this._length = this._clockwise = undefined;
-			if (this._curves && !(flags & 16)) {
-				for (var i = 0, l = this._curves.length; i < l; i++)
+			if (flags & 16) {
+				this._version++;
+			} else if (this._curves) {
+			   for (var i = 0, l = this._curves.length; i < l; i++)
 					this._curves[i]._changed();
 			}
 			this._monoCurves = undefined;
@@ -6833,14 +6977,24 @@
 		return this._segments.length === 0;
 	},
 
-	isPolygon: function() {
-		for (var i = 0, l = this._segments.length; i < l; i++) {
-			if (!this._segments[i].isLinear())
+	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;
+	},
+
 	_transformContent: function(matrix) {
 		var coords = new Array(6);
 		for (var i = 0, l = this._segments.length; i < l; i++)
@@ -7039,7 +7193,7 @@
 			end = iterator.length + (this._closed ? -step : step) / 2;
 		var segments = [];
 		while (pos <= end) {
-			segments.push(new Segment(iterator.evaluate(pos, 0)));
+			segments.push(new Segment(iterator.getPointAt(pos)));
 			pos += step;
 		}
 		this.setSegments(segments);
@@ -7175,8 +7329,8 @@
 			radius,
 			topCenter;
 
-		function isColinear(i, j) {
-			return segments[i].isColinear(segments[j]);
+		function isCollinear(i, j) {
+			return segments[i].isCollinear(segments[j]);
 		}
 
 		function isOrthogonal(i) {
@@ -7184,20 +7338,20 @@
 		}
 
 		function isArc(i) {
-			return segments[i].isArc();
+			return segments[i].isOrthogonalArc();
 		}
 
 		function getDistance(i, j) {
 			return segments[i]._point.getDistance(segments[j]._point);
 		}
 
-		if (this.isPolygon() && segments.length === 4
-				&& isColinear(0, 2) && isColinear(1, 3) && isOrthogonal(1)) {
+		if (!this.hasHandles() && segments.length === 4
+				&& isCollinear(0, 2) && isCollinear(1, 3) && isOrthogonal(1)) {
 			type = Shape.Rectangle;
 			size = new Size(getDistance(0, 3), getDistance(0, 1));
 			topCenter = segments[1]._point.add(segments[2]._point).divide(2);
 		} else if (segments.length === 8 && isArc(0) && isArc(2) && isArc(4)
-				&& isArc(6) && isColinear(1, 5) && isColinear(3, 7)) {
+				&& isArc(6) && isCollinear(1, 5) && isCollinear(3, 7)) {
 			type = Shape.Rectangle;
 			size = new Size(getDistance(1, 6), getDistance(0, 3));
 			radius = size.subtract(new Size(getDistance(0, 7),
@@ -7217,16 +7371,13 @@
 
 		if (type) {
 			var center = this.getPosition(true),
-				shape = new type({
+				shape = this._clone(new type({
 					center: center,
 					size: size,
 					radius: radius,
 					insert: false
-				});
+				}), insert, false);
 			shape.rotate(topCenter.subtract(center).getAngle() + 90);
-			shape.setStyle(this._style);
-			if (insert || insert === undefined)
-				shape.insertAbove(this);
 			return shape;
 		}
 		return null;
@@ -7354,7 +7505,7 @@
 						: null;
 	}
 
-}, Base.each(['getPoint', 'getTangent', 'getNormal', 'getCurvature'],
+}, Base.each(Curve.evaluateMethods,
 	function(name) {
 		this[name + 'At'] = function(offset, isParameter) {
 			var loc = this.getLocationAt(offset, isParameter);
@@ -7400,8 +7551,9 @@
 		var curves = this.getCurves(),
 			length = 0;
 		if (isParameter) {
-			var index = ~~offset;
-			return curves[index].getLocationAt(offset - index, true);
+			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,
@@ -7989,7 +8141,7 @@
 			var handleIn = segment._handleIn,
 				handleOut = segment._handleOut;
 			if (join === 'round' || !handleIn.isZero() && !handleOut.isZero()
-					&& handleIn.isColinear(handleOut)) {
+					&& handleIn.isCollinear(handleOut)) {
 				addRound(segment);
 			} else {
 				Path._addBevelJoin(segment, join, radius, miterLimit, add);
@@ -8068,7 +8220,7 @@
 	_addSquareCap: function(segment, cap, radius, addPoint, area) {
 		var point = segment._point,
 			loc = segment.getLocation(),
-			normal = loc.getNormal().normalize(radius);
+			normal = loc.getNormal().multiply(radius);
 		if (area) {
 			addPoint(point.subtract(normal));
 			addPoint(point.add(normal));
@@ -8522,17 +8674,17 @@
 			} while (segment && !segment._intersection && segment !== startSeg);
 			for (var j = 0; j < 3; j++) {
 				var length = totalLength * (j + 1) / 4;
-				for (k = 0, m = chain.length; k < m; k++) {
+				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)
+						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,
+									.getTangentAt(0.5, true).y) < tolerance,
 							path = curve._path;
 						if (path._parent instanceof CompoundPath)
 							path = path._parent;
@@ -8625,7 +8777,7 @@
 				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.evaluate(values, roots[j], 0).y;
+						var y = Curve.getPoint(values, roots[j]).y;
 						if (y < yBefore && y > yTop) {
 							yTop = y;
 						} else if (y > yAfter && y < yBottom) {
@@ -8643,43 +8795,47 @@
 		} 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,
-					prevT,
-					prevX;
+					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],
-						x = Curve.evaluate(values, t, 0).x,
-						slope = Curve.evaluate(values, t, 1).y;
-					if (!(t > tMax
-							&& (i === l - 1 || curve.next !== curves[i + 1])
-							&& abs(Curve.evaluate(curve.next.values, 0, 0).x -x)
-								<= tolerance
-						|| i > 0 && curve.previous === curves[i - 1]
-							&& abs(prevX - x) < tolerance
-							&& prevT > tMax && t < tMin)) {
+					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.evaluate(
-									curve.previous.values, 1, 1).y < 0
-								|| t > tMax && slope * Curve.evaluate(
-									curve.next.values, 0, 1).y < 0) {
+								|| 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;
 						}
+						if (curve.previous !== curves[i - 1])
+							startCounted = t < tMin && counted;
 					}
+					prevCurve = curve;
 					prevT = t;
-					prevX = x;
 				}
 			}
 		}
@@ -8759,7 +8915,7 @@
 				path.lastSegment._handleOut.set(0, 0);
 			}
 			if (path._segments.length >
-					(path._closed ? path.isPolygon() ? 2 : 0 : 1))
+					(path._closed ? path.isLinear() ? 2 : 0 : 1))
 				paths.push(path);
 		}
 		return paths;
@@ -8889,7 +9045,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.evaluate(values, roots[j], 0).x);
+						xIntercepts.push(Curve.getPoint(values, roots[j]).x);
 				}
 				if (xIntercepts.length > 1)
 					break;
@@ -9017,11 +9173,6 @@
 		};
 	},
 
-	evaluate: function(offset, type) {
-		var param = this.getParameterAt(offset);
-		return Curve.evaluate(this.curves[param.index], param.value, type);
-	},
-
 	drawPart: function(ctx, from, to) {
 		from = this.getParameterAt(from);
 		to = this.getParameterAt(to);
@@ -9034,10 +9185,11 @@
 			ctx.bezierCurveTo.apply(ctx, curve.slice(2));
 		}
 	}
-}, Base.each(['getPoint', 'getTangent', 'getNormal', 'getCurvature'],
-	function(name, index) {
-		this[name + 'At'] = function(offset) {
-			return this.evaluate(offset, index);
+}, 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);
 		};
 	}, {})
 );
@@ -9093,18 +9245,19 @@
 		}
 		var uPrime = this.chordLengthParameterize(first, last),
 			maxError = Math.max(this.error, this.error * this.error),
-			split;
+			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) {
+			if (max.error < this.error && parametersInOrder) {
 				this.addCurve(curve);
 				return;
 			}
 			split = max.index;
 			if (max.error >= maxError)
 				break;
-			this.reparameterize(first, last, uPrime, curve);
+			parametersInOrder = this.reparameterize(first, last, uPrime, curve);
 			maxError = max.error;
 		}
 		var V1 = this.points[split - 1].subtract(this.points[split]),
@@ -9168,20 +9321,35 @@
 			}
 		}
 
-		var segLength = pt2.getDistance(pt1);
-		epsilon *= segLength;
-		if (alpha1 < epsilon || alpha2 < epsilon) {
+		var segLength = pt2.getDistance(pt1),
+			eps = epsilon * segLength,
+			handle1,
+			handle2;
+		if (alpha1 < eps || alpha2 < eps) {
 			alpha1 = alpha2 = segLength / 3;
-		}
-
-		return [pt1, pt1.add(tan1.normalize(alpha1)),
-				pt2.add(tan2.normalize(alpha2)), pt2];
+		} else {
+			var line = pt2.subtract(pt1);
+			handle1 = tan1.normalize(alpha1);
+			handle2 = tan2.normalize(alpha2);
+			if (handle1.dot(line) - handle2.dot(line) > segLength * segLength) {
+				alpha1 = alpha2 = segLength / 3;
+				handle1 = handle2 = null;
+			}
+		}
+
+		return [pt1, pt1.add(handle1 || tan1.normalize(alpha1)),
+				pt2.add(handle2 || tan2.normalize(alpha2)), pt2];
 	},
 
 	reparameterize: function(first, last, u, curve) {
 		for (var i = first; i <= last; i++) {
 			u[i - first] = this.findRoot(curve, this.points[i], u[i - first]);
 		}
+		for (var i = 1, l = u.length; i < l; i++) {
+			if (u[i] <= u[i - 1])
+				return false;
+		}
+		return true;
 	},
 
 	findRoot: function(curve, point, u) {
@@ -9266,9 +9434,9 @@
 		return this._content === item._content;
 	},
 
-	_clone: function _clone(copy, insert) {
+	_clone: function _clone(copy, insert, includeMatrix) {
 		copy.setContent(this._content);
-		return _clone.base.call(this, copy, insert);
+		return _clone.base.call(this, copy, insert, includeMatrix);
 	},
 
 	getContent: function() {
@@ -9547,11 +9715,8 @@
 					this._properties = types[type];
 					this._type = type;
 				}
-				value = parser.call(this, value);
-				if (value != null) {
-					this._components[index] = value;
-					this._changed();
-				}
+				this._components[index] = parser.call(this, value);
+				this._changed();
 			};
 		}, this);
 	}, {
@@ -9659,8 +9824,7 @@
 					read = 1;
 			}
 			this._type = type || 'rgb';
-			if (type === 'gradient')
-				this._id = Color._id = (Color._id || 0) + 1;
+			this._id = UID.get(Color);
 			if (!components) {
 				this._components = components = [];
 				var parsers = componentParsers[this._type];
@@ -9897,7 +10061,7 @@
 	_class: 'Gradient',
 
 	initialize: function Gradient(stops, radial) {
-		this._id = Gradient._id = (Gradient._id || 0) + 1;
+		this._id = UID.get();
 		if (stops && this._set(stops))
 			stops = radial = null;
 		if (!this._stops)
@@ -9938,7 +10102,7 @@
 		var stops = [];
 		for (var i = 0, l = this._stops.length; i < l; i++)
 			stops[i] = this._stops[i].clone();
-		return new Gradient(stops);
+		return new Gradient(stops, this._radial);
 	},
 
 	getStops: function() {
@@ -10900,12 +11064,23 @@
 	},
 
 	getPixelSize: function(size) {
-		var ctx = this._context,
-			prevFont = ctx.font;
-		ctx.font = size + ' serif';
-		size = parseFloat(ctx.font);
-		ctx.font = prevFont;
-		return size;
+		var browser = paper.browser,
+			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);
+		} else {
+			var ctx = this._context,
+				prevFont = ctx.font;
+			ctx.font = size + ' serif';
+			pixels = parseFloat(ctx.font);
+			ctx.font = prevFont;
+		}
+		return pixels;
 	},
 
 	getTextWidth: function(font, lines) {
@@ -10919,9 +11094,9 @@
 		return width;
 	},
 
-	update: function() {
+	update: function(force) {
 		var project = this._project;
-		if (!project || !project._needsUpdate)
+		if (!project || !force && !project._needsUpdate)
 			return false;
 		var ctx = this._context,
 			size = this._viewSize;
@@ -11125,6 +11300,7 @@
 
 	charCodeMap = {},
 	keyMap = {},
+	commandFixMap,
 	downCode;
 
 	function handleKey(down, keyCode, charCode, event) {
@@ -11137,13 +11313,28 @@
 			tool = scope && scope.tool,
 			name;
 		keyMap[key] = down;
-		if (specialKey && (name = Base.camelize(specialKey)) in modifiers)
-			modifiers[name] = down;
 		if (down) {
 			charCodeMap[keyCode] = charCode;
 		} else {
 			delete charCodeMap[keyCode];
 		}
+		if (specialKey && (name = Base.camelize(specialKey)) in modifiers) {
+			modifiers[name] = down;
+			var browser = paper.browser;
+			if (name === 'command' && browser && browser.mac) {
+				if (down) {
+					commandFixMap = {};
+				} 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));
@@ -11336,9 +11527,9 @@
 
 	setMinDistance: function(minDistance) {
 		this._minDistance = minDistance;
-		if (this._minDistance != null && this._maxDistance != null
-				&& this._minDistance > this._maxDistance) {
-			this._maxDistance = this._minDistance;
+		if (minDistance != null && this._maxDistance != null
+				&& minDistance > this._maxDistance) {
+			this._maxDistance = minDistance;
 		}
 	},
 
@@ -11348,8 +11539,8 @@
 
 	setMaxDistance: function(maxDistance) {
 		this._maxDistance = maxDistance;
-		if (this._minDistance != null && this._maxDistance != null
-				&& this._maxDistance < this._minDistance) {
+		if (this._minDistance != null && maxDistance != null
+				&& maxDistance < this._minDistance) {
 			this._minDistance = maxDistance;
 		}
 	},
@@ -11373,10 +11564,9 @@
 					distance = vector.getLength();
 				if (distance < minDist)
 					return false;
-				var maxDist = maxDistance != null ? maxDistance : 0;
-				if (maxDist != 0) {
-					if (distance > maxDist) {
-						point = this._point.add(vector.normalize(maxDist));
+				if (maxDistance != null && maxDistance != 0) {
+					if (distance > maxDistance) {
+						point = this._point.add(vector.normalize(maxDistance));
 					} else if (matchMaxDistance) {
 						return false;
 					}
@@ -11470,10 +11660,11 @@
 });
 
 var Http = {
-	request: function(method, url, callback) {
+	request: function(method, url, callback, async) {
+		async = (async === undefined) ? true : async;
 		var xhr = new (window.ActiveXObject || XMLHttpRequest)(
 					'Microsoft.XMLHTTP');
-		xhr.open(method.toUpperCase(), url, true);
+		xhr.open(method.toUpperCase(), url, async);
 		if ('overrideMimeType' in xhr)
 			xhr.overrideMimeType('text/plain');
 		xhr.onreadystatechange = function() {
@@ -11904,19 +12095,22 @@
 		return node;
 	}
 
-	function exportRaster(item) {
+	function exportRaster(item, options) {
 		var attrs = getTransform(item._matrix, true),
-			size = item.getSize();
+			size = item.getSize(),
+			image = item.getImage();
 		attrs.x -= size.width / 2;
 		attrs.y -= size.height / 2;
 		attrs.width = size.width;
 		attrs.height = size.height;
-		attrs.href = item.toDataURL();
+		attrs.href = options.embedImages === false && image && image.src
+				|| item.toDataURL();
 		return createElement('image', attrs);
 	}
 
 	function exportPath(item, options) {
-		if (options.matchShapes) {
+		var matchShapes = options.matchShapes;
+		if (matchShapes) {
 			var shape = item.toShape(false);
 			if (shape)
 				return exportShape(shape, options);
@@ -11926,11 +12120,11 @@
 			attrs = getTransform(item._matrix);
 		if (segments.length === 0)
 			return null;
-		if (item.isPolygon()) {
+		if (matchShapes && !item.hasHandles()) {
 			if (segments.length >= 3) {
 				type = item._closed ? 'polygon' : 'polyline';
 				var parts = [];
-				for(i = 0, l = segments.length; i < l; i++)
+				for(var i = 0, l = segments.length; i < l; i++)
 					parts.push(formatter.point(segments[i]._point));
 				attrs.points = parts.join(' ');
 			} else {
@@ -12270,10 +12464,17 @@
 			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);
+			}
+		}
 		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))
 				children.push(child);
@@ -12473,7 +12674,7 @@
 			color.setAlpha(parseFloat(value));
 	}
 
-	var attributes = 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) {
@@ -12483,7 +12684,7 @@
 							item.getPosition(true).negate()));
 			}
 		};
-	}, {
+	}, {}), {
 		id: function(item, value) {
 			definitions[value] = item;
 			if (item.setName)
@@ -12657,7 +12858,7 @@
 		}
 		if (isRoot) {
 			definitions = {};
-			if (applyMatrix && item)
+			if (item && Base.pick(options.applyMatrix, applyMatrix))
 				item.matrix.apply(true, true);
 		}
 		return item;
@@ -12827,13 +13028,17 @@
 						|| parentType === 'MemberExpression' && parent.computed
 				)) {
 					if (node.type === 'UpdateExpression') {
-						var arg = getCode(node.argument);
-						var str = arg + ' = __$__(' + arg
-								+ ', "' + node.operator[0] + '", 1)';
+						var arg = getCode(node.argument),
+							exp = '__$__(' + arg + ', "' + node.operator[0]
+									+ '", 1)',
+							str = arg + ' = ' + exp;
 						if (!node.prefix
 								&& (parentType === 'AssignmentExpression'
-									|| parentType === 'VariableDeclarator'))
+									|| parentType === 'VariableDeclarator')) {
+							if (getCode(parent.left || parent.id) === arg)
+								str = exp;
 							str = arg + '; ' + str;
+						}
 						replaceCode(node, str);
 					} else {
 						if (/^.=$/.test(node.operator)
@@ -12958,7 +13163,8 @@
 				&& PaperScope.getAttribute(script, 'ignore') !== 'true') {
 			var canvasId = PaperScope.getAttribute(script, 'canvas'),
 				canvas = document.getElementById(canvasId),
-				src = script.src,
+				src = script.src || script.getAttribute('data-src'),
+				async = PaperScope.hasAttribute(script, 'async'),
 				scopeAttribute = 'data-paper-scope';
 			if (!canvas)
 				throw new Error('Unable to find canvas with id "'
@@ -12969,7 +13175,7 @@
 			if (src) {
 				Http.request('get', src, function(code) {
 					execute(code, scope, src);
-				});
+				}, async);
 			} else {
 				execute(script.innerHTML, scope, script.baseURI);
 			}