server/php/basic/public_html/static/lib/paper/paper-full.js
changeset 598 eb4f4eceada0
parent 495 444b80998255
--- a/server/php/basic/public_html/static/lib/paper/paper-full.js	Fri Mar 18 14:15:23 2016 +0100
+++ b/server/php/basic/public_html/static/lib/paper/paper-full.js	Thu Mar 31 17:12:38 2016 +0200
@@ -1,5 +1,5 @@
 /*!
- * Paper.js v0.9.24 - The Swiss Army Knife of Vector Graphics Scripting.
+ * Paper.js v0.9.25 - 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: Fri Aug 21 16:39:41 2015 +0200
+ * Date: Sun Oct 25 11:23:38 2015 +0100
  *
  ***
  *
@@ -581,7 +581,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);
 			}
 		}
@@ -746,7 +746,7 @@
 		}
 	},
 
-	version: '0.9.24',
+	version: "0.9.25",
 
 	getView: function() {
 		return this.project && this.project.getView();
@@ -929,18 +929,26 @@
 	var abs = Math.abs,
 		sqrt = Math.sqrt,
 		pow = Math.pow,
-		TOLERANCE = 1e-6,
 		EPSILON = 1e-12,
 		MACHINE_EPSILON = 1.12e-16;
 
+	function clip(value, min, max) {
+		return value < min ? min : value > max ? max : value;
+	}
+
 	return {
-		TOLERANCE: TOLERANCE,
+		TOLERANCE: 1e-6,
 		EPSILON: EPSILON,
 		MACHINE_EPSILON: MACHINE_EPSILON,
+		CURVETIME_EPSILON: 4e-7,
+		GEOMETRIC_EPSILON: 2e-7,
+		WINDING_EPSILON: 2e-7,
+		TRIGONOMETRIC_EPSILON: 1e-7,
+		CLIPPING_EPSILON: 1e-7,
 		KAPPA: 4 * (sqrt(2) - 1) / 3,
 
 		isZero: function(val) {
-			return abs(val) <= EPSILON;
+			return val >= -EPSILON && val <= EPSILON;
 		},
 
 		integrate: function(f, a, b, n) {
@@ -978,16 +986,18 @@
 
 		solveQuadratic: function(a, b, c, roots, min, max) {
 			var count = 0,
+				eMin = min - EPSILON,
+				eMax = max + EPSILON,
 				x1, x2 = Infinity,
 				B = b,
 				D;
-			b /= 2;
+			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)));
+					var mult = pow(10,
+							abs(Math.floor(Math.log(gmC) * Math.LOG10E)));
 					if (!isFinite(mult))
 						mult = 0;
 					a *= mult;
@@ -1000,25 +1010,22 @@
 				if (abs(B) < EPSILON)
 					return abs(c) < EPSILON ? -1 : 0;
 				x1 = -c / B;
-			} else {
-				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;
-						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;
+			} else if (D >= -MACHINE_EPSILON) {
+				var Q = D < 0 ? 0 : sqrt(D),
+					R = b + (b < 0 ? -Q : Q);
+				if (R === 0) {
+					x1 = c / a;
+					x2 = -x1;
+				} else {
+					x1 = R / a;
+					x2 = c / R;
+				}
+			}
+			if (isFinite(x1) && (min == null || x1 > eMin && x1 < eMax))
+				roots[count++] = min == null ? x1 : clip(x1, min, max);
 			if (x2 !== x1
-					&& isFinite(x2) && (min == null || x2 >= min && x2 <= max))
-				roots[count++] = x2;
+					&& isFinite(x2) && (min == null || x2 > eMin && x2 < eMax))
+				roots[count++] = min == null ? x2 : clip(x2, min, max);
 			return count;
 		},
 
@@ -1071,8 +1078,8 @@
 			}
 			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;
+					&& (min == null || x > min - EPSILON && x < max + EPSILON))
+				roots[count++] = min == null ? x : clip(x, min, max);
 			return count;
 		}
 	};
@@ -1259,11 +1266,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,18 +1312,22 @@
 		return Rectangle.read(arguments).contains(this);
 	},
 
-	isClose: function(point, tolerance) {
+	isClose: function() {
+		var point = Point.read(arguments),
+			tolerance = Base.read(arguments);
 		return this.getDistance(point) < tolerance;
 	},
 
-	isCollinear: function(point) {
-		return Math.abs(this.cross(point)) < 0.000001;
+	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() {
@@ -1338,16 +1349,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,6 +1378,18 @@
 
 		random: function() {
 			return new Point(Math.random(), Math.random());
+		},
+
+		isCollinear: function(x1, y1, x2, y2) {
+			return Math.abs(x1 * y2 - y1 * x2)
+					<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+						* 1e-7;
+		},
+
+		isOrthogonal: function(x1, y1, x2, y2) {
+			return Math.abs(x1 * x2 + y1 * y2)
+					<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+						* 1e-7;
 		}
 	}
 }, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
@@ -1922,7 +1941,8 @@
 			this._owner[this._setter](this);
 		return this;
 	}
-}, new function() {
+},
+new function() {
 	var proto = Rectangle.prototype;
 
 	return Base.each(['x', 'y', 'width', 'height'], function(key) {
@@ -2255,7 +2275,7 @@
 	_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;
@@ -2417,10 +2437,10 @@
 				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) {
@@ -2429,29 +2449,45 @@
 				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 +2495,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 (ccw === 0 && !isInfinite) {
+				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 +2508,9 @@
 				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);
 		}
 	}
 });
@@ -2783,70 +2812,32 @@
 		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);
-					}
+	_events: Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
+			'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
+		function(name) {
+			this[name] = {
+				install: function(type) {
+					this.getView()._installEvent(type);
+				},
+
+				uninstall: function(type) {
+					this.getView()._uninstallEvent(type);
+				}
+			};
+		}, {
+			onFrame: {
+				install: function() {
+					this.getView()._animateItem(this, true);
+				},
+
+				uninstall: function() {
+					this.getView()._animateItem(this, false);
 				}
 			},
-			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);
-	},
+
+			onLoad: {}
+		}
+	),
 
 	_serialize: function(options, dictionary) {
 		var props = {},
@@ -3452,8 +3443,8 @@
 	intersects: function(item, _matrix) {
 		if (!(item instanceof Item))
 			return false;
-		return this._asPathItem().getIntersections(item._asPathItem(),
-				_matrix || item._matrix).length > 0;
+		return this._asPathItem().getIntersections(item._asPathItem(), null,
+				_matrix || item._matrix, true).length > 0;
 	},
 
 	hitTest: function() {
@@ -3476,7 +3467,7 @@
 			tolerancePadding = options._tolerancePadding = new Size(
 						Path._getPenPadding(1, totalMatrix.inverted())
 					).multiply(
-						Math.max(options.tolerance, 0.000001)
+						Math.max(options.tolerance, 1e-6)
 					);
 		point = matrix._inverseTransform(point);
 
@@ -3599,7 +3590,7 @@
 				var overlapping = match.overlapping,
 					inside = match.inside,
 					bounds = overlapping || inside,
-					rect =	bounds && Rectangle.read([bounds]);
+					rect = bounds && Rectangle.read([bounds]);
 				param = {
 					items: [],
 					inside: !!inside,
@@ -3903,6 +3894,10 @@
 		return item ? item.isDescendant(this) : false;
 	},
 
+	isSibling: function(item) {
+		return this._parent === item._parent;
+	},
+
 	isGroupedWith: function(item) {
 		var parent = this._parent;
 		while (parent) {
@@ -4178,7 +4173,7 @@
 			if (this._drawSelected)
 				this._drawSelected(ctx, mx, selectedItems);
 			if (this._boundsSelected) {
-				var half = size / 2;
+				var half = size / 2,
 					coords = mx._transformCorners(this.getInternalBounds());
 				ctx.beginPath();
 				for (var i = 0; i < 8; i++)
@@ -4557,7 +4552,6 @@
 	}
 },
 new function() {
-
 	function getCornerCenter(that, point, expand) {
 		var radius = that._radius;
 		if (!radius.isZero()) {
@@ -4902,7 +4896,7 @@
 		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 ) {
@@ -5133,7 +5127,7 @@
 			point, handleIn, handleOut;
 		if (count === 0) {
 		} else if (count === 1) {
-			if (arg0.point) {
+			if ('point' in arg0) {
 				point = arg0.point;
 				handleIn = arg0.handleIn;
 				handleOut = arg0.handleOut;
@@ -5157,8 +5151,9 @@
 	},
 
 	_serialize: function(options) {
-		return Base.serialize(this.isStraight() ? this._point
-				: [this._point, this._handleIn, this._handleOut],
+		return Base.serialize(this.hasHandles()
+				? [this._point, this._handleIn, this._handleOut]
+				: this._point,
 				options, true);
 	},
 
@@ -5209,33 +5204,13 @@
 	},
 
 	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',
+		return !this._handleIn.isZero() || !this._handleOut.isZero();
+	},
+
+	clearHandles: function() {
+		this._handleIn.set(0, 0);
+		this._handleOut.set(0, 0);
+	},
 
 	_selectionState: 0,
 
@@ -5309,7 +5284,25 @@
 				|| this._path._closed && segments[segments.length - 1]) || null;
 	},
 
+	isFirst: function() {
+		return this._index === 0;
+	},
+
+	isLast: function() {
+		var path = this._path;
+		return path && this._index === path._segments.length - 1 || false;
+	},
+
 	reverse: function() {
+		var handleIn = this._handleIn,
+			handleOut = this._handleOut,
+			inX = handleIn._x,
+			inY = handleIn._y;
+		handleIn.set(handleOut._x, handleOut._y);
+		handleOut.set(inX, inY);
+	},
+
+	reversed: function() {
 		return new Segment(this._point, this._handleOut, this._handleIn);
 	},
 
@@ -5390,46 +5383,6 @@
 			}
 		}
 		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;
-		},
 	}
 });
 
@@ -5508,42 +5461,88 @@
 	_class: 'Curve',
 
 	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;
+			seg1 = arg1;
+			seg2 = arg2;
 		} else if (count === 0) {
-			this._segment1 = new Segment();
-			this._segment2 = new Segment();
+			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) {
+		return Base.serialize(this.hasHandles()
+				? [this.getPoint1(), this.getHandle1(), this.getHandle2(),
+					this.getPoint2()]
+				: [this.getPoint1(), this.getPoint2()],
+				options, true);
 	},
 
 	_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(', ') + ' }';
+	},
+
+	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;
+	},
+
 	getPoint1: function() {
 		return this._segment1._point;
 	},
@@ -5608,6 +5607,16 @@
 				|| this._path._closed && curves[curves.length - 1]) || null;
 	},
 
+	isFirst: function() {
+		return this._segment1._index === 0;
+	},
+
+	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()
@@ -5635,11 +5644,8 @@
 	},
 
 	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 +5653,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,27 +5665,10 @@
 		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, []));
+		return Curve._getIntersections(this.getValues(),
+				curve && curve !== this ? curve.getValues() : null,
+				this, curve, [], {});
 	},
 
 	_getParameter: function(offset, isParameter) {
@@ -5688,39 +5681,35 @@
 						: this.getParameterAt(offset, 0);
 	},
 
-	divide: function(offset, isParameter, ignoreLinear) {
+	divide: function(offset, isParameter, _setHandles) {
 		var parameter = this._getParameter(offset, isParameter),
-			tolerance = 0.000001,
+			tMin = 4e-7,
+			tMax = 1 - tMin,
 			res = null;
-		if (parameter > tolerance && parameter < 1 - tolerance) {
+		if (parameter >= tMin && parameter <= tMax) {
 			var parts = Curve.subdivide(this.getValues(), parameter),
-				isLinear = ignoreLinear ? false : this.isLinear(),
 				left = parts[0],
-				right = parts[1];
-
-			if (!isLinear) {
-				this._segment1._handleOut.set(left[2] - left[0],
+				right = parts[1],
+				setHandles = _setHandles || this.hasHandles(),
+				segment1 = this._segment1,
+				segment2 = this._segment2,
+				path = this._path;
+			if (setHandles) {
+				segment1._handleOut.set(left[2] - left[0],
 						left[3] - left[1]);
-				this._segment2._handleIn.set(right[4] - right[6],
+				segment2._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;
+						setHandles && new Point(left[4] - x, left[5] - y),
+						setHandles && new Point(right[2] - x, right[3] - y));
+			if (path) {
+				path.insert(segment1._index + 1, segment);
+				res = this.getNext();
 			} else {
-				var end = this._segment2;
 				this._segment2 = segment;
-				res = new Curve(segment, end);
+				res = new Curve(segment, segment2);
 			}
 		}
 		return res;
@@ -5733,34 +5722,13 @@
 			: 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(', ') + ' }';
+	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: {
@@ -5811,43 +5779,86 @@
 		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;
+	getParameterOf: function(v, point) {
+		var p1 = new Point(v[0], v[1]),
+			p2 = new Point(v[6], v[7]),
+			epsilon = 1e-12,
+			t = point.isClose(p1, epsilon) ? 0
+			  : point.isClose(p2, epsilon) ? 1
+			  : null;
+		if (t !== null)
+			return t;
+		var coords = [point.x, point.y],
+			roots = [],
+			geomEpsilon = 2e-7;
+		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++) {
+				t = roots[i];
+				if (point.isClose(Curve.getPoint(v, t), geomEpsilon))
+					return t;
+			}
+		}
+		return point.isClose(p1, geomEpsilon) ? 0
+			 : point.isClose(p2, geomEpsilon) ? 1
+			 : null;
+	},
+
+	getNearestParameter: function(v, point) {
+		if (Curve.isStraight(v)) {
+			var p1x = v[0], p1y = v[1],
+				p2x = v[6], p2y = v[7],
+				vx = p2x - p1x, vy = p2y - p1y,
+				det = vx * vx + vy * vy;
+			if (det === 0)
+				return 0;
+			var u = ((point.x - p1x) * vx + (point.y - p1y) * vy) / det;
+			return u < 1e-12 ? 0
+				 : u > 0.999999999999 ? 1
+				 : Curve.getParameterOf(v,
+					new Point(p1x + u * vx, p1y + 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 > 4e-7) {
+			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;
+		return flip
+				? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]]
+				: v;
 	},
 
 	hasHandles: function(v) {
@@ -5856,14 +5867,6 @@
 				&& 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],
@@ -5879,21 +5882,14 @@
 
 	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]);
+			p2x = v[6], p2y = v[7],
+			h1x = (v[2] + p1x) / 2,
+			h1y = (v[3] + p1y) / 2,
+			h2x = (v[4] + v[6]) / 2,
+			h2y = (v[5] + v[7]) / 2;
+		return 6 * ((p1x - h1x) * (h1y + p1y)
+				  + (h1x - h2x) * (h2y + h1y)
+				  + (h2x - p2x) * (p2y + h2y)) / 10;
 	},
 
 	getBounds: function(v) {
@@ -5919,7 +5915,7 @@
 			b = 2 * (v0 + v2) - 4 * v1,
 			c = v1 - v0,
 			count = Numerical.solveQuadratic(a, b, c, roots),
-			tMin = 0.000001,
+			tMin = 4e-7,
 			tMax = 1 - tMin;
 		add(v3, 0);
 		for (var i = 0; i < count; i++) {
@@ -5941,14 +5937,72 @@
 				this._bounds = {};
 			var bounds = this._bounds[name];
 			if (!bounds) {
-				bounds = this._bounds[name] = Path[name]([this._segment1,
-						this._segment2], false, this._path.getStyle());
+				var path = this._path;
+				bounds = this._bounds[name] = Path[name](
+						[this._segment1, this._segment2], false,
+						path && path.getStyle());
 			}
 			return bounds.clone();
 		};
 	},
 {
 
+}), Base.each({
+	isStraight: function(l, h1, h2) {
+		if (h1.isZero() && h2.isZero()) {
+			return true;
+		} else if (l.isZero()) {
+			return false;
+		} else if (h1.isCollinear(l) && h2.isCollinear(l)) {
+			var div = l.dot(l),
+				p1 = l.dot(h1) / div,
+				p2 = l.dot(h2) / div;
+			return p1 >= 0 && p1 <= 1 && p2 <= 0 && p2 >= -1;
+		}
+		return false;
+	},
+
+	isLinear: function(l, h1, h2) {
+		var third = l.divide(3);
+		return h1.equals(third) && h2.negate().equals(third);
+	}
+}, function(test, name) {
+	this[name] = function() {
+		var seg1 = this._segment1,
+			seg2 = this._segment2;
+		return test(seg2._point.subtract(seg1._point),
+				seg1._handleOut, seg2._handleIn);
+	};
+
+	this.statics[name] = function(v) {
+		var p1x = v[0], p1y = v[1],
+			p2x = v[6], p2y = v[7];
+		return test(new Point(p2x - p1x, p2y - p1y),
+				new Point(v[2] - p1x, v[3] - p1y),
+				new Point(v[4] - p2x, v[5] - p2y));
+	};
+}, {
+	statics: {},
+
+	hasHandles: function() {
+		return !this._segment1._handleOut.isZero()
+				|| !this._segment2._handleIn.isZero();
+	},
+
+	isCollinear: function(curve) {
+		return curve && this.isStraight() && curve.isStraight()
+				&& this.getLine().isCollinear(curve.getLine());
+	},
+
+	isHorizontal: function() {
+		return this.isStraight() && Math.abs(this.getTangentAt(0.5, true).y)
+				< 1e-7;
+	},
+
+	isVertical: function() {
+		return this.isStraight() && Math.abs(this.getTangentAt(0.5, true).x)
+				< 1e-7;
+	}
 }), {
 	beans: false,
 
@@ -5957,8 +6011,7 @@
 	},
 
 	getParameterOf: function() {
-		var point = Point.read(arguments);
-		return Curve.getParameterOf(this.getValues(), point.x, point.y);
+		return Curve.getParameterOf(this.getValues(), Point.read(arguments));
 	},
 
 	getLocationAt: function(offset, isParameter) {
@@ -5981,32 +6034,9 @@
 	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.getNearestParameter(values, point),
+			pt = Curve.getPoint(values, t);
+		return new CurveLocation(this, t, pt, null, point.getDistance(pt));
 	},
 
 	getNearestPoint: function() {
@@ -6064,11 +6094,12 @@
 			c1x = v[2], c1y = v[3],
 			c2x = v[4], c2y = v[5],
 			p2x = v[6], p2y = v[7],
-			tolerance = 0.000001,
+			tMin = 4e-7,
+			tMax = 1 - tMin,
 			x, y;
 
-		if (type === 0 && (t < tolerance || t > 1 - tolerance)) {
-			var isZero = t < tolerance;
+		if (type === 0 && (t < tMin || t > tMax)) {
+			var isZero = t < tMin;
 			x = isZero ? p1x : p2x;
 			y = isZero ? p1y : p2y;
 		} else {
@@ -6083,10 +6114,10 @@
 				x = ((ax * t + bx) * t + cx) * t + p1x;
 				y = ((ay * t + by) * t + cy) * t + p1y;
 			} else {
-				if (t < tolerance) {
+				if (t < tMin) {
 					x = cx;
 					y = cy;
-				} else if (t > 1 - tolerance) {
+				} else if (t > tMax) {
 					x = 3 * (p2x - c2x);
 					y = 3 * (p2y - c2y);
 				} else {
@@ -6094,14 +6125,15 @@
 					y = (3 * ay * t + 2 * by) * t + cy;
 				}
 				if (normalized) {
-					if (x === 0 && y === 0
-							&& (t < tolerance || t > 1 - tolerance)) {
+					if (x === 0 && y === 0 && (t < tMin || t > tMax)) {
 						x = c2x - c1x;
 						y = c2y - c1y;
 					}
 					var len = Math.sqrt(x * x + y * y);
-					x /= len;
-					y /= len;
+					if (len) {
+						x /= len;
+						y /= len;
+					}
 				}
 				if (type === 3) {
 					var x2 = 6 * ax * t + 2 * bx,
@@ -6115,18 +6147,14 @@
 		return type === 2 ? new Point(y, -x) : new Point(x, y);
 	}
 
-	return {
-		statics: true,
+	return { statics: {
 
 		getLength: function(v, a, b) {
 			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])) {
+			if (a === 0 && b === 1 && Curve.isStraight(v)) {
 				var dx = v[6] - v[0],
 					dy = v[7] - v[1];
 				return Math.sqrt(dx * dx + dy * dy);
@@ -6140,15 +6168,14 @@
 				start = offset < 0 ? 1 : 0
 			if (offset === 0)
 				return start;
-			var tolerance = 0.000001,
-				abs = Math.abs,
+			var 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 (abs(offset - rangeLength) < tolerance) {
+			if (abs(offset - rangeLength) < 1e-12) {
 				return forward ? b : a;
 			} else if (abs(offset) > rangeLength) {
 				return null;
@@ -6161,8 +6188,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) {
@@ -6188,24 +6215,55 @@
 		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,
+	}};
+},
+new function() {
+
+	function addLocation(locations, param, v1, c1, t1, p1, v2, c2, t2, p2,
+			overlap) {
+		var startConnected = param.startConnected,
+			endConnected = param.endConnected,
+			tMin = 4e-7,
+			tMax = 1 - tMin;
+		if (t1 == null)
+			t1 = Curve.getParameterOf(v1, p1);
+		if (t1 !== null && t1 >= (startConnected ? tMin : 0) &&
+			t1 <= (endConnected ? tMax : 1)) {
+			if (t2 == null)
+				t2 = Curve.getParameterOf(v2, p2);
+			if (t2 !== null && t2 >= (endConnected ? tMin : 0) &&
+				t2 <= (startConnected ? tMax : 1)) {
+				var renormalize = param.renormalize;
+				if (renormalize) {
+					var res = renormalize(t1, t2);
+					t1 = res[0];
+					t2 = res[1];
+				}
+				var loc1 = new CurveLocation(c1, t1,
+						p1 || Curve.getPoint(v1, t1), overlap),
+					loc2 = new CurveLocation(c2, t2,
+						p2 || Curve.getPoint(v2, t2), overlap),
+					flip = loc1.getPath() === loc2.getPath()
+						&& loc1.getIndex() > loc2.getIndex(),
+					loc = flip ? loc2 : loc1,
+					include = param.include;
+				loc1._intersection = loc2;
+				loc2._intersection = loc1;
+				if (!include || include(loc)) {
+					CurveLocation.insert(locations, loc, true);
+				}
+			}
+		}
+	}
+
+	function addCurveIntersections(v1, v2, c1, c2, locations, param,
 			tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) {
-		if (recursion > 32)
+		if (++recursion >= 24)
 			return;
 		var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7],
-			tolerance = 0.000001,
 			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,61 +6271,51 @@
 			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;
-		} 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);
-		}
+			hull = getConvexHull(dp0, dp1, dp2, dp3),
+			top = hull[0],
+			bottom = hull[1],
+			tMinClip,
+			tMaxClip;
+		if ((tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null ||
+			(tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(),
+				dMin, dMax)) == null)
+			return;
+		v1 = Curve.getPart(v1, tMinClip, tMaxClip);
+		var tDiff = tMaxClip - tMinClip,
+			tMinNew = tMin + (tMax - tMin) * tMinClip,
+			tMaxNew = tMin + (tMax - tMin) * 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);
+					v2, parts[0], c2, c1, locations, param,
+					uMin, uMax, tMinNew, t, tDiff, !reverse, recursion);
 				addCurveIntersections(
-					v2, parts[1], curve2, curve1, locations, include,
+					v2, parts[1], c2, c1, locations, param,
 					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);
+					parts[0], v1, c2, c1, locations, param,
+					uMin, t, tMinNew, tMaxNew, tDiff, !reverse, recursion);
 				addCurveIntersections(
-					parts[1], v1, curve2, curve1, locations, include,
+					parts[1], v1, c2, c1, locations, param,
 					t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
 			}
-		} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) {
+		} else if (Math.max(uMax - uMin, tMaxNew - tMinNew)
+				< 1e-7) {
 			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);
+			v1 = c1.getValues();
+			v2 = c2.getValues();
+			addLocation(locations, param,
+				reverse ? v2 : v1, reverse ? c2 : c1, reverse ? t2 : t1, null,
+				reverse ? v1 : v2, reverse ? c1 : c2, reverse ? t1 : t2, null);
+		} else if (tDiff > 1e-12) {
+			addCurveIntersections(v2, v1, c2, c1, locations, param,
+					uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
 		}
 	}
 
@@ -6276,32 +6324,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 <= .5 ? [p0, p2, p3]
+				: [p0, p1, p2, p3],
+				[p0, p3]
+			];
+		}
+		return (dist1 || dist2) < 0 ? hull.reverse() : hull;
 	}
 
 	function clipConvexHull(hullTop, hullBottom, dMin, dMax) {
@@ -6320,17 +6357,18 @@
 		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),
+	function addCurveLineIntersections(v1, v2, c1, c2, locations, param) {
+		var flip = Curve.isStraight(v1),
 			vc = flip ? v2 : v1,
 			vl = flip ? v1 : v2,
 			lx1 = vl[0], ly1 = vl[1],
@@ -6340,113 +6378,221 @@
 			angle = Math.atan2(-ldy, ldx),
 			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(
 				x * cos - y * sin,
-				y * cos + x * sin);
+				x * sin + y * cos);
 		}
 		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),
+				pc = Curve.getPoint(vc, tc),
+				tl = Curve.getParameterOf(vl, pc);
+			if (tl !== null) {
+				var pl = Curve.getPoint(vl, tl),
 					t1 = flip ? tl : tc,
 					t2 = flip ? tc : tl;
-				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(
+				if (!param.endConnected || t2 > Numerical.CURVETIME_EPSILON) {
+					addLocation(locations, param,
+							v1, c1, t1, flip ? pl : pc,
+							v2, c2, t2, flip ? pc : pl);
+				}
+			}
+		}
+	}
+
+	function addLineIntersection(v1, v2, c1, c2, locations, param) {
+		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;
-			addLocation(locations, include,
-					curve1, Curve.getParameterOf(v1, x, y), point,
-					curve2, Curve.getParameterOf(v2, x, y), point);
+		if (pt) {
+			addLocation(locations, param, v1, c1, null, pt, v2, c2, null, pt);
 		}
 	}
 
 	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
+		_getIntersections: function(v1, v2, c1, c2, locations, param) {
+			if (!v2) {
+				return Curve._getSelfIntersection(v1, c1, locations, param);
+			}
+			var c1p1x = v1[0], c1p1y = v1[1],
+				c1p2x = v1[6], c1p2y = v1[7],
+				c2p1x = v2[0], c2p1y = v2[1],
+				c2p2x = v2[6], c2p2y = v2[7],
+				c1s1x = (3 * v1[2] + c1p1x) / 4,
+				c1s1y = (3 * v1[3] + c1p1y) / 4,
+				c1s2x = (3 * v1[4] + c1p2x) / 4,
+				c1s2y = (3 * v1[5] + c1p2y) / 4,
+				c2s1x = (3 * v2[2] + c2p1x) / 4,
+				c2s1y = (3 * v2[3] + c2p1y) / 4,
+				c2s2x = (3 * v2[4] + c2p2x) / 4,
+				c2s2y = (3 * v2[5] + c2p2y) / 4,
+				min = Math.min,
+				max = Math.max;
+			if (!(	max(c1p1x, c1s1x, c1s2x, c1p2x) >=
+					min(c2p1x, c2s1x, c2s2x, c2p2x) &&
+					min(c1p1x, c1s1x, c1s2x, c1p2x) <=
+					max(c2p1x, c2s1x, c2s2x, c2p2x) &&
+					max(c1p1y, c1s1y, c1s2y, c1p2y) >=
+					min(c2p1y, c2s1y, c2s2y, c2p2y) &&
+					min(c1p1y, c1s1y, c1s2y, c1p2y) <=
+					max(c2p1y, c2s1y, c2s2y, c2p2y)))
+				return locations;
+			if (!param.startConnected && !param.endConnected) {
+				var overlaps = Curve.getOverlaps(v1, v2);
+				if (overlaps) {
+					for (var i = 0; i < 2; i++) {
+						var overlap = overlaps[i];
+						addLocation(locations, param,
+							v1, c1, overlap[0], null,
+							v2, c2, overlap[1], null, true);
+					}
+					return locations;
+				}
+			}
+
+			var straight1 = Curve.isStraight(v1),
+				straight2 = Curve.isStraight(v2),
+				straight = straight1 && straight2,
+				epsilon = 1e-12,
+				before = locations.length;
+			(straight
 				? addLineIntersection
-				: linear1 || linear2
+				: straight1 || straight2
 					? addCurveLineIntersections
 					: addCurveIntersections)(
-						v1, v2, c1, c2, locations, include,
+						v1, v2, c1, c2, locations, param,
 						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);
+			if (straight && locations.length > before)
+				return locations;
+			var c1p1 = new Point(c1p1x, c1p1y),
+				c1p2 = new Point(c1p2x, c1p2y),
+				c2p1 = new Point(c2p1x, c2p1y),
+				c2p2 = new Point(c2p2x, c2p2y);
+			if (c1p1.isClose(c2p1, epsilon))
+				addLocation(locations, param, v1, c1, 0, c1p1, v2, c2, 0, c2p1);
+			if (!param.startConnected && c1p1.isClose(c2p2, epsilon))
+				addLocation(locations, param, v1, c1, 0, c1p1, v2, c2, 1, c2p2);
+			if (!param.endConnected && c1p2.isClose(c2p1, epsilon))
+				addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 0, c2p1);
+			if (c1p2.isClose(c2p2, epsilon))
+				addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 1, c2p2);
 			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--;
+		_getSelfIntersection: function(v1, c1, locations, param) {
+			var p1x = v1[0], p1y = v1[1],
+				h1x = v1[2], h1y = v1[3],
+				h2x = v1[4], h2y = v1[5],
+				p2x = v1[6], p2y = v1[7];
+			var line = new Line(p1x, p1y, p2x, p2y, false),
+				side1 = line.getSide(new Point(h1x, h1y), true),
+				side2 = line.getSide(new Point(h2x, h2y), true);
+			if (side1 === side2) {
+				var edgeSum = (p1x - h2x) * (h1y - p2y)
+							+ (h1x - p2x) * (h2y - p1y);
+				if (edgeSum * side1 > 0)
+					return locations;
+			}
+			var ax = p2x - 3 * h2x + 3 * h1x - p1x,
+				bx = h2x - 2 * h1x + p1x,
+				cx = h1x - p1x,
+				ay = p2y - 3 * h2y + 3 * h1y - p1y,
+				by = h2y - 2 * h1y + p1y,
+				cy = h1y - p1y,
+				ac = ay * cx - ax * cy,
+				ab = ay * bx - ax * by,
+				bc = by * cx - bx * cy;
+			if (ac * ac - 4 * ab * bc < 0) {
+				var roots = [],
+					tSplit,
+					count = Numerical.solveCubic(
+							ax * ax	 + ay * ay,
+							3 * (ax * bx + ay * by),
+							2 * (bx * bx + by * by) + ax * cx + ay * cy,
+							bx * cx + by * cy,
+							roots, 0, 1);
+				if (count > 0) {
+					for (var i = 0, maxCurvature = 0; i < count; i++) {
+						var curvature = Math.abs(
+								c1.getCurvatureAt(roots[i], true));
+						if (curvature > maxCurvature) {
+							maxCurvature = curvature;
+							tSplit = roots[i];
+						}
 					}
-				}
-			}
-			if (_expand) {
-				for (var i = last; i >= 0; i--)
-					locations.push(locations[i].getIntersection());
-				locations.sort(compare);
+					var parts = Curve.subdivide(v1, tSplit);
+					param.endConnected = true;
+					param.renormalize = function(t1, t2) {
+						return [t1 * tSplit, t2 * (1 - tSplit) + tSplit];
+					};
+					Curve._getIntersections(parts[0], parts[1], c1, c1,
+							locations, param);
+				}
 			}
 			return locations;
+		},
+
+		getOverlaps: function(v1, v2) {
+			var abs = Math.abs,
+				timeEpsilon = 4e-7,
+				geomEpsilon = 2e-7,
+				straight1 = Curve.isStraight(v1),
+				straight2 = Curve.isStraight(v2),
+				straight =	straight1 && straight2;
+
+			function getLineLengthSquared(v) {
+				var x = v[6] - v[0],
+					y = v[7] - v[1];
+				return x * x + y * y;
+			}
+
+			if (straight) {
+				var flip = getLineLengthSquared(v1) < getLineLengthSquared(v2),
+					l1 = flip ? v2 : v1,
+					l2 = flip ? v1 : v2,
+					line = new Line(l1[0], l1[1], l1[6], l1[7]);
+				if (line.getDistance(new Point(l2[0], l2[1])) > geomEpsilon ||
+					line.getDistance(new Point(l2[6], l2[7])) > geomEpsilon)
+					return null;
+			} else if (straight1 ^ straight2) {
+				return null;
+			}
+
+			var v = [v1, v2],
+				pairs = [];
+			for (var i = 0, t1 = 0;
+					i < 2 && pairs.length < 2;
+					i += t1 === 0 ? 0 : 1, t1 = t1 ^ 1) {
+				var t2 = Curve.getParameterOf(v[i ^ 1], new Point(
+						v[i][t1 === 0 ? 0 : 6],
+						v[i][t1 === 0 ? 1 : 7]));
+				if (t2 != null) {
+					var pair = i === 0 ? [t1, t2] : [t2, t1];
+					if (pairs.length === 0 ||
+						abs(pair[0] - pairs[0][0]) > timeEpsilon &&
+						abs(pair[1] - pairs[0][1]) > timeEpsilon)
+						pairs.push(pair);
+				}
+				if (i === 1 && pairs.length === 0)
+					break;
+			}
+			if (pairs.length !== 2) {
+				pairs = null;
+			} else if (!straight) {
+				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;
 		}
 	}};
 });
@@ -6455,58 +6601,82 @@
 	_class: 'CurveLocation',
 	beans: true,
 
-	initialize: function CurveLocation(curve, parameter, point, _curve2,
-			_parameter2, _point2, _distance) {
+	initialize: function CurveLocation(curve, parameter, point,
+			_overlap, _distance) {
+		if (parameter > 0.9999996) {
+			var next = curve.getNext();
+			if (next) {
+				parameter = 0;
+				curve = next;
+			}
+		}
 		this._id = UID.get(CurveLocation);
+		this._setCurve(curve);
+		this._parameter = parameter;
+		this._point = point || curve.getPointAt(parameter, true);
+		this._overlap = _overlap;
+		this._distance = _distance;
+		this._intersection = this._next = this._prev = null;
+	},
+
+	_setCurve: function(curve) {
 		var path = curve._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) {
-			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)
+	_setSegment: function(segment) {
+		this._setCurve(segment.getCurve());
+		this._segment = segment;
+		this._parameter = segment === this._segment1 ? 0 : 1;
+		this._point = segment._point.clone();
+	},
+
+	getSegment: function() {
+		var curve = this.getCurve(),
+			segment = this._segment;
+		if (!segment) {
+			var parameter = this.getParameter();
+			if (parameter === 0) {
+				segment = curve._segment1;
+			} else if (parameter === 1) {
+				segment = curve._segment2;
+			} else if (parameter != null) {
+				segment = curve.getPartLength(0, parameter)
 					< curve.getPartLength(parameter, 1)
 						? curve._segment1
 						: curve._segment2;
 			}
-		}
-		return this._segment;
+			this._segment = segment;
+		}
+		return segment;
 	},
 
 	getCurve: function() {
 		var curve = this._curve,
-			path = curve && curve._path;
+			path = curve && curve._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;
+			curve = this._parameter = this._curve = this._offset = null;
+		}
+
+		function trySegment(segment) {
+			var curve = segment && segment.getCurve();
+			if (curve && (that._parameter = curve.getParameterOf(that._point))
+					!= null) {
+				that._setCurve(curve);
+				that._segment = segment;
+				return curve;
+			}
+		}
+
+		return curve
+			|| trySegment(this._segment)
+			|| trySegment(this._segment1)
+			|| trySegment(this._segment2.getPrevious());
 	},
 
 	getPath: function() {
@@ -6532,8 +6702,19 @@
 	},
 
 	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() {
@@ -6543,13 +6724,7 @@
 	},
 
 	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 +6732,44 @@
 	},
 
 	divide: function() {
-		var curve = this.getCurve();
-		return curve && curve.divide(this.getParameter(), true);
+		var curve = this.getCurve(),
+			res = null;
+		if (curve) {
+			res = curve.divide(this.getParameter(), true);
+			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;
+		return curve ? curve.split(this.getParameter(), true) : null;
+	},
+
+	equals: function(loc, _ignoreOther) {
+		var res = this === loc,
+			epsilon = 2e-7;
+		if (!res && loc instanceof CurveLocation
+				&& this.getPath() === loc.getPath()
+				&& this.getPoint().isClose(loc.getPoint(), epsilon)) {
+			var c1 = this.getCurve(),
+				c2 = loc.getCurve(),
+				abs = Math.abs,
+				diff = abs(
+					((c1.isLast() && c2.isFirst() ? -1 : c1.getIndex())
+							+ this.getParameter()) -
+					((c2.isLast() && c1.isFirst() ? -1 : c2.getIndex())
+							+ loc.getParameter()));
+			res = (diff < 4e-7
+				|| ((diff = abs(this.getOffset() - loc.getOffset())) < epsilon
+					|| abs(this.getPath().getLength() - diff) < epsilon))
+				&& (_ignoreOther
+					|| (!this._intersection && !loc._intersection
+						|| this._intersection && this._intersection.equals(
+								loc._intersection, true)));
+		}
+		return res;
 	},
 
 	toString: function() {
@@ -6593,17 +6787,123 @@
 		if (this._distance != null)
 			parts.push('distance: ' + f.number(this._distance));
 		return '{ ' + parts.join(', ') + ' }';
+	},
+
+	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.getParameter(),
+			t2 = inter.getParameter(),
+			tMin = 4e-7,
+			tMax = 1 - tMin;
+		if (t1 >= tMin && t1 <= tMax || t2 >= tMin && t2 <= tMax)
+			return !this.isTouching();
+		var c2 = this.getCurve(),
+			c1 = c2.getPrevious(),
+			c4 = inter.getCurve(),
+			c3 = c4.getPrevious(),
+			PI = Math.PI;
+		if (!c1 || !c3)
+			return false;
+
+		function isInRange(angle, min, max) {
+			return min < max
+				? angle > min && angle < max
+				: angle > min && angle <= PI || angle >= -PI && angle < max;
+		}
+
+		var a1 = c1.getTangentAt(tMax, true).negate().getAngleInRadians(),
+			a2 = c2.getTangentAt(tMin, true).getAngleInRadians(),
+			a3 = c3.getTangentAt(tMax, true).negate().getAngleInRadians(),
+			a4 = c4.getTangentAt(tMin, true).getAngleInRadians();
+
+		return (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2))
+			&& (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1));
+	},
+
+	isOverlap: function() {
+		return !!this._overlap;
 	}
 }, 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 get = name + 'At';
+	this[name] = function() {
+		var parameter = this.getParameter(),
+			curve = this.getCurve();
+		return parameter != null && curve && curve[get](parameter, 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(),
+						2e-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
+				? (loc.getIndex() + loc.getParameter())
+				- (loc2.getIndex() + loc2.getParameter())
+				: path1._id - path2._id;
+			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 = 0, l = locations.length; i < l; i++) {
+				insert(expanded, locations[i]._intersection, false);
+			}
+			return expanded;
+		}
+	}};
+});
 
 var PathItem = Item.extend({
 	_class: 'PathItem',
@@ -6611,60 +6911,64 @@
 	initialize: function PathItem() {
 	},
 
-	getIntersections: function(path, _matrix, _expand) {
-		if (this === path)
-			path = null;
-		var locations = [],
-			curves1 = this.getCurves(),
-			curves2 = path ? path.getCurves() : curves1,
+	getIntersections: function(path, include, _matrix, _returnFirst) {
+		var self = this === path || !path,
 			matrix1 = this._matrix.orNullIfIdentity(),
-			matrix2 = path ? (_matrix || path._matrix).orNullIfIdentity()
-				: matrix1,
+			matrix2 = self ? matrix1
+				: (_matrix || path._matrix).orNullIfIdentity();
+		if (!self && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
+			return [];
+		var curves1 = this.getCurves(),
+			curves2 = self ? curves1 : path.getCurves(),
 			length1 = curves1.length,
-			length2 = path ? curves2.length : length1,
+			length2 = self ? length1 : curves2.length,
 			values2 = [],
-			tMin = 0.000001,
-			tMax = 1 - tMin;
-		if (path && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
-			return [];
+			arrays = [],
+			locations,
+			path;
 		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;
-						}
+				values1 = self ? values2[i] : curve1.getValues(matrix1),
+				path1 = curve1.getPath();
+			if (path1 !== path) {
+				path = path1;
+				locations = [];
+				arrays.push(locations);
+			}
+			if (self) {
+				Curve._getSelfIntersection(values1, curve1, locations, {
+					include: include,
+					startConnected: length1 === 1 &&
+							curve1.getPoint1().equals(curve1.getPoint2())
+				});
+			}
+			for (var j = self ? i + 1 : 0; j < length2; j++) {
+				if (_returnFirst && locations.length)
+					return locations;
+				var curve2 = curves2[j];
+				Curve._getIntersections(
+					values1, values2[j], curve1, curve2, locations,
+					{
+						include: include,
+						startConnected: self && curve1.getPrevious() === curve2,
+						endConnected: self && curve1.getNext() === curve2
+					}
 				);
 			}
 		}
-		return Curve.filterIntersections(locations, _expand);
+		locations = [];
+		for (var i = 0, l = arrays.length; i < l; i++) {
+			locations.push.apply(locations, arrays[i]);
+		}
+		return locations;
+	},
+
+	getCrossings: function(path) {
+		return this.getIntersections(path, function(inter) {
+			return inter.isCrossing();
+		});
 	},
 
 	_asPathItem: function() {
@@ -6840,14 +7144,14 @@
 			var parent = this._parent;
 			if (parent)
 				parent._currentPath = undefined;
-			this._length = this._clockwise = undefined;
+			this._length = this._area = this._clockwise = this._monoCurves =
+					undefined;
 			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;
 		} else if (flags & 32) {
 			this._bounds = undefined;
 		}
@@ -6977,24 +7281,6 @@
 		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;
-	},
-
 	_transformContent: function(matrix) {
 		var coords = new Array(6);
 		for (var i = 0, l = this._segments.length; i < l; i++)
@@ -7024,12 +7310,11 @@
 			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,
+		if (curves) {
+			var total = this._countCurves(),
+				from = index + amount - 1 === total ? index - 1 : index,
 				start = from,
-				to = Math.min(from + amount, this._countCurves());
+				to = Math.min(from + amount, total);
 			if (segs._curves) {
 				curves.splice.apply(curves, [from, 0].concat(segs._curves));
 				start += segs._curves.length;
@@ -7134,22 +7419,57 @@
 
 	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();
-		return area;
+		if (this._area == null) {
+			var segments = this._segments,
+				count = segments.length,
+				last = count - 1,
+				area = 0;
+			for (var i = 0, l = this._closed ? count : last; i < l; i++) {
+				area += Curve.getArea(Curve.getValues(
+						segments[i], segments[i < last ? i + 1 : 0]));
+			}
+			this._area = area;
+		}
+		return this._area;
+	},
+
+	isClockwise: function() {
+		if (this._clockwise !== undefined)
+			return this._clockwise;
+		return this.getArea() >= 0;
+	},
+
+	setClockwise: function(clockwise) {
+		if (this.isClockwise() != (clockwise = !!clockwise))
+			this.reverse();
+		this._clockwise = clockwise;
 	},
 
 	isFullySelected: function() {
@@ -7203,7 +7523,8 @@
 		var curves = this.getCurves();
 		for (var i = curves.length - 1; i >= 0; i--) {
 			var curve = curves[i];
-			if (curve.isLinear() && curve.getLength() === 0)
+			if (!curve.hasHandles() && (curve.getLength() === 0
+					|| curve.isCollinear(curve.getNext())))
 				curve.remove();
 		}
 		return this;
@@ -7228,14 +7549,15 @@
 			index = arg.index;
 			parameter = arg.parameter;
 		}
-		var tolerance = 0.000001;
-		if (parameter >= 1 - tolerance) {
+		var tMin = 4e-7,
+			tMax = 1 - tMin;
+		if (parameter >= tMax) {
 			index++;
 			parameter--;
 		}
 		var curves = this.getCurves();
 		if (index >= 0 && index < curves.length) {
-			if (parameter > tolerance) {
+			if (parameter >= tMin) {
 				curves[index++].divide(parameter, true);
 			}
 			var segs = this.removeSegments(index, this._segments.length, true),
@@ -7244,7 +7566,9 @@
 				this.setClosed(false);
 				path = this;
 			} else {
-				path = this._clone(new Path().insertAbove(this, true));
+				path = new Path(Item.NO_INSERT);
+				path.insertAbove(this, true);
+				this._clone(path);
 			}
 			path._add(segs, 0);
 			this.addSegment(segs[0]);
@@ -7253,18 +7577,6 @@
 		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;
-	},
-
 	reverse: function() {
 		this._segments.reverse();
 		for (var i = 0, l = this._segments.length; i < l; i++) {
@@ -7305,7 +7617,7 @@
 					this._add(segments.slice());
 				}
 			}
-			if (path.closed)
+			if (path._closed)
 				this._add([segments[0]]);
 			path.remove();
 		}
@@ -7330,15 +7642,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) {
@@ -7515,22 +7855,6 @@
 {
 	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();
@@ -7563,7 +7887,7 @@
 				return curve.getLocationAt(offset - start);
 			}
 		}
-		if (offset <= this.getLength())
+		if (curves.length > 0 && offset <= this.getLength())
 			return new CurveLocation(curves[curves.length - 1], 1);
 		return null;
 	},
@@ -7586,7 +7910,8 @@
 	getNearestPoint: function() {
 		return this.getNearestLocation.apply(this, arguments).getPoint();
 	}
-}), new function() {
+}),
+new function() {
 
 	function drawHandles(ctx, segments, matrix, size) {
 		var half = size / 2;
@@ -7750,8 +8075,8 @@
 			drawHandles(ctx, this._segments, matrix, paper.settings.handleSize);
 		}
 	};
-}, new function() {
-
+},
+new function() {
 	function getFirstControlPoints(rhs) {
 		var n = rhs.length,
 			x = [],
@@ -7843,7 +8168,8 @@
 			}
 		}
 	};
-}, new function() {
+},
+new function() {
 	function getCurrentSegment(that) {
 		var segments = that._segments;
 		if (segments.length === 0)
@@ -7929,7 +8255,6 @@
 					x = pt.x,
 					y = pt.y,
 					abs = Math.abs,
-					epsilon = 1e-12,
 					rx = abs(radius.width),
 					ry = abs(radius.height),
 					rxSq = rx * rx,
@@ -7945,7 +8270,7 @@
 				}
 				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(
@@ -8075,14 +8400,6 @@
 	},
 
 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) {
 		var first = segments[0];
 		if (!first)
@@ -8427,6 +8744,13 @@
 	},
 
 	insertChildren: function insertChildren(index, items, _preserve) {
+		for (var i = items.length - 1; i >= 0; i--) {
+			var item = items[i];
+			if (item instanceof CompoundPath) {
+				items.splice.apply(items, [i, 1].concat(item.removeChildren()));
+				item.remove();
+			}
+		}
 		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];
@@ -8448,15 +8772,20 @@
 	},
 
 	reduce: function reduce() {
-		if (this._children.length === 0) {
+		var children = this._children;
+		for (var i = children.length - 1; i >= 0; i--) {
+			var path = children[i].reduce();
+			if (path.isEmpty())
+				children.splice(i, 1);
+		}
+		if (children.length === 0) {
 			var path = new Path(Item.NO_INSERT);
 			path.insertAbove(this);
 			path.setStyle(this._style);
 			this.remove();
 			return path;
-		} else {
-			return reduce.base.call(this);
-		}
+		}
+		return reduce.base.call(this);
 	},
 
 	isClockwise: function() {
@@ -8514,7 +8843,7 @@
 			var child = children[i],
 				mx = child._matrix;
 			paths.push(child.getPathData(_matrix && !mx.isIdentity()
-					? _matrix.chain(mx) : mx, _precision));
+					? _matrix.chain(mx) : _matrix, _precision));
 		}
 		return paths.join(' ');
 	}
@@ -8562,7 +8891,8 @@
 						: matrix.chain(mx));
 		}
 	}
-}, new function() {
+},
+new function() {
 	function getCurrentPath(that, check) {
 		var children = that._children;
 		if (check && children.length === 0)
@@ -8573,7 +8903,8 @@
 	var fields = {
 		moveTo: function() {
 			var current = getCurrentPath(this),
-				path = current && current.isEmpty() ? current : new Path();
+				path = current && current.isEmpty() ? current
+						: new Path(Item.NO_INSERT);
 			if (path !== current)
 				this.addChild(path);
 			path.moveTo.apply(path, arguments);
@@ -8623,24 +8954,40 @@
 		}
 	};
 
+	function preparePath(path, resolve) {
+		var res = path.clone(false).reduce().transform(null, true, true);
+		return resolve ? res.resolveCrossings().reorient() : res;
+	}
+
+	function finishBoolean(ctor, paths, path1, path2, reduce) {
+		var result = new ctor(Item.NO_INSERT);
+		result.addChildren(paths, true);
+		if (reduce)
+			result = result.reduce();
+		result.insertAbove(path2 && path1.isSibling(path2)
+				&& path1.getIndex() < path2.getIndex()
+					? path2 : path1);
+		result.setStyle(path1._style);
+		return result;
+	}
+
 	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 (!path1._children && !path1._closed)
+			return computeOpenBoolean(path1, path2, operation);
+		var _path1 = preparePath(path1, true),
+			_path2 = path2 && path1 !== path2 && preparePath(path2, true);
 		if (_path2 && /^(subtract|exclude)$/.test(operation)
 				^ (_path2.isClockwise() !== _path1.isClockwise()))
 			_path2.reverse();
-		splitPath(_path1.getIntersections(_path2, null, true));
-
-		var chain = [],
-			segments = [],
-			monoCurves = [],
-			tolerance = 0.000001;
+		var intersections = CurveLocation.expand(
+			_path1.getIntersections(_path2, function(inter) {
+				return _path2 && inter.isOverlap() || inter.isCrossing();
+			})
+		);
+		divideLocations(intersections);
+
+		var segments = [],
+			monoCurves = [];
 
 		function collect(paths) {
 			for (var i = 0, l = paths.length; i < l; i++) {
@@ -8653,114 +9000,121 @@
 		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 = intersections.length; i < l; i++) {
+			propagateWinding(intersections[i]._segment, _path1, _path2,
+					monoCurves, operation);
+		}
 		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);
-						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 (segment._winding == null) {
+				propagateWinding(segment, _path1, _path2, monoCurves,
+						operation);
+			}
+		}
+		return finishBoolean(CompoundPath, tracePaths(segments, operation),
+				path1, path2, true);
+	}
+
+	function computeOpenBoolean(path1, path2, operation) {
+		if (!path2 || !path2._children && !path2._closed
+				|| !/^(subtract|intersect)$/.test(operation))
+			return null;
+		var _path1 = preparePath(path1, false),
+			_path2 = preparePath(path2, false),
+			intersections = _path1.getIntersections(_path2, function(inter) {
+				return inter.isOverlap() || inter.isCrossing();
+			}),
+			sub = operation === 'subtract',
+			paths = [];
+
+		function addPath(path) {
+			if (_path2.contains(path.getPointAt(path.getLength() / 2)) ^ sub) {
+				paths.unshift(path);
+				return true;
+			}
+		}
+
+		for (var i = intersections.length - 1; i >= 0; i--) {
+			var path = intersections[i].split();
+			if (path) {
+				if (addPath(path))
+					path.getFirstSegment().setHandleIn(0, 0);
+				_path1.getLastSegment().setHandleOut(0, 0);
+			}
+		}
+		addPath(_path1);
+		return finishBoolean(Group, paths, path1, path2);
+	}
+
+	function linkIntersections(from, to) {
+		var prev = from;
+		while (prev) {
+			if (prev === to)
+				return;
+			prev = prev._prev;
+		}
+		while (from._next && from._next !== to)
+			from = from._next;
+		if (!from._next) {
+			while (to._prev)
+				to = to._prev;
+			from._next = to;
+			to._prev = from;
+		}
+	}
+
+	function divideLocations(locations) {
+		var tMin = 4e-7,
 			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;
+			noHandles = false,
+			clearSegments = [],
+			prevCurve,
+			prevT;
+
+		for (var i = locations.length - 1; i >= 0; i--) {
+			var loc = locations[i],
+				curve = loc._curve,
+				t = loc._parameter,
+				origT = t;
+			if (curve !== prevCurve) {
+				noHandles = !curve.hasHandles();
+			} else if (prevT > 0) {
+				t /= prevT;
+			}
+			var segment;
+			if (t < tMin) {
+				segment = curve._segment1;
+			} else if (t > tMax) {
+				segment = curve._segment2;
 			} else {
-				curve = loc._curve;
-				if (linearHandles)
-					resetLinear();
-				linearHandles = curve.isLinear() ? [
-						curve._segment1._handleOut,
-						curve._segment2._handleIn
-					] : null;
-			}
-			var newCurve,
-				segment;
-			if (newCurve = curve.divide(t, true, true)) {
-				segment = newCurve._segment1;
-				curve = newCurve.getPrevious();
-				if (linearHandles)
-					linearHandles.push(segment._handleOut, segment._handleIn);
+				segment = curve.divide(t, true, true)._segment1;
+				if (noHandles)
+					clearSegments.push(segment);
+			}
+			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 = 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();
+				segment._intersection = dest;
+			}
+			prevCurve = curve;
+			prevT = origT;
+		}
+		for (var i = 0, l = clearSegments.length; i < l; i++) {
+			clearSegments[i].clearHandles();
+		}
 	}
 
 	function getWinding(point, curves, horizontal, testContains) {
-		var tolerance = 0.000001,
-			tMin = tolerance,
+		var epsilon = 2e-7,
+			tMin = 4e-7,
 			tMax = 1 - tMin,
 			px = point.x,
 			py = point.y,
@@ -8771,8 +9125,8 @@
 		if (horizontal) {
 			var yTop = -Infinity,
 				yBottom = Infinity,
-				yBefore = py - tolerance,
-				yAfter = py + tolerance;
+				yBefore = py - epsilon,
+				yAfter = py + epsilon;
 			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) {
@@ -8789,12 +9143,14 @@
 			yTop = (yTop + py) / 2;
 			yBottom = (yBottom + py) / 2;
 			if (yTop > -Infinity)
-				windLeft = getWinding(new Point(px, yTop), curves);
+				windLeft = getWinding(new Point(px, yTop), curves, false,
+						testContains);
 			if (yBottom < Infinity)
-				windRight = getWinding(new Point(px, yBottom), curves);
+				windRight = getWinding(new Point(px, yBottom), curves, false,
+						testContains);
 		} else {
-			var xBefore = px - tolerance,
-				xAfter = px + tolerance;
+			var xBefore = px - epsilon,
+				xAfter = px + epsilon;
 			var startCounted = false,
 				prevCurve,
 				prevT;
@@ -8814,7 +9170,7 @@
 						var x = Curve.getPoint(values, t).x,
 							slope = Curve.getTangent(values, t).y,
 							counted = false;
-						if (Numerical.isZero(slope) && !Curve.isLinear(values)
+						if (Numerical.isZero(slope) && !Curve.isStraight(values)
 								|| t < tMin && slope * Curve.getTangent(
 									curve.previous.values, 1).y < 0
 								|| t > tMax && slope * Curve.getTangent(
@@ -8842,81 +9198,157 @@
 		return Math.max(abs(windLeft), abs(windRight));
 	}
 
-	function tracePaths(segments, operator, selfOp) {
+	function propagateWinding(segment, path1, path2, monoCurves, operation) {
+		var epsilon = 2e-7,
+			chain = [],
+			start = segment,
+			totalLength = 0,
+			windingSum = 0;
+		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);
+		for (var i = 0; i < 3; i++) {
+			var length = totalLength * (i + 1) / 4;
+			for (var k = 0, m = chain.length; k < m; k++) {
+				var node = chain[k],
+					curveLength = node.length;
+				if (length <= curveLength) {
+					if (length < epsilon || curveLength - length < epsilon)
+						length = curveLength / 2;
+					var curve = node.curve,
+						path = curve._path,
+						parent = path._parent,
+						pt = curve.getPointAt(length),
+						hor = curve.isHorizontal();
+					if (parent instanceof CompoundPath)
+						path = parent;
+					windingSum += operation === 'subtract' && path2
+						&& (path === path1 && path2._getWinding(pt, hor)
+						|| path === path2 && !path1._getWinding(pt, hor))
+						? 0
+						: getWinding(pt, monoCurves, hor);
+					break;
+				}
+				length -= curveLength;
+			}
+		}
+		var winding = Math.round(windingSum / 3);
+		for (var j = chain.length - 1; j >= 0; j--)
+			chain[j].segment._winding = winding;
+	}
+
+	function tracePaths(segments, operation) {
 		var paths = [],
-			tMin = 0.000001,
-			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))
+			start,
+			otherStart,
+			operator = operators[operation],
+			overlapWinding = {
+				unite: { 1: 2 },
+				intersect: { 2: 1 }
+			}[operation];
+
+		function isValid(seg, adjusted) {
+			if (seg._visited)
+				return false;
+			if (!operator)
+				return true;
+			var winding = seg._winding,
+				inter = seg._intersection;
+			if (inter && adjusted && overlapWinding && inter.isOverlap())
+				winding = overlapWinding[winding] || winding;
+			return operator(winding);
+		}
+
+		function isStart(seg) {
+			return seg === start || seg === otherStart;
+		}
+
+		function findBestIntersection(inter, strict) {
+			if (!inter._next)
+				return inter;
+			while (inter) {
+				var seg = inter._segment,
+					nextSeg = seg.getNext(),
+					nextInter = nextSeg._intersection;
+				if (isStart(nextSeg)
+					|| !seg._visited && !nextSeg._visited
+					&& (!operator
+						|| (!strict || isValid(seg))
+						&& (!(strict && nextInter && nextInter.isOverlap())
+							&& isValid(nextSeg)
+							|| !strict && nextInter
+							&& isValid(nextInter._segment))
+					))
+					return inter;
+				inter = inter._next;
+			}
+			return null;
+		}
+
+		function findStartSegment(inter, next) {
+			while (inter) {
+				var seg = inter._segment;
+				if (isStart(seg))
+					return seg;
+				inter = inter[next ? '_next' : '_prev'];
+			}
+		}
+
+		for (var i = 0, l = segments.length; i < l; i++) {
+			var seg = segments[i],
+				path = null,
+				finished = false;
+			if (!isValid(seg, true))
 				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;
-							} else {
-								seg._visited = interSeg._visited;
-								seg = interSeg;
-								if (nextSeg._visited)
-									dir = 1;
-							}
-						} else {
-							dir = 1;
+			start = otherStart = null;
+			while (!finished) {
+				var inter = seg._intersection,
+					handleIn = path && seg._handleIn;
+				inter = inter && (findBestIntersection(inter, true)
+						|| findBestIntersection(inter, false)) || inter;
+				var other = inter && inter._segment;
+				if (other && isValid(other))
+					seg = other;
+				if (seg._visited) {
+					finished = isStart(seg);
+					if (!finished && inter) {
+						var found = findStartSegment(inter, true)
+							|| findStartSegment(inter, false);
+						if (found) {
+							seg = found;
+							finished = true;
 						}
 					}
-					handleOut = dir > 0 ? seg._handleOut : seg._handleIn;
-				}
-				path.add(new Segment(seg._point, added && handleIn, handleOut));
-				added = true;
+					break;
+				}
+				if (!path) {
+					path = new Path(Item.NO_INSERT);
+					start = seg;
+					otherStart = other;
+				}
+				path.add(new Segment(seg._point, handleIn, 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);
+				seg = seg.getNext();
+				finished = isStart(seg);
+			}
+			if (finished) {
+				path.firstSegment.setHandleIn(seg._handleIn);
 				path.setClosed(true);
-			} else {
-				path.lastSegment._handleOut.set(0, 0);
-			}
-			if (path._segments.length >
-					(path._closed ? path.isLinear() ? 2 : 0 : 1))
+			} else if (path) {
+				console.error('Boolean operation resulted in open path',
+						'segments =', path._segments.length,
+						'length =', path.getLength());
+				path = null;
+			}
+			if (path && (path._segments.length > 8
+					|| !Numerical.isZero(path.getArea()))) {
 				paths.push(path);
+				path = null;
+			}
 		}
 		return paths;
 	}
@@ -8944,7 +9376,23 @@
 		},
 
 		divide: function(path) {
-			return new Group([this.subtract(path), this.intersect(path)]);
+			return finishBoolean(Group,
+					[this.subtract(path), this.intersect(path)],
+					this, path, true);
+		},
+
+		resolveCrossings: function() {
+			var crossings = this.getCrossings();
+			if (!crossings.length)
+				return this;
+			divideLocations(CurveLocation.expand(crossings));
+			var paths = this._children || [this],
+				segments = [];
+			for (var i = 0, l = paths.length; i < l; i++) {
+				segments.push.apply(segments, paths[i]._segments);
+			}
+			return finishBoolean(CompoundPath, tracePaths(segments),
+					this, null, false);
 		}
 	};
 });
@@ -8980,24 +9428,24 @@
 				y1 = v[3],
 				y2 = v[5],
 				y3 = v[7];
-			if (Curve.isLinear(v)) {
+			if (Curve.isStraight(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) {
+					tMin = 4e-7,
+					tMax = 1 - tMin,
+					roots = [],
+					n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax);
+				if (n === 0) {
 					insertCurve(v);
 				} else {
 					roots.sort();
 					var t = roots[0],
 						parts = Curve.subdivide(v, t);
 					insertCurve(parts[0]);
-					if (count > 1) {
+					if (n > 1) {
 						t = (roots[1] - t) / (1 - t);
 						parts = Curve.subdivide(parts[1], t);
 						insertCurve(parts[0]);
@@ -9112,7 +9560,7 @@
 		function computeParts(curve, index, minT, maxT) {
 			if ((maxT - minT) > minDifference
 					&& !Curve.isFlatEnough(curve, tolerance || 0.25)) {
-				var split = Curve.subdivide(curve),
+				var split = Curve.subdivide(curve, 0.5),
 					halfT = (minT + maxT) / 2;
 				computeParts(split[0], index, minT, halfT);
 				computeParts(split[1], index, halfT, maxT);
@@ -9120,7 +9568,7 @@
 				var x = curve[6] - curve[0],
 					y = curve[7] - curve[1],
 					dist = Math.sqrt(x * x + y * y);
-				if (dist > 0.000001) {
+				if (dist > 1e-6) {
 					length += dist;
 					parts.push({
 						offset: length,
@@ -9366,7 +9814,7 @@
 			pt2 = this.evaluate(1, curve2, u),
 			diff = pt.subtract(point),
 			df = pt1.dot(pt1) + diff.dot(pt2);
-		if (Math.abs(df) < 0.000001)
+		if (Math.abs(df) < 1e-6)
 			return u;
 		return u - diff.dot(pt1) / df;
 	},
@@ -9801,7 +10249,7 @@
 								: '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++) {
@@ -10003,7 +10451,8 @@
 			}
 		}
 	});
-}, new function() {
+},
+new function() {
 	var operators = {
 		add: function(a, b) {
 			return a + b;
@@ -10039,24 +10488,6 @@
 	});
 });
 
-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',
 
@@ -10681,19 +11112,29 @@
 		return true;
 	},
 
-	_events: {
-		onFrame: {
-			install: function() {
-				this.play();
-			},
-
-			uninstall: function() {
-				this.pause();
-			}
-		},
-
-		onResize: {}
-	},
+	_events: Base.each(['onResize', 'onMouseDown', 'onMouseUp', 'onMouseMove'],
+		function(name) {
+			this[name] = {
+				install: function(type) {
+					this._installEvent(type);
+				},
+
+				uninstall: function(type) {
+					this._uninstallEvent(type);
+				}
+			};
+		}, {
+			onFrame: {
+				install: function() {
+					this.play();
+				},
+
+				uninstall: function() {
+					this.pause();
+				}
+			}
+		}
+	),
 
 	_animate: false,
 	_time: 0,
@@ -10888,7 +11329,8 @@
 			return new CanvasView(project, element);
 		}
 	}
-}, new function() {
+},
+new function() {
 	var tool,
 		prevFocus,
 		tempFocus,
@@ -11011,11 +11453,50 @@
 		load: updateFocus
 	});
 
+	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
+		}
+	};
+
 	return {
 		_viewEvents: viewEvents,
 
 		_handleEvent: function() {},
 
+		_installEvent: function(type) {
+			var counters = this._eventCounters;
+			if (counters) {
+				for (var key in mouseFlags) {
+					counters[key] = (counters[key] || 0)
+							+ (mouseFlags[key][type] || 0);
+				}
+			}
+		},
+
+		_uninstallEvent: function(type) {
+			var counters = this._eventCounters;
+			if (counters) {
+				for (var key in mouseFlags)
+					counters[key] -= mouseFlags[key][type] || 0;
+			}
+		},
+
 		statics: {
 			updateFocus: updateFocus
 		}
@@ -11105,8 +11586,8 @@
 		project._needsUpdate = false;
 		return true;
 	}
-}, new function() {
-
+},
+new function() {
 	var downPoint,
 		lastPoint,
 		overPoint,
@@ -11551,8 +12032,7 @@
 	},
 
 	setFixedDistance: function(distance) {
-		this._minDistance = distance;
-		this._maxDistance = distance;
+		this._minDistance = this._maxDistance = distance;
 	},
 
 	_updateEvent: function(type, point, minDistance, maxDistance, start,