diff --git a/examples/Animated/BooleanOperations.html b/examples/Animated/BooleanOperations.html
index 9c271a99..83fd01c0 100644
--- a/examples/Animated/BooleanOperations.html
+++ b/examples/Animated/BooleanOperations.html
@@ -92,7 +92,7 @@
                 text.content = 'ring.' + operation + '(square)';
             }
             result.selected = true;
-            result.fillColor = colors[curIndex % operations.length];
+            result.fillColor = colors[curIndex % colors.length];
             result.moveBelow(text);
 
             // If the result is a group, color each of its children differently:
diff --git a/examples/Scripts/BooleanOperations.html b/examples/Scripts/BooleanOperations.html
index e12356ff..9ed464f0 100644
--- a/examples/Scripts/BooleanOperations.html
+++ b/examples/Scripts/BooleanOperations.html
@@ -269,7 +269,7 @@
         // // annotatePath(pathB)
         // // pathB.translate([ 300, 0 ]);
         // // pathB.segments.filter(function(a) { return a._ixPair; }).map(
-        // //    function(a) { a._ixPair.getIntersection()._segment.selected = true; });
+        // //    function(a) { a._ixPair.intersection._segment.selected = true; });
 
         // console.time('unite');
         // var nup = unite(pathA, pathB);
@@ -360,14 +360,12 @@
 
     function disperse(path, distance) {
         distance = distance || 10;
-        if (! path instanceof CompoundPath || ! path instanceof Group) { return; }
         var center = path.bounds.center;
         var children = path.children, i ,len;
-        for (var i = 0, len = children.length; i < len; i++) {
+        for (var i = 0, len = children && children.length; i < len; i++) {
             var cCenter = children[i].bounds.center;
             var vec = cCenter.subtract(center);
-            vec = (vec.isClose([0,0], 0.5))? vec : vec.normalize(distance);
-            children[i].translate(vec);
+            children[i].translate(vec.length < 0.5 ? vec : vec.normalize(distance));
         }
     }
 
diff --git a/package.json b/package.json
index 5ebfca0f..0fd06d12 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
   "devDependencies": {
     "gulp": "^3.9.0",
     "gulp-qunit": "^1.2.1",
-    "prepro": "~0.8.3",
+    "prepro": "~0.9.0",
     "qunitjs": "~1.15.0",
     "uglify-js": "~2.4.24"
   },
diff --git a/src/basic/Line.js b/src/basic/Line.js
index 3d143109..7ed66852 100644
--- a/src/basic/Line.js
+++ b/src/basic/Line.js
@@ -48,30 +48,30 @@ var Line = Base.extend(/** @lends Line# */{
     },
 
     /**
-     * The starting point of the line
+     * The starting point of the line.
      *
-     * @name Line#point
      * @type Point
+     * @bean
      */
     getPoint: function() {
         return new Point(this._px, this._py);
     },
 
     /**
-     * The vector of the line
+     * The direction of the line as a vector.
      *
-     * @name Line#vector
      * @type Point
+     * @bean
      */
     getVector: function() {
         return new Point(this._vx, this._vy);
     },
 
     /**
-     * The length of the line
+     * The length of the line.
      *
-     * @name Line#length
      * @type Number
+     * @bean
      */
     getLength: function() {
         return this.getVector().getLength();
@@ -113,35 +113,47 @@ var Line = Base.extend(/** @lends Line# */{
     },
 
     isCollinear: function(line) {
-        // TODO: Tests showed that 1e-10 might work well here, but we want to
-        // keep it in sync with Point#isCollinear()
-        return this._vx * line._vy - this._vy * line._vx
-                < /*#=*/Numerical.TOLERANCE;
+        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: /** @lends Line */{
-        intersect: function(apx, apy, avx, avy, bpx, bpy, bvx, bvy, asVector,
+        intersect: function(p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector,
                 isInfinite) {
             // Convert 2nd points to vectors if they are not specified as such.
             if (!asVector) {
-                avx -= apx;
-                avy -= apy;
-                bvx -= bpx;
-                bvy -= bpy;
+                v1x -= p1x;
+                v1y -= p1y;
+                v2x -= p2x;
+                v2y -= p2y;
             }
-            var cross = avx * bvy - avy * bvx;
+            var cross = v1x * v2y - v1y * v2x;
             // Avoid divisions by 0, and errors when getting too close to 0
             if (!Numerical.isZero(cross)) {
-                var dx = apx - bpx,
-                    dy = apy - bpy,
-                    ta = (bvx * dy - bvy * dx) / cross,
-                    tb = (avx * dy - avy * dx) / cross;
-                // Check the ranges of t parameters if the line is not allowed
-                // to extend beyond the definition points.
-                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,
+                    // Check the ranges of the u parameters if the line is not
+                    // allowed to extend beyond the definition points, but
+                    // compare with EPSILON tolerance over the [0, 1] bounds.
+                    epsilon = /*#=*/Numerical.EPSILON,
+                    uMin = -epsilon,
+                    uMax = 1 + epsilon;
+                if (isInfinite
+                        || uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) {
+                    if (!isInfinite) {
+                        // Address the tolerance at the bounds by clipping to
+                        // the actual range.
+                        u1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1;
+                    }
                     return new Point(
-                                apx + ta * avx,
-                                apy + ta * avy);
+                            p1x + u1 * v1x,
+                            p1y + u1 * v1y);
+                }
             }
         },
 
@@ -157,9 +169,7 @@ var Line = Base.extend(/** @lends Line# */{
                 ccw = v2x * vx + v2y * vy; // ccw = v2.dot(v1);
                 if (ccw > 0) {
                     // ccw = v2.subtract(v1).dot(v1);
-                    v2x -= vx;
-                    v2y -= vy;
-                    ccw = v2x * vx + v2y * vy;
+                    ccw = (v2x - vx) * vx + (v2y - vy) * vy;
                     if (ccw < 0)
                         ccw = 0;
                 }
@@ -172,11 +182,13 @@ var Line = Base.extend(/** @lends Line# */{
                 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);
+            // Based on the error analysis by @iconexperience outlined in
+            // https://github.com/paperjs/paper.js/issues/799
+            return vx === 0
+                ? vy >= 0 ? px - x : x - px
+                : vy === 0
+                ? vx >= 0 ? y - py : py - y
+                : (vx * (y - py) - vy * (x - px)) / Math.sqrt(vx * vx + vy * vy);
         }
     }
 });
diff --git a/src/basic/Point.js b/src/basic/Point.js
index f9af8718..61304f51 100644
--- a/src/basic/Point.js
+++ b/src/basic/Point.js
@@ -460,11 +460,11 @@ var Point = Base.extend(/** @lends Point# */{
             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;
     },
@@ -690,7 +690,9 @@ var Point = Base.extend(/** @lends Point# */{
      * @param {Number} tolerance the maximum distance allowed
      * @return {Boolean} {@true if it is within the given distance}
      */
-    isClose: function(point, tolerance) {
+    isClose: function(/* point, tolerance */) {
+        var point = Point.read(arguments),
+            tolerance = Base.read(arguments);
         return this.getDistance(point) < tolerance;
     },
 
@@ -701,11 +703,9 @@ var Point = Base.extend(/** @lends Point# */{
      * @param {Point} point the vector to check against
      * @return {Boolean} {@true it is collinear}
      */
-    isCollinear: function(point) {
-        // NOTE: Numerical.EPSILON is too small, breaking shape-path-shape
-        // conversion test. But tolerance is probably too large?
-        // TODO: Tests showed that 1e-10 might work well here.
-        return Math.abs(this.cross(point)) < /*#=*/Numerical.TOLERANCE;
+    isCollinear: function(/* point */) {
+        var point = Point.read(arguments);
+        return Point.isCollinear(this.x, this.y, point.x, point.y);
     },
 
     // TODO: Remove version with typo after a while (deprecated June 2015)
@@ -718,10 +718,9 @@ var Point = Base.extend(/** @lends Point# */{
      * @param {Point} point the vector to check against
      * @return {Boolean} {@true it is orthogonal}
      */
-    isOrthogonal: function(point) {
-        // NOTE: Numerical.EPSILON is too small, breaking shape-path-shape
-        // conversion test.
-        return Math.abs(this.dot(point)) < /*#=*/Numerical.TOLERANCE;
+    isOrthogonal: function(/* point */) {
+        var point = Point.read(arguments);
+        return Point.isOrthogonal(this.x, this.y, point.x, point.y);
     },
 
     /**
@@ -767,23 +766,19 @@ var Point = Base.extend(/** @lends Point# */{
     },
 
     /**
-     * Returns the projection of the point on another point.
+     * Returns the projection of the point onto another point.
      * Both points are interpreted as vectors.
      *
      * @param {Point} point
-     * @return {Point} the projection of the point on another point
+     * @return {Point} the projection of the point onto another point
      */
     project: function(/* point */) {
-        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
+        );
     },
 
     /**
@@ -920,6 +915,23 @@ var Point = Base.extend(/** @lends Point# */{
          */
         random: function() {
             return new Point(Math.random(), Math.random());
+        },
+
+        isCollinear: function(x1, y1, x2, y2) {
+            // NOTE: We use normalized vectors so that the epsilon comparison is
+            // reliable. We could instead scale the epsilon based on the vector
+            // length. But instead of normalizing the vectors before calculating
+            // the cross product, we can scale the epsilon accordingly.
+            return Math.abs(x1 * y2 - y1 * x2)
+                    <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+                        * /*#=*/Numerical.TRIGONOMETRIC_EPSILON;
+        },
+
+        isOrthogonal: function(x1, y1, x2, y2) {
+            // See Point.isCollinear()
+            return Math.abs(x1 * x2 + y1 * y2)
+                    <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+                        * /*#=*/Numerical.TRIGONOMETRIC_EPSILON;
         }
     }
 }, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
diff --git a/src/basic/Rectangle.js b/src/basic/Rectangle.js
index e26f27f7..75a43e30 100644
--- a/src/basic/Rectangle.js
+++ b/src/basic/Rectangle.js
@@ -475,7 +475,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
      */
 
     /**
-     * The area of the rectangle in square points.
+     * The area of the rectangle.
      *
      * @type Number
      * @bean
@@ -857,7 +857,8 @@ var LinkedRectangle = Rectangle.extend({
             this._owner[this._setter](this);
         return this;
     }
-}, new function() {
+},
+new function() {
     var proto = Rectangle.prototype;
 
     return Base.each(['x', 'y', 'width', 'height'], function(key) {
diff --git a/src/core/PaperScope.js b/src/core/PaperScope.js
index 4e154fa4..7b6a3b4f 100644
--- a/src/core/PaperScope.js
+++ b/src/core/PaperScope.js
@@ -120,7 +120,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
      *
      * @type String
      */
-    version: '/*#=*/__options.version',
+    version: /*#=*/__options.version,
 
     // DOCS: PaperScope#settings
     /**
diff --git a/src/item/Item.js b/src/item/Item.js
index 0dbb9364..2f9d2dde 100644
--- a/src/item/Item.js
+++ b/src/item/Item.js
@@ -1144,7 +1144,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
 
     /**
      * @bean
-     * @deprecated use {@link #getApplyMatrix()} instead.
+     * @deprecated use {@link #applyMatrix} instead.
      */
     getTransformContent: '#getApplyMatrix',
     setTransformContent: '#setApplyMatrix',
@@ -1638,10 +1638,10 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
     intersects: function(item, _matrix) {
         if (!(item instanceof Item))
             return false;
-        // Tell _getIntersections to return as soon as some intersections are
+        // Tell getIntersections() to return as soon as some intersections are
         // found, because all we care for here is there are some or none:
-        return this._asPathItem()._getIntersections(item._asPathItem(),
-                _matrix || item._matrix, [], true).length > 0;
+        return this._asPathItem().getIntersections(item._asPathItem(), null,
+                _matrix || item._matrix, true).length > 0;
     },
 
     /**
@@ -1652,7 +1652,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
      * and may contain a combination of the following values:
      *
      * @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
-     * {Number} the tolerance of the hit-test in points
+     * {Number} the tolerance of the hit-test
      * @option options.class {Function} only hit-test again a certain item class
      * and its sub-classes: {@code Group, Layer, Path, CompoundPath,
      * Shape, Raster, PlacedSymbol, PointText}, etc
@@ -2618,6 +2618,16 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
         return item ? item.isDescendant(this) : false;
     },
 
+    /**
+     * Checks if the item is an a sibling of the specified item.
+     *
+     * @param {Item} item the item to check against
+     * @return {Boolean} {@true if the item is aa sibling of the specified item}
+     */
+    isSibling: function(item) {
+        return this._parent === item._parent;
+    },
+
     /**
      * Checks whether the item is grouped with the specified item.
      *
diff --git a/src/item/Raster.js b/src/item/Raster.js
index 57961a1f..a0ef9a41 100644
--- a/src/item/Raster.js
+++ b/src/item/Raster.js
@@ -207,7 +207,7 @@ var Raster = Item.extend(/** @lends Raster# */{
     /**
      * @private
      * @bean
-     * @deprecated use {@link #getResolution()} instead.
+     * @deprecated use {@link #resolution} instead.
      */
     getPpi: '#getResolution',
 
diff --git a/src/item/Shape.js b/src/item/Shape.js
index 69c033e3..a9c2c75d 100644
--- a/src/item/Shape.js
+++ b/src/item/Shape.js
@@ -64,7 +64,7 @@ var Shape = Item.extend(/** @lends Shape# */{
     /**
      * @private
      * @bean
-     * @deprecated use {@link #getType()} instead.
+     * @deprecated use {@link #type} instead.
      */
     getShape: '#getType',
     setShape: '#setType',
@@ -277,7 +277,6 @@ var Shape = Item.extend(/** @lends Shape# */{
     }
 },
 new function() { // Scope for _contains() and _hitTestSelf() code.
-
     // Returns the center of the quarter corner ellipse for rounded rectangle,
     // if the point lies within its bounding box.
     function getCornerCenter(that, point, expand) {
diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js
index fb285478..aca47247 100644
--- a/src/path/CompoundPath.js
+++ b/src/path/CompoundPath.js
@@ -102,6 +102,15 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
     },
 
     insertChildren: function insertChildren(index, items, _preserve) {
+        // Convert CompoundPath items in the children list by adding their
+        // children to the list and removing their parent.
+        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();
+            }
+        }
         // Pass on 'path' for _type, to make sure that only paths are added as
         // children.
         items = insertChildren.base.call(this, index, items, _preserve, Path);
@@ -131,16 +140,23 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
             this._children[i].smooth();
     },
 
+    // DOCS: reduce()
+    // TEST: reduce()
     reduce: function reduce() {
-        if (this._children.length === 0) { // Replace with a simple empty Path
+        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) { // Replace with a simple empty Path
             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);
     },
 
     /**
@@ -220,8 +236,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
     },
 
     /**
-     * The area of the path in square points. Self-intersecting paths can
-     * contain sub-areas that cancel each other out.
+     * The area that the path's geometry is covering. Self-intersecting paths
+     * can contain sub-areas that cancel each other out.
      *
      * @type Number
      * @bean
@@ -298,7 +314,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
                         : matrix.chain(mx));
         }
     }
-}, new function() { // Injection scope for PostScript-like drawing functions
+},
+new function() { // Injection scope for PostScript-like drawing functions
     /**
      * Helper method that returns the current path and checks if a moveTo()
      * command is required first.
diff --git a/src/path/Curve.js b/src/path/Curve.js
index 5b0ed02d..e0b80985 100644
--- a/src/path/Curve.js
+++ b/src/path/Curve.js
@@ -58,43 +58,74 @@ var Curve = Base.extend(/** @lends Curve# */{
      * @param {Number} y2
      */
     initialize: function Curve(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
-        var count = arguments.length;
+        var count = arguments.length,
+            values,
+            seg1, seg2,
+            point1, point2,
+            handle1, handle2;
+        // The following code has to either set seg1 & seg2,
+        // or point1, point2, handle1 & handle2. At the end, the internal
+        // segments are created accordingly.
         if (count === 3) {
             // Undocumented internal constructor, used by Path#getCurves()
             // new Segment(path, segment1, segment2);
             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) {
             // new Segment(segment);
             // Note: This copies from existing segments through bean getters
-            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) {
+                // As printed by #toString()
+                point1 = arg0.point1;
+                handle1 = arg0.handle1;
+                handle2 = arg0.handle2;
+                point2 = arg0.point2;
+            } else if (Array.isArray(arg0)) {
+                // Convert getValues() array back to points and handles so we
+                // can create segments for those.
+                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) {
             // new Segment(segment1, segment2);
-            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) {
-                // Convert getValue() array back to points and handles so we
-                // can create segments for those.
-                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) {
+            // Convert getValues() array from arguments list back to points and
+            // handles so we can create segments for those.
+            // NOTE: This could be merged with the above code after the array
+            // check through the `arguments` object, but it would break JS
+            // optimizations.
+            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) {
+        // If it has no handles, only serialize points, otherwise handles too.
+        return Base.serialize(this.hasHandles()
+                ? [this.getPoint1(), this.getHandle1(), this.getHandle2(),
+                    this.getPoint2()]
+                : [this.getPoint1(), this.getPoint2()],
+                options, true);
     },
 
     _changed: function() {
@@ -102,6 +133,45 @@ var Curve = Base.extend(/** @lends Curve# */{
         this._length = this._bounds = undefined;
     },
 
+    /**
+     * Returns a copy of the curve.
+     *
+     * @return {Curve}
+     */
+    clone: function() {
+        return new Curve(this._segment1, this._segment2);
+    },
+
+    /**
+     * @return {String} a string representation of the curve
+     */
+    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(', ') + ' }';
+    },
+
+    /**
+     * Removes the curve from the path that it belongs to, by removing its
+     * second segment and merging its handle with the first segment.
+     * @return {Boolean} {@true if the curve was removed}
+     */
+    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;
+    },
+
     /**
      * The first anchor point of the curve.
      *
@@ -228,6 +298,26 @@ var Curve = Base.extend(/** @lends Curve# */{
                 || this._path._closed && curves[curves.length - 1]) || null;
     },
 
+    /**
+     * Checks if the this is the first curve in the {@link Path#curves} array.
+     *
+     * @return {Boolean} {@true if this is the first curve}
+     */
+    isFirst: function() {
+        return this._segment1._index === 0;
+    },
+
+    /**
+     * Checks if the this is the last curve in the {@link Path#curves} array.
+     *
+     * @return {Boolean} {@true if this is the last curve}
+     */
+    isLast: function() {
+        var path = this._path;
+        return path && this._segment1._index === path._curves.length - 1
+                || false;
+    },
+
     /**
      * Specifies whether the points and handles of the curve are selected.
      *
@@ -248,10 +338,30 @@ var Curve = Base.extend(/** @lends Curve# */{
         this.getPoint2().setSelected(selected);
     },
 
+    /**
+     * An array of 8 float values, describing this curve's geometry in four
+     * absolute x/y pairs (point1, handle1, handle2, point2). This format is
+     * used internally for efficient processing of curve geometries, e.g. when
+     * calculating intersections or bounds.
+     *
+     * Note that the handles are converted to absolute coordinates.
+     *
+     * @type Number[]
+     * @bean
+     */
     getValues: function(matrix) {
         return Curve.getValues(this._segment1, this._segment2, matrix);
     },
 
+    /**
+     * An array of 4 point objects, describing this curve's geometry in absolute
+     * coordinates (point1, handle1, handle2, point2).
+     *
+     * Note that the handles are converted to absolute coordinates.
+     *
+     * @type Point[]
+     * @bean
+     */
     getPoints: function() {
         // Convert to array of absolute points
         var coords = this.getValues(),
@@ -262,25 +372,47 @@ var Curve = Base.extend(/** @lends Curve# */{
     },
 
     /**
-     * The approximated length of the curve in points.
+     * The approximated length of the curve.
      *
      * @type Number
      * @bean
      */
     getLength: function() {
-        if (this._length == null) {
-            // Use simple point distance for linear curves
-            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;
     },
 
+    /**
+     * The area that the curve's geometry is covering.
+     *
+     * @type Number
+     * @bean
+     */
     getArea: function() {
         return Curve.getArea(this.getValues());
     },
 
+    /**
+     * @type Line
+     * @bean
+     * @private
+     */
+    getLine: function() {
+        return new Line(this._segment1._point, this._segment2._point);
+    },
+
+    /**
+     * Creates a new curve as a sub-curve from this curve, its range defined by
+     * the given parameters. If {@code from} is larger than {@code to}, then
+     * the resulting curve will have its direction reversed.
+     *
+     * @param {Number} from the curve-time parameter at which the sub-curve
+     * starts
+     * @param {Number} to the curve-time parameter at which the sub-curve
+     * ends
+     * @return {Curve} the newly create sub-curve
+     */
     getPart: function(from, to) {
         return new Curve(Curve.getPart(this.getValues(), from, to));
     },
@@ -291,71 +423,19 @@ var Curve = Base.extend(/** @lends Curve# */{
     },
 
     /**
-     * Checks if this curve defines any curve handle.
+     * Returns all intersections between two {@link Curve} objects as an array
+     * of {@link CurveLocation} objects.
      *
-     * @return {Boolean} {@true if the curve has handles defined}
-     * @see Segment#hasHandles()
-     * @see Path#hasHandles()
+     * @param {Curve} curve the other curve to find the intersections with (if
+     * the curve itself or {@code null} is passed, the self intersection of the
+     * curve is returned, if it exists)
+     * @return {CurveLocation[]} the locations of all intersections between the
+     * curves
      */
-    hasHandles: function() {
-        return !this.isStraight();
-    },
-
-    /**
-     * Checks whether the curve is straight, meaning it has no curve handles
-     * defined and thus appears as a line.
-     * Note that this is not the same as {@link #isLinear()}, which performs a
-     * full linearity check that includes handles running collinear to the line
-     * direction.
-     *
-     * @return {Boolean} {@true if the curve is straight}
-     * @see Segment#isStraight()
-     */
-    isStraight: function() {
-        return this._segment1._handleOut.isZero()
-                && this._segment2._handleIn.isZero();
-    },
-
-    /**
-     * Checks if this curve appears as a line. This can mean that it has no
-     * handles defined, or that the handles run collinear with the line.
-     *
-     * @return {Boolean} {@true if the curve is linear}
-     * @see Segment#isLinear()
-     * @see Path#isLinear()
-     */
-    isLinear: function() {
-        return Segment.isLinear(this._segment1, this._segment2);
-    },
-
-    /**
-     * Checks if the the two curves describe lines that are collinear, meaning
-     * they run in parallel.
-     *
-     * @param {Curve} the other curve to check against
-     * @return {Boolean} {@true if the two lines are collinear}
-     * @see Segment#isCollinear(segment)
-     */
-    isCollinear: function(curve) {
-        return Segment.isCollinear(this._segment1, this._segment2,
-                curve._segment1, curve._segment2);
-    },
-
-    /**
-     * Checks if the curve describes an orthogonal arc, as used in the
-     * construction of circles and ellipses.
-     *
-     * @return {Boolean} {@true if the curve describes an orthogonal arc}
-     * @see Segment#isOrthogonalArc()
-     */
-    isOrthogonalArc: function() {
-        return Segment.isOrthogonalArc(this._segment1, this._segment2);
-    },
-
-    // DOCS: Curve#getIntersections()
     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, [], {});
     },
 
     // TODO: adjustThroughPoint
@@ -392,54 +472,46 @@ var Curve = Base.extend(/** @lends Curve# */{
      * is within the valid range, {code null} otherwise.
      */
     // TODO: Rename to divideAt()?
-    divide: function(offset, isParameter, ignoreStraight) {
+    divide: function(offset, isParameter, _setHandles) {
         var parameter = this._getParameter(offset, isParameter),
-            tolerance = /*#=*/Numerical.TOLERANCE,
+            tMin = /*#=*/Numerical.CURVETIME_EPSILON,
+            tMax = 1 - tMin,
             res = null;
         // Only divide if not at the beginning or end.
-        if (parameter >= tolerance && parameter <= 1 - tolerance) {
+        if (parameter >= tMin && parameter <= tMax) {
             var parts = Curve.subdivide(this.getValues(), parameter),
-                setHandles = ignoreStraight || this.hasHandles(),
                 left = parts[0],
-                right = parts[1];
-
-            // Write back the results:
+                right = parts[1],
+                setHandles = _setHandles || this.hasHandles(),
+                segment1 = this._segment1,
+                segment2 = this._segment2,
+                path = this._path;
             if (setHandles) {
-                this._segment1._handleOut.set(left[2] - left[0],
-                        left[3] - left[1]);
-                // segment2 is the end segment. By inserting newSegment
-                // between segment1 and 2, 2 becomes the end segment.
+                // Adjust the handles on the existing segments. The new segment
+                // will be inserted between the existing segment1 and segment2:
                 // Convert absolute -> relative
-                this._segment2._handleIn.set(right[4] - right[6],
+                segment1._handleOut.set(left[2] - left[0],
+                        left[3] - left[1]);
+                segment2._handleIn.set(right[4] - right[6],
                         right[5] - right[7]);
             }
-
-            // Create the new segment, convert absolute -> relative:
+            // Create the new segment:
             var x = left[6], y = left[7],
                 segment = new Segment(new Point(x, y),
                         setHandles && new Point(left[4] - x, left[5] - y),
                         setHandles && new Point(right[2] - x, right[3] - y));
-
             // Insert it in the segments list, if needed:
-            if (this._path) {
-                // Insert at the end if this curve is a closing curve of a
-                // closed path, since otherwise it would be inserted at 0.
-                if (this._segment1._index > 0 && this._segment2._index === 0) {
-                    this._path.add(segment);
-                } else {
-                    this._path.insert(this._segment2._index, segment);
-                }
-                // The way Path#_add handles curves, this curve will always
-                // become the owner of the newly inserted segment.
-                // TODO: I expect this.getNext() to produce the correct result,
-                // but since we're inserting differently in _add (something
-                // linked with CurveLocation#divide()), this is not the case...
-                res = this; // this.getNext();
+            if (path) {
+                // By inserting at segment1.index + 1, we make sure to insert at
+                // the end if this curve is a closing curve of a closed path,
+                // as with segment2.index it would be inserted at 0.
+                path.insert(segment1._index + 1, segment);
+                // The newly inserted segment is the start of the next curve:
+                res = this.getNext();
             } else {
                 // otherwise create it from the result of split
-                var end = this._segment2;
                 this._segment2 = segment;
-                res = new Curve(segment, end);
+                res = new Curve(segment, segment2);
             }
         }
         return res;
@@ -473,50 +545,19 @@ var Curve = Base.extend(/** @lends Curve# */{
      *
      * @return {Curve} a reversed version of the curve
      */
-    reverse: function() {
-        return new Curve(this._segment2.reverse(), this._segment1.reverse());
+    reversed: function() {
+        return new Curve(this._segment2.reversed(), this._segment1.reversed());
     },
 
     /**
-     * Removes the curve from the path that it belongs to, by removing its
-     * second segment and merging its handle with the first segment.
-     * @return {Boolean} {@true if the curve was removed}
+     * Clears the curve's handles by setting their coordinates to zero,
+     * turning the curve into a straight line.
      */
-    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;
+    clearHandles: function() {
+        this._segment1._handleOut.set(0, 0);
+        this._segment2._handleIn.set(0, 0);
     },
 
-    /**
-     * Returns a copy of the curve.
-     *
-     * @return {Curve}
-     */
-    clone: function() {
-        return new Curve(this._segment1, this._segment2);
-    },
-
-    /**
-     * @return {String} a string representation of the curve
-     */
-    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(', ') + ' }';
-    },
-
-// Mess with indentation in order to get more line-space below...
 statics: {
     getValues: function(segment1, segment2, matrix) {
         var p1 = segment1._point,
@@ -572,57 +613,83 @@ statics: {
         return Numerical.solveCubic(a, b, c, p1 - val, roots, min, max);
     },
 
-    getParameterOf: function(v, x, y) {
-        // Handle beginnings and end separately, as they are not detected
-        // sometimes.
-        var tolerance = /*#=*/Numerical.TOLERANCE,
-            abs = Math.abs;
-        if (abs(v[0] - x) < tolerance && abs(v[1] - y) < tolerance)
-            return 0;
-        if (abs(v[6] - x) < tolerance && 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;
-        // sx, sy === -1 means infinite solutions:
-        // Loop through all solutions for x and match with solutions for y,
-        // to see if we either have a matching pair, or infinite solutions
-        // for one or the other.
-        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) {
-                        // Handle infinite solutions by assigning root of
-                        // the other polynomial
-                        if (sx === -1) {
-                            tx = ty;
-                        } else if (sy === -1) {
-                            ty = tx;
-                        }
-                        // Use average if we're within tolerance
-                        if (abs(tx - ty) < tolerance)
-                            return (tx + ty) * 0.5;
-                    }
-                }
-                // Avoid endless loops here: If sx is infinite and there was
-                // no fitting ty, there's no solution for this bezier
-                if (sx === -1)
-                    break;
+    getParameterOf: function(v, point) {
+        // Before solving cubics, compare the beginning and end of the curve
+        // with zero epsilon:
+        var p1 = new Point(v[0], v[1]),
+            p2 = new Point(v[6], v[7]),
+            epsilon = /*#=*/Numerical.EPSILON,
+            t = point.isClose(p1, epsilon) ? 0
+              : point.isClose(p2, epsilon) ? 1
+              : null;
+        if (t !== null)
+            return t;
+        // Solve the cubic for both x- and y-coordinates and consider all found
+        // solutions, testing with the larger / looser geometric epsilon.
+        var coords = [point.x, point.y],
+            roots = [],
+            geomEpsilon = /*#=*/Numerical.GEOMETRIC_EPSILON;
+        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 null;
+        // For very short curves (length ~ 1e-13), the above code will not
+        // necessarily produce any valid roots. As a fall-back, just check the
+        // beginnings and ends at the end so we can still return a valid result.
+        return point.isClose(p1, geomEpsilon) ? 0
+             : point.isClose(p2, geomEpsilon) ? 1
+             : null;
+    },
+
+    getNearestParameter: function(v, point) {
+        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);
+
+        // Now iteratively refine solution until we reach desired precision.
+        var step = 1 / (count * 2);
+        while (step > /*#=*/Numerical.CURVETIME_EPSILON) {
+            if (!refine(minT - step) && !refine(minT + step))
+                step /= 2;
+        }
+        return minT;
     },
 
     // TODO: Find better name
     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]; // [1] right
         // Interpolate the parameter at 'to' in the new curve and cut there.
         if (to < 1)
             v = Curve.subdivide(v, (to - from) / (1 - from))[0]; // [0] left
-        return v;
+        // Return reversed curve if from / to were flipped:
+        return flip
+                ? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]]
+                : v;
     },
 
     hasHandles: function(v) {
@@ -631,15 +698,6 @@ statics: {
                 && isZero(v[4] - v[6]) && isZero(v[5] - v[7]));
     },
 
-    isLinear: function(v) {
-        // See Segment#isLinear():
-        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) {
         // Thanks to Kaspar Fischer and Roger Willcocks for the following:
         // http://hcklbrrfnn.files.wordpress.com/2012/08/bez.pdf
@@ -656,29 +714,26 @@ statics: {
     },
 
     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];
+        // This is a combination of the methods to decide if a path is clockwise
+        // and to calculate the area, as described here:
         // http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
-        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) {
-        // Method derived from:
         // http://stackoverflow.com/questions/1165647
         // We treat the curve points and handles as the outline of a polygon of
         // which we determine the orientation using the method of calculating
         // the sum over the edges. This will work even with non-convex polygons,
-        // telling you whether it's mostly clockwise
-        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]);
+        // telling you whether it's mostly clockwise.
+        // With bezier curves, the trick appears to be to calculate edge sum
+        // with half the handles' lengths, and then:
+        // area = 6 * edge-sum / 10
+        var p1x = v[0], p1y = v[1],
+            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) {
@@ -718,7 +773,7 @@ statics: {
             // Add some tolerance for good roots, as t = 0, 1 are added
             // separately anyhow, and we don't want joins to be added with radii
             // in getStrokeBounds()
-            tMin = /*#=*/Numerical.TOLERANCE,
+            tMin = /*#=*/Numerical.CURVETIME_EPSILON,
             tMax = 1 - tMin;
         // Only add strokeWidth to bounds for points which lie  within 0 < t < 1
         // The corner cases for cap and join are handled in getStrokeBounds()
@@ -751,14 +806,18 @@ statics: {
             if (!bounds) {
                 // Calculate the curve bounds by passing a segment list for the
                 // curve to the static Path.get*Boudns methods.
-                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();
         };
     },
 /** @lends Curve# */{
     /**
+     * {@grouptitle Bounding Boxes}
+     *
      * The bounding rectangle of the curve excluding stroke width.
      *
      * @name Curve#bounds
@@ -787,6 +846,117 @@ statics: {
      * @type Rectangle
      * @ignore
      */
+}), Base.each({ // Injection scope for tests both as instance and static methods
+    isStraight: function(l, h1, h2) {
+        if (h1.isZero() && h2.isZero()) {
+            // No handles.
+            return true;
+        } else if (l.isZero()) {
+            // Zero-length line, with some handles defined.
+            return false;
+        } else if (h1.isCollinear(l) && h2.isCollinear(l)) {
+            // Collinear handles. Project them onto line to see if they are
+            // within the line's range:
+            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) {
+    // Produce the instance version that is called on curve object.
+    this[name] = function() {
+        var seg1 = this._segment1,
+            seg2 = this._segment2;
+        return test(seg2._point.subtract(seg1._point),
+                seg1._handleOut, seg2._handleIn);
+    };
+
+    // Produce the static version that handles a curve values array.
+    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));
+    };
+}, /** @lends Curve# */{
+    statics: {}, // Filled in the Base.each loop above.
+
+    /**
+     * {@grouptitle Curve Tests}
+     *
+     * Checks if this curve has any curve handles set.
+     *
+     * @return {Boolean} {@true if the curve has handles set}
+     * @see Curve#handle1
+     * @see Curve#handle2
+     * @see Segment#hasHandles()
+     * @see Path#hasHandles()
+     */
+    hasHandles: function() {
+        return !this._segment1._handleOut.isZero()
+                || !this._segment2._handleIn.isZero();
+    },
+
+    /**
+     * Checks if this curve appears as a straight line. This can mean that
+     * it has no handles defined, or that the handles run collinear with the
+     * line that connects the curve's start and end point, not falling
+     * outside of the line.
+     *
+     * @name Curve#isStraight
+     * @function
+     * @return {Boolean} {@true if the curve is straight}
+     */
+
+    /**
+     * Checks if this curve is parametrically linear, meaning that it is
+     * straight and its handles are positioned at 1/3 and 2/3 of the total
+     * length of the curve.
+     *
+     * @name Curve#isLinear
+     * @function
+     * @return {Boolean} {@true if the curve is parametrically linear}
+     */
+
+    /**
+     * Checks if the the two curves describe straight lines that are
+     * collinear, meaning they run in parallel.
+     *
+     * @param {Curve} curve the other curve to check against
+     * @return {Boolean} {@true if the two lines are collinear}
+     */
+    isCollinear: function(curve) {
+        return curve && this.isStraight() && curve.isStraight()
+                && this.getLine().isCollinear(curve.getLine());
+    },
+
+    /**
+     * Checks if the curve is a straight horizontal line.
+     *
+     * @return {Boolean} {@true if the line is horizontal}
+     */
+    isHorizontal: function() {
+        return this.isStraight() && Math.abs(this.getTangentAt(0.5, true).y)
+                < /*#=*/Numerical.TRIGONOMETRIC_EPSILON;
+    },
+
+    /**
+     * Checks if the curve is a straight vertical line.
+     *
+     * @return {Boolean} {@true if the line is vertical}
+     */
+    isVertical: function() {
+        return this.isStraight() && Math.abs(this.getTangentAt(0.5, true).x)
+                < /*#=*/Numerical.TRIGONOMETRIC_EPSILON;
+    }
 }), /** @lends Curve# */{
     // Explicitly deactivate the creation of beans, as we have functions here
     // that look like bean getters but actually read arguments.
@@ -794,6 +964,8 @@ statics: {
     beans: false,
 
     /**
+     * {@grouptitle Positions on Curves}
+     *
      * Calculates the curve time parameter of the specified offset on the path,
      * relative to the provided start parameter. If offset is a negative value,
      * the parameter is searched to the left of the start parameter. If no start
@@ -816,8 +988,7 @@ statics: {
      * @return {Number} the curve time parameter of the specified point
      */
     getParameterOf: function(/* point */) {
-        var point = Point.read(arguments);
-        return Curve.getParameterOf(this.getValues(), point.x, point.y);
+        return Curve.getParameterOf(this.getValues(), Point.read(arguments));
     },
 
     /**
@@ -861,38 +1032,30 @@ statics: {
         return loc ? loc.getOffset() : null;
     },
 
+    /**
+     * Returns the nearest location on the curve to the specified point.
+     *
+     * @function
+     * @param {Point} point the point for which we search the nearest location
+     * @return {CurveLocation} the location on the curve that's the closest to
+     * the specified point
+     */
     getNearestLocation: function(/* point */) {
         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);
-
-        // Now iteratively refine solution until we reach desired precision.
-        var step = 1 / (count * 2);
-        while (step > /*#=*/Numerical.TOLERANCE) {
-            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));
     },
 
+    /**
+     * Returns the nearest point on the curve to the specified point.
+     *
+     * @function
+     * @param {Point} point the point for which we search the nearest point
+     * @return {Point} the point on the curve that's the closest to the
+     * specified point
+     */
     getNearestPoint: function(/* point */) {
         return this.getNearestLocation.apply(this, arguments).getPoint();
     }
@@ -1037,12 +1200,13 @@ new function() { // Scope for methods that require private functions
             c1x = v[2], c1y = v[3],
             c2x = v[4], c2y = v[5],
             p2x = v[6], p2y = v[7],
-            tolerance = /*#=*/Numerical.TOLERANCE,
+            tMin = /*#=*/Numerical.CURVETIME_EPSILON,
+            tMax = 1 - tMin,
             x, y;
 
         // Handle special case at beginning / end of curve
-        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 {
@@ -1066,10 +1230,10 @@ new function() { // Scope for methods that require private functions
                 // the x and y coordinates:
                 // Prevent tangents and normals of length 0:
                 // http://stackoverflow.com/questions/10506868/
-                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 {
@@ -1080,8 +1244,7 @@ new function() { // Scope for methods that require private functions
                     // When the tangent at t is zero and we're at the beginning
                     // or the end, we can use the vector between the handles,
                     // but only when normalizing as its weighted length is 0.
-                    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;
                     }
@@ -1110,20 +1273,15 @@ new function() { // Scope for methods that require private functions
         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;
-            // See if the curve is linear by checking p1 == c1 and p2 == c2
-            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])) {
-                // Straight line
+            if (a === 0 && b === 1 && Curve.isStraight(v)) {
+                // The length of straight curves can be calculated more easily.
                 var dx = v[6] - v[0], // p2x - p1x
                     dy = v[7] - v[1]; // p2y - p1y
                 return Math.sqrt(dx * dx + dy * dy);
@@ -1139,8 +1297,7 @@ new function() { // Scope for methods that require private functions
                 return start;
             // See if we're going forward or backward, and handle cases
             // differently
-            var tolerance = /*#=*/Numerical.TOLERANCE,
-                abs = Math.abs,
+            var abs = Math.abs,
                 forward = offset > 0,
                 a = forward ? start : 0,
                 b = forward ? 1 : start,
@@ -1150,7 +1307,7 @@ new function() { // Scope for methods that require private functions
                 // Get length of total range
                 rangeLength = Numerical.integrate(ds, a, b,
                         getIterations(a, b));
-            if (abs(offset - rangeLength) < tolerance) {
+            if (abs(offset - rangeLength) < /*#=*/Numerical.EPSILON) {
                 // Matched the end:
                 return forward ? b : a;
             } else if (abs(offset) > rangeLength) {
@@ -1174,8 +1331,8 @@ new function() { // Scope for methods that require private functions
             }
             // Start with out initial guess for x.
             // NOTE: guess is a negative value when not looking forward.
-            return Numerical.findRoot(f, ds, start + guess, a, b, 16,
-                    tolerance);
+            return Numerical.findRoot(f, ds, start + guess, a, b, 32,
+                    /*#=*/Numerical.EPSILON);
         },
 
         getPoint: function(v, t) {
@@ -1201,20 +1358,63 @@ new function() { // Scope for methods that require private functions
         getCurvature: function(v, t) {
             return evaluate(v, t, 3, false).x;
         }
-    };
-}, new function() { // Scope for intersection using bezier fat-line clipping
-    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);
-        } else {
-            loc = null;
+    }};
+},
+new function() { // Scope for intersection using bezier fat-line clipping
+
+    function addLocation(locations, param, v1, c1, t1, p1, v2, c2, t2, p2,
+            overlap) {
+        var startConnected = param.startConnected,
+            endConnected = param.endConnected,
+            tMin = /*#=*/Numerical.CURVETIME_EPSILON,
+            tMax = 1 - tMin;
+        if (t1 == null)
+            t1 = Curve.getParameterOf(v1, p1);
+        // Check t1 and t2 against correct bounds, based on start-/endConnected:
+        // - startConnected means the start of c1 connects to the end of c2
+        // - endConneted means the end of c1 connects to the start of c2
+        // - If either c1 or c2 are at the end of the path, exclude their end,
+        //   which connects back to the beginning, but only if it's not part of
+        //   a found overlap. The normal intersection will already be found at
+        //   the beginning, and would be added twice otherwise.
+        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)) {
+                // TODO: Don't we need to check the range of t2 as well? Does it
+                // also need startConnected / endConnected values?
+                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),
+                    // For self-intersections, detect the case where the second
+                    // curve wrapped around, and flip them so they can get
+                    // matched to a potentially already existing intersection.
+                    flip = loc1.getPath() === loc2.getPath()
+                        && loc1.getIndex() > loc2.getIndex(),
+                    loc = flip ? loc2 : loc1,
+                    include = param.include;
+                // Link the two locations to each other.
+                loc1._intersection = loc2;
+                loc2._intersection = loc1;
+                // TODO: Remove this once debug logging is removed.
+                (flip ? loc1 : loc2)._other = true;
+                if (!include || include(loc)) {
+                    CurveLocation.insert(locations, loc, true);
+                }
+            }
         }
-        return loc;
     }
 
-    function addCurveIntersections(v1, v2, curve1, curve2, locations, include,
+    function addCurveIntersections(v1, v2, c1, c2, locations, param,
             tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) {
         // Avoid deeper recursion.
         // NOTE: @iconexperience determined that more than 20 recursions are
@@ -1222,16 +1422,16 @@ new function() { // Scope for methods that require private functions
         // below when determining which curve converges the least. He also
         // recommended a threshold of 0.5 instead of the initial 0.8
         // See: https://github.com/paperjs/paper.js/issues/565
-        if (recursion > 32)
+        if (++recursion >= 24)
             return;
         // Let P be the first curve and Q be the second
         var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7],
-            tolerance = /*#=*/Numerical.TOLERANCE,
+            epsilon = /*#=*/(Numerical.CURVETIME_EPSILON / 10),
             getSignedDistance = Line.getSignedDistance,
             // Calculate the fat-line L for Q is the baseline l and two
             // offsets which completely encloses the curve P.
-            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),
@@ -1242,35 +1442,25 @@ new function() { // Scope for methods that require private functions
             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) {
-            // The fatline of Q has converged to a point, the clipping is not
-            // reliable. Return the value we have even though we will miss the
-            // precision.
-            tMaxNew = tMinNew = (tMax + tMin) / 2;
-            tDiff = 0;
-        } else {
             // Get the top and bottom parts of the convex-hull
-            var hull = getConvexHull(dp0, dp1, dp2, dp3),
-                top = hull[0],
-                bottom = hull[1],
-                tMinClip, tMaxClip;
-            // Clip the convex-hull with dMin and dMax
-            tMinClip = clipConvexHull(top, bottom, dMin, dMax);
-            top.reverse();
-            bottom.reverse();
-            tMaxClip = clipConvexHull(top, bottom, dMin, dMax);
-            // No intersections if one of the tvalues are null or 'undefined'
-            if (tMinClip == null || tMaxClip == null)
-                return;
-            // Clip P with the fatline for Q
-            v1 = Curve.getPart(v1, tMinClip, tMaxClip);
-            tDiff = tMaxClip - tMinClip;
+            hull = getConvexHull(dp0, dp1, dp2, dp3),
+            top = hull[0],
+            bottom = hull[1],
+            tMinClip,
+            tMaxClip;
+        // Clip the convex-hull with dMin and dMax, taking into account that
+        // there will be no intersections if one of the tvalues are null.
+        if ((tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null ||
+            (tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(),
+                dMin, dMax)) == null)
+            return;
+        // Clip P with the fat-line for Q
+        v1 = Curve.getPart(v1, tMinClip, tMaxClip);
+        var tDiff = tMaxClip - tMinClip,
             // tMin and tMax are within the range (0, 1). We need to project it
             // to the original parameter range for v2.
-            tMinNew = tMax * tMinClip + tMin * (1 - tMinClip);
-            tMaxNew = tMax * tMaxClip + tMin * (1 - tMaxClip);
-        }
+            tMinNew = tMin + (tMax - tMin) * tMinClip,
+            tMaxNew = tMin + (tMax - tMin) * tMaxClip;
         // Check if we need to subdivide the curves
         if (oldTDiff > 0.5 && tDiff > 0.5) {
             // Subdivide the curve which has converged the least.
@@ -1278,37 +1468,38 @@ new function() { // Scope for methods that require private functions
                 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) < epsilon) {
             // We have isolated the intersection with sufficient precision
             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) { // Iterate
-            addCurveIntersections(v2, v1, curve2, curve1, locations, include,
-                    uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, ++recursion);
+            // Since we've been chopping up v1 and v2, we need to pass on the
+            // original full curves here again to match the parameter space of
+            // t1 and t2.
+            // TODO: Add two more arguments to addCurveIntersections after param
+            // to pass on the sub-curves.
+            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 > /*#=*/Numerical.EPSILON) { // Iterate
+            addCurveIntersections(v2, v1, c2, c1, locations, param,
+                    uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
         }
     }
 
@@ -1331,54 +1522,38 @@ new function() { // Scope for methods that require private functions
             p1 = [ 1 / 3, dq1 ],
             p2 = [ 2 / 3, dq2 ],
             p3 = [ 1, dq3 ],
-            // Find signed distance of p1 and p2 from line [ p0, p3 ]
-            getSignedDistance = Line.getSignedDistance,
-            dist1 = getSignedDistance(0, dq0, 1, dq3, 1 / 3, dq1),
-            dist2 = getSignedDistance(0, dq0, 1, dq3, 2 / 3, dq2),
-            flip = false,
+            // Find vertical signed distance of p1 and p2 from line [p0, p3]
+            dist1 = dq1 - (2 * dq0 + dq3) / 3,
+            dist2 = dq2 - (dq0 + 2 * dq3) / 3,
             hull;
-        // Check if p1 and p2 are on the same side of the line [ p0, p3 ]
+        // Check if p1 and p2 are on the opposite side of the line [p0, p3]
         if (dist1 * dist2 < 0) {
-            // p1 and p2 lie on different sides of [ p0, p3 ]. The hull is a
-            // quadrilateral and line [ p0, p3 ] is NOT part of the hull so we
-            // are pretty much done here.
-            // The top part includes p1,
-            // we will reverse it later if that is not the case
+            // p1 and p2 lie on different sides of [p0, p3]. The hull is a
+            // quadrilateral and line [p0, p3] is NOT part of the hull so we are
+            // pretty much done here. The top part includes p1, we will reverse
+            // it later if that is not the case.
             hull = [[p0, p1, p3], [p0, p2, p3]];
-            flip = dist1 < 0;
         } else {
-            // p1 and p2 lie on the same sides of [ p0, p3 ]. The hull can be
-            // a triangle or a quadrilateral and line [ p0, p3 ] is part of the
-            // hull. Check if the hull is a triangle or a quadrilateral.
-            // Also, if at least one of the distances for p1 or p2, from line
-            // [p0, p3] is zero then hull must at most have 3 vertices.
-            var pmax, cross = 0,
-                distZero = dist1 === 0 || dist2 === 0;
-            if (Math.abs(dist1) > Math.abs(dist2)) {
-                pmax = p1;
-                // apex is dq3 and the other apex point is dq0 vector dqapex ->
-                // dqapex2 or base vector which is already part of the hull.
-                cross = (dq3 - dq2 - (dq3 - dq0) / 3)
-                        * (2 * (dq3 - dq2) - dq3 + dq1) / 3;
-            } else {
-                pmax = p2;
-                // apex is dq0 in this case, and the other apex point is dq3
-                // vector dqapex -> dqapex2 or base vector which is already part
-                // of the hull.
-                cross = (dq1 - dq0 + (dq0 - dq3) / 3)
-                        * (-2 * (dq0 - dq1) + dq0 - dq2) / 3;
-            }
-            // Compare cross products of these vectors to determine if the point
-            // is in the triangle [ p3, pmax, p0 ], or if it is a quadrilateral.
-            hull = cross < 0 || distZero
-                    // p2 is inside the triangle, hull is a triangle.
-                    ? [[p0, pmax, p3], [p0, p3]]
-                    // Convex hull is a quadrilateral and we need all lines in
-                    // correct order where line [ p0, p3 ] is part of the hull.
-                    : [[p0, p1, p2, p3], [p0, p3]];
-            flip = dist1 ? dist1 < 0 : dist2 < 0;
+            // p1 and p2 lie on the same sides of [p0, p3]. The hull can be a
+            // triangle or a quadrilateral and line [p0, p3] is part of the
+            // hull. Check if the hull is a triangle or a quadrilateral. We have
+            // a triangle if the vertical distance of one of the middle points
+            // (p1, p2) is equal or less than half the vertical distance of the
+            // other middle point.
+            var distRatio = dist1 / dist2;
+            hull = [
+                // p2 is inside, the hull is a triangle.
+                distRatio >= 2 ? [p0, p1, p3]
+                // p1 is inside, the hull is a triangle.
+                : distRatio <= .5 ? [p0, p2, p3]
+                // Hull is a quadrilateral, we need all lines in correct order.
+                : [p0, p1, p2, p3],
+                // Line [p0, p3] is part of the hull.
+                [p0, p3]
+            ];
         }
-        return flip ? hull.reverse() : hull;
+        // Flip hull if dist1 is negative or if it is zero and dist2 is negative
+        return (dist1 || dist2) < 0 ? hull.reverse() : hull;
     }
 
     /**
@@ -1405,8 +1580,10 @@ new function() { // Scope for methods that require private functions
         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;
         }
@@ -1420,9 +1597,8 @@ new function() { // Scope for methods that require private functions
      * line is on the X axis, and solve the implicit equations for the X axis
      * and the curve.
      */
-    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],
@@ -1435,9 +1611,6 @@ new function() { // Scope for methods that require private functions
             sin = Math.sin(angle),
             cos = Math.cos(angle),
             // (rlx1, rly1) = (0, 0)
-            rlx2 = ldx * cos - ldy * sin,
-            // The curve values for the rotated line.
-            rvl = [0, 0, 0, 0, rlx2, 0, rlx2, 0],
             // Calculate the curve values of the rotated curve.
             rvc = [];
         for(var i = 0; i < 8; i += 2) {
@@ -1445,222 +1618,288 @@ new function() { // Scope for methods that require private functions
                 y = vc[i + 1] - ly1;
             rvc.push(
                 x * cos - y * sin,
-                y * cos + x * sin);
+                x * sin + y * cos);
         }
+        // Solve it for y = 0. We need to include t = 0, 1 and let addLocation()
+        // do the filtering, to catch important edge cases.
         var roots = [],
             count = Curve.solveCubic(rvc, 1, 0, roots, 0, 1);
         // NOTE: count could be -1 for infinite solutions, but that should only
         // happen with lines, in which case we should not be here.
         for (var i = 0; i < count; i++) {
+            // For each found solution on the rotated curve, get the point on
+            // the real curve and with that the location on the line.
             var tc = roots[i],
-                x = Curve.getPoint(rvc, tc).x;
-            // We do have a point on the infinite line. Check if it falls on
-            // the line *segment*.
-            if (x >= 0 && x <= rlx2) {
-                // Find the parameter of the intersection on the rotated line.
-                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(
-                v1[0], v1[1], v1[6], v1[7],
-                v2[0], v2[1], v2[6], v2[7]);
-        if (point) {
-            // We need to return the parameters for the intersection,
-            // since they will be used for sorting
-            var x = point.x,
-                y = point.y;
-            addLocation(locations, include,
-                    curve1, Curve.getParameterOf(v1, x, y), point,
-                    curve2, Curve.getParameterOf(v2, x, y), point);
-        }
-    }
-
-    /**
-     * Code to detect overlaps of intersecting curves by @iconexperience:
-     * https://github.com/paperjs/paper.js/issues/648
-     */
-    function addOverlap(v1, v2, curve1, curve2, locations, include) {
-        var abs = Math.abs,
-            tolerance = /*#=*/Numerical.TOLERANCE,
-            epsilon = /*#=*/Numerical.EPSILON,
-            linear1 = Curve.isLinear(v1),
-            linear2 = Curve.isLinear(v2),
-            linear =  linear1 && linear2;
-        if (linear) {
-            // Linear curves can only overlap if they are collinear, which means
-            // they must be are collinear and any point of curve 1 must be on
-            // curve 2
-            var line1 = new Line(v1[0], v1[1], v1[6], v1[7], false),
-                line2 = new Line(v2[0], v2[1], v2[6], v2[7], false);
-            if (!line1.isCollinear(line2) ||
-                    line1.getDistance(line2.getPoint()) > epsilon)
-                return false;
-        } else if (linear1 ^ linear2) {
-            // If one curve is linear, the other curve must be linear, too,
-            // otherwise they cannot overlap.
-            return false;
-        }
-        var v = [v1, v2],
-            pairs = [];
-        // Iterate through all end points: First p1 and p2 of curve 1,
-        // then p1 and p2 of curve 2
-        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],
-                    v[i][t1 === 0 ? 0 : 6],
-                    v[i][t1 === 0 ? 1 : 7]);
-            if (t2 != null) {  // If point is on curve
-                var pair = i === 0 ? [t1, t2] : [t2, t1];
-                if (pairs.length === 1 && pair[0] < pairs[0][0]) {
-                    pairs.unshift(pair);
-                } else if (pairs.length === 0
-                        || abs(pair[0] - pairs[0][0]) > tolerance
-                        || abs(pair[1] - pairs[0][1]) > tolerance) {
-                    pairs.push(pair);
+                // If the two curves are connected and the 2nd is very short,
+                // (l < Numerical.GEOMETRIC_EPSILON), we need to filter out an
+                // invalid intersection at the beginning of this short curve.
+                if (!param.endConnected || t2 > Numerical.CURVETIME_EPSILON) {
+                    addLocation(locations, param,
+                            v1, c1, t1, flip ? pl : pc,
+                            v2, c2, t2, flip ? pc : pl);
                 }
             }
-            // If we checked 3 points but found no match, curves cannot overlap
-            if (i === 1 && pairs.length === 0)
-                return false;
         }
-        // If we found 2 pairs, the end points of v1 & v2 should be the same.
-        // We only have to check if the handles are the same, too.
-        if (pairs.length === 2) {
-            // create values for overlapping part of each curve
-            var c1 = Curve.getPart(v[0], pairs[0][0], pairs[1][0]),
-                c2 = Curve.getPart(v[1], Math.min(pairs[0][1], pairs[1][1]),
-                        Math.max(pairs[0][1], pairs[1][1]));
-            // Reverse values of second curve if necessary
-            // if (abs(c1[0] - c2[6]) < epsilon && abs(c1[1] - c2[7]) < epsilon) {
-            if (pairs[0][1] > pairs[1][1]) {
-                c2 = [c2[6], c2[7], c2[4], c2[5], c2[2], c2[3], c2[0], c2[1]];
-            }
-            // Check if handles of overlapping paths are similar enough.
-            // We could do another check for curve identity here if we find a
-            // better criteria.
-            if (linear ||
-                    abs(c2[0] - c1[0]) < epsilon &&
-                    abs(c2[1] - c1[1]) < epsilon &&
-                    abs(c2[1] - c1[1]) < epsilon &&
-                    abs(c2[3] - c1[3]) < epsilon &&
-                    abs(c2[2] - c1[2]) < epsilon &&
-                    abs(c2[5] - c1[5]) < epsilon &&
-                    abs(c2[3] - c1[3]) < epsilon &&
-                    abs(c2[7] - c1[7]) < epsilon) {
-                // Overlapping parts are identical
-                var t11 = pairs[0][0],
-                    t12 = pairs[0][1],
-                    t21 = pairs[1][0],
-                    t22 = pairs[1][1],
-                    loc1 = addLocation(locations, include,
-                        curve1, t11, Curve.getPoint(v1, t11),
-                        curve2, t12, Curve.getPoint(v2, t12), true),
-                    loc2 = addLocation(locations, include,
-                        curve1, t21, Curve.getPoint(v1, t21),
-                        curve2, t22, Curve.getPoint(v2, t22), true);
-                if (loc1)
-                    loc1._overlap = true;
-                if (loc2)
-                    loc2._overlap = true;
-                return true;
-            }
+    }
+
+    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 (pt) {
+            addLocation(locations, param, v1, c1, null, pt, v2, c2, null, pt);
         }
-        return false;
     }
 
     return { statics: /** @lends Curve */{
-        // We need to provide the original left curve reference to the
-        // #getIntersections() calls as it is required to create the resulting
-        // CurveLocation objects.
-        getIntersections: function(v1, v2, c1, c2, locations, include) {
-            if (addOverlap(v1, v2, c1, c2, locations, include))
+        _getIntersections: function(v1, v2, c1, c2, locations, param) {
+            if (!v2) {
+                // If v2 is not provided, search for self intersection on v1.
+                return Curve._getSelfIntersection(v1, c1, locations, param);
+            }
+            // Avoid checking curves if completely out of control bounds. As
+            // a little optimization, we can scale the handles with 0.75
+            // before calculating the control bounds and still be sure that
+            // the curve is fully contained.
+            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],
+                // 's' stands for scaled handles...
+                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;
-            var linear1 = Curve.isLinear(v1),
-                linear2 = Curve.isLinear(v2),
-                c1p1 = c1.getPoint1(),
-                c1p2 = c1.getPoint2(),
-                c2p1 = c2.getPoint1(),
-                c2p2 = c2.getPoint2(),
-                tolerance = /*#=*/Numerical.TOLERANCE;
-            // Handle a special case where if both curves start or end at the
-            // same point, the same end-point case will be handled after we
-            // calculate other intersections within the curve.
-            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);
-            // Determine the correct intersection method based on values of
-            // linear1 & 2:
-            (linear1 && linear2
+            // Now detect and handle overlaps:
+            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,
+                // NOTE: Use smaller Numerical.EPSILON to compare beginnings and
+                // end points to avoid matching them on almost collinear lines,
+                // see: https://github.com/paperjs/paper.js/issues/777
+                epsilon = /*#=*/Numerical.EPSILON,
+                before = locations.length;
+            // Determine the correct intersection method based on whether one or
+            // curves are straight lines:
+            (straight
                 ? addLineIntersection
-                : linear1 || linear2
+                : straight1 || straight2
                     ? addCurveLineIntersections
                     : addCurveIntersections)(
-                        v1, v2, c1, c2, locations, include,
+                        v1, v2, c1, c2, locations, param,
                         // Define the defaults for these parameters of
                         // addCurveIntersections():
                         // tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion
                         0, 1, 0, 1, 0, false, 0);
-            // Handle the special case where c1's end-point overlap with
-            // c2's points.
-            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);
+            // We're done if we handle lines and found one intersection already:
+            // https://github.com/paperjs/paper.js/issues/805#issuecomment-148503018
+            if (straight && locations.length > before)
+                return locations;
+            // Handle the special case where the first curve's start- or end-
+            // point overlaps with the second curve's start or end-point.
+            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 - /*#=*/Numerical.TOLERANCE;
-            // Merge intersections very close to the end of a curve to the
-            // beginning of the next curve.
-            for (var i = last; i >= 0; i--) {
-                var loc = locations[i];
-                if (loc._parameter >= tMax && (next = loc._curve.getNext())) {
-                    loc._parameter = 0;
-                    loc._curve = next;
-                }
-                if (loc._parameter2 >= tMax && (next = loc._curve2.getNext())) {
-                    loc._parameter2 = 0;
-                    loc._curve2 = next;
-                }
+        _getSelfIntersection: function(v1, c1, locations, param) {
+            // Read a detailed description of the approach used to handle self-
+            // intersection, developed by @iconexperience here:
+            // https://github.com/paperjs/paper.js/issues/773#issuecomment-144018379
+            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];
+            // Get the side of both control handles
+            var line = new Line(p1x, p1y, p2x, p2y, false),
+                side1 = line.getSide(h1x, h1y),
+                side2 = Line.getSide(h2x, h2y);
+            if (side1 === side2) {
+                var edgeSum = (p1x - h2x) * (h1y - p2y)
+                            + (h1x - p2x) * (h2y - p1y);
+                // If both handles are on the same side, the curve can only have
+                // a self intersection if the edge sum and the handles' sides
+                // have different signs. If the handles are on the left side,
+                // the edge sum must be negative for a self intersection (and
+                // vice-versa).
+                if (edgeSum * side1 > 0)
+                    return locations;
             }
-
-            if (last > 0) {
-                CurveLocation.sort(locations);
-                // Filter out duplicate locations, but preserve _overlap setting
-                // among all duplicated (only one of them will have it defined).
-                var i = last,
-                    loc = locations[i];
-                while(--i >= 0) {
-                    var prev = locations[i];
-                    if (prev.equals(loc)) {
-                        locations.splice(i + 1, 1); // Remove loc.
-                        // Preserve overlap setting.
-                        var overlap = loc._overlap;
-                        if (overlap)
-                            prev._overlap = overlap;
-                        last--;
+            // As a second condition we check if the curve has an inflection
+            // point. If an inflection point exists, the curve cannot have a
+            // self intersection.
+            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,
+                // Condition for 1 or 2 inflection points:
+                // (ay*cx-ax*cy)^2 - 4*(ay*bx-ax*by)*(by*cx-bx*cy) >= 0
+                ac = ay * cx - ax * cy,
+                ab = ay * bx - ax * by,
+                bc = by * cx - bx * cy;
+            if (ac * ac - 4 * ab * bc < 0) {
+                // The curve has no inflection points, so it may have a self
+                // intersection. Find the right parameter at which to split the
+                // curve. We search for the parameter where the velocity has an
+                // extremum by finding the roots of the cross product between
+                // the bezier curve's first and second derivative.
+                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) {
+                    // Select extremum with highest curvature. This is always on
+                    // the loop in case of a self intersection.
+                    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];
+                        }
                     }
-                    loc = prev;
+                    // Divide the curve in two and then apply the normal curve
+                    // intersection code.
+                    var parts = Curve.subdivide(v1, tSplit);
+                    // After splitting, the end is always connected:
+                    param.endConnected = true;
+                    // Since the curve was split above, we need to adjust the
+                    // parameters for both locations.
+                    param.renormalize = function(t1, t2) {
+                        return [t1 * tSplit, t2 * (1 - tSplit) + tSplit];
+                    };
+                    Curve._getIntersections(parts[0], parts[1], c1, c1,
+                            locations, param);
                 }
             }
-            if (expand) {
-                for (var i = last; i >= 0; i--)
-                    locations.push(locations[i].getIntersection());
-                CurveLocation.sort(locations);
-            }
             return locations;
+        },
+
+        /**
+         * Code to detect overlaps of intersecting curves by @iconexperience:
+         * https://github.com/paperjs/paper.js/issues/648
+         */
+        getOverlaps: function(v1, v2) {
+            var abs = Math.abs,
+                timeEpsilon = /*#=*/Numerical.CURVETIME_EPSILON,
+                geomEpsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
+                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) {
+                // Linear curves can only overlap if they are collinear. Instead
+                // of using the #isCollinear() check, we pick the longer of the
+                // two lines and see how far the starting and end points of the
+                // other line are from this line (assumed as an infinite line).
+                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) {
+                // If one curve is straight, the other curve must be straight,
+                // too, otherwise they cannot overlap.
+                return null;
+            }
+
+            var v = [v1, v2],
+                pairs = [];
+            // Iterate through all end points: First p1 and p2 of curve 1,
+            // then p1 and p2 of curve 2
+            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) {  // If point is on curve
+                    var pair = i === 0 ? [t1, t2] : [t2, t1];
+                    // Filter out tiny overlaps
+                    // TODO: Compare distance of points instead of curve time?
+                    if (pairs.length === 0 ||
+                        abs(pair[0] - pairs[0][0]) > timeEpsilon &&
+                        abs(pair[1] - pairs[0][1]) > timeEpsilon)
+                        pairs.push(pair);
+                }
+                // If we checked 3 points but found no match, curves cannot
+                // overlap
+                if (i === 1 && pairs.length === 0)
+                    break;
+            }
+            if (pairs.length !== 2) {
+                pairs = null;
+            } else if (!straight) {
+                // Straight pairs don't need further checks. If we found 2 pairs
+                // the end points on v1 & v2 should be the same.
+                var o1 = Curve.getPart(v1, pairs[0][0], pairs[1][0]),
+                    o2 = Curve.getPart(v2, pairs[0][1], pairs[1][1]);
+                // Check if handles of the overlapping curves are the same too.
+                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;
         }
     }};
 });
diff --git a/src/path/CurveLocation.js b/src/path/CurveLocation.js
index 504f5a23..fccd561d 100644
--- a/src/path/CurveLocation.js
+++ b/src/path/CurveLocation.js
@@ -13,16 +13,16 @@
 /**
  * @name CurveLocation
  *
- * @class CurveLocation objects describe a location on {@link Curve}
- * objects, as defined by the curve {@link #parameter}, a value between
- * {@code 0} (beginning of the curve) and {@code 1} (end of the curve). If
- * the curve is part of a {@link Path} item, its {@link #index} inside the
+ * @class CurveLocation objects describe a location on {@link Curve} objects,
+ * as defined by the curve-time {@link #parameter}, a value between {@code 0}
+ * (beginning of the curve) and {@code 1} (end of the curve). If the curve is
+ * part of a {@link Path} item, its {@link #index} inside the
  * {@link Path#curves} array is also provided.
  *
  * The class is in use in many places, such as
- * {@link Path#getLocationAt(offset, isParameter)},
+ * {@link Path#getLocationAt(offset)},
  * {@link Path#getLocationOf(point)},
- * {@link Path#getNearestLocation(point),
+ * {@link Path#getNearestLocation(point)},
  * {@link PathItem#getIntersections(path)},
  * etc.
  */
@@ -41,22 +41,35 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
      * @param {Number} parameter
      * @param {Point} [point]
      */
-    initialize: function CurveLocation(curve, parameter, point, _curve2,
-            _parameter2, _point2, _distance) {
+    initialize: function CurveLocation(curve, parameter, point,
+            _overlap, _distance) {
+        // Merge intersections very close to the end of a curve with the
+        // beginning of the next curve.
+        if (parameter > /*#=*/(1 - Numerical.CURVETIME_EPSILON)) {
+            var next = curve.getNext();
+            if (next) {
+                parameter = 0;
+                curve = next;
+            }
+        }
         // Define this CurveLocation's unique id.
         // NOTE: We do not use the same pool as the rest of the library here,
         // since this is only required to be unique at runtime among other
         // CurveLocation objects.
         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; // To be determined, see #getSegment()
         // Also store references to segment1 and segment2, in case path
         // splitting / dividing is going to happen, in which case the segments
         // can be used to determine the new curves, see #getCurve(true)
@@ -64,31 +77,40 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
         this._segment2 = curve._segment2;
     },
 
+    _setSegment: function(segment) {
+        this._setCurve(segment.getCurve());
+        this._segment = segment;
+        this._parameter = segment === this._segment1 ? 0 : 1;
+        // To avoid issues with imprecision in getCurve() / trySegment()
+        this._point = segment._point.clone();
+    },
+
     /**
      * The segment of the curve which is closer to the described location.
      *
      * @type Segment
      * @bean
      */
-    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 {
+    getSegment: function() {
+        // Request curve first, so _segment gets invalidated if it's out of sync
+        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) {
                 // Determine the closest segment by comparing curve lengths
-                this._segment = curve.getPartLength(0, parameter)
+                segment = curve.getPartLength(0, parameter)
                     < curve.getPartLength(parameter, 1)
                         ? curve._segment1
                         : curve._segment2;
             }
+            this._segment = segment;
         }
-        return this._segment;
+        return segment;
     },
 
     /**
@@ -99,29 +121,34 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
      */
     getCurve: function() {
         var curve = this._curve,
-            path = curve && curve._path;
+            path = curve && curve._path,
+            that = this;
         if (path && path._version !== this._version) {
             // If the path's segments have changed in the meantime, clear the
             // internal _parameter value and force refetching of the correct
             // curve again here.
-            curve = null;
-            this._parameter = null;
+            curve = this._parameter = this._curve = this._offset = null;
         }
-        if (!curve) {
-            // If we're asked to get the curve uncached, access current curve
-            // objects through segment1 / segment2. Since path splitting or
-            // dividing might have happened in the meantime, try segment1's
-            // curve, and see if _point lies on it still, otherwise assume it's
-            // the curve before segment2.
-            curve = this._segment1.getCurve();
-            if (curve.getParameterOf(this._point) == null)
-                curve = this._segment2.getPrevious().getCurve();
-            this._curve = curve;
-            // Fetch path again as it could be on a new one through split()
-            path = curve._path;
-            this._version = path ? path._version : 0;
+
+        // If path is out of sync, access current curve objects through segment1
+        // / segment2. Since path splitting or dividing might have happened in
+        // the meantime, try segment1's curve, and see if _point lies on it
+        // still, otherwise assume it's the curve before segment2.
+        function trySegment(segment) {
+            var curve = segment && segment.getCurve();
+            if (curve && (that._parameter = curve.getParameterOf(that._point))
+                    != null) {
+                // Fetch path again as it could be on a new one through split()
+                that._setCurve(curve);
+                that._segment = segment;
+                return curve;
+            }
         }
-        return curve;
+
+        return curve
+            || trySegment(this._segment)
+            || trySegment(this._segment1)
+            || trySegment(this._segment2.getPrevious());
     },
 
     /**
@@ -136,8 +163,8 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
     },
 
     /**
-     * The index of the curve within the {@link Path#curves} list, if the
-     * curve is part of a {@link Path} item.
+     * The index of the {@link #curve} within the {@link Path#curves} list, if
+     * it is part of a {@link Path} item.
      *
      * @type Index
      * @bean
@@ -148,9 +175,9 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
     },
 
     /**
-     * The curve parameter, as used by various bezier curve calculations. It is
-     * value between {@code 0} (beginning of the curve) and {@code 1} (end of
-     * the curve).
+     * The curve-time parameter, as used by various bezier curve calculations.
+     * It is value between {@code 0} (beginning of the curve) and {@code 1}
+     * (end of the curve).
      *
      * @type Number
      * @bean
@@ -183,8 +210,13 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
      * @bean
      */
     getOffset: function() {
-        var path = this.getPath();
-        return path ? path._getOffset(this) : this.getCurveOffset();
+        var offset = this._offset;
+        if (offset == null) {
+            var path = this.getPath();
+            offset = this._offset = path ? path._getOffset(this)
+                    : this.getCurveOffset();
+        }
+        return offset;
     },
 
     /**
@@ -209,37 +241,31 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
      * @bean
      */
     getIntersection: function() {
-        var intersection = this._intersection;
-        if (!intersection && this._curve2) {
-            // If we have the parameter on the other curve use that for
-            // intersection rather than the point.
-            this._intersection = intersection = new CurveLocation(this._curve2,
-                    this._parameter2, this._point2 || this._point);
-            intersection._overlap = this._overlap;
-            intersection._intersection = this;
-        }
-        return intersection;
+        return this._intersection;
     },
 
     /**
      * The tangential vector to the {@link #curve} at the given location.
      *
-     * @name Item#tangent
+     * @name CurveLocation#getTangent
      * @type Point
+     * @bean
      */
 
     /**
      * The normal vector to the {@link #curve} at the given location.
      *
-     * @name Item#normal
+     * @name CurveLocation#getNormal
      * @type Point
+     * @bean
      */
 
     /**
      * The curvature of the {@link #curve} at the given location.
      *
-     * @name Item#curvature
+     * @name CurveLocation#getCurvature
      * @type Number
+     * @bean
      */
 
     /**
@@ -274,23 +300,40 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
      * @param {CurveLocation} location
      * @return {Boolean} {@true if the locations are equal}
      */
-    equals: function(loc) {
-        var abs = Math.abs,
-            // Use the same tolerance for curve time parameter comparisons as
-            // in Curve.js when considering two locations the same.
-            tolerance = /*#=*/Numerical.TOLERANCE;
-        return this === loc
-                || loc instanceof CurveLocation
-                    // Call getCurve() and getParameter() to keep in sync
-                    && this.getCurve() === loc.getCurve()
-                    && abs(this.getParameter() - loc.getParameter()) < tolerance
-                    // _curve2/_parameter2 are only used for Boolean operations
-                    // and don't need syncing there.
-                    // TODO: That's not quite true though... Rework this!
-                    && this._curve2 === loc._curve2
-                    && abs((this._parameter2 || 0) - (loc._parameter2 || 0))
-                            < tolerance
-                || false;
+    equals: function(loc, _ignoreOther) {
+        var res = this === loc,
+            epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON;
+        // NOTE: We need to compare both by (index + parameter) and by proximity
+        // of points. See:
+        // https://github.com/paperjs/paper.js/issues/784#issuecomment-143161586
+        if (!res && loc instanceof CurveLocation
+                && this.getPath() === loc.getPath()
+                && this.getPoint().isClose(loc.getPoint(), epsilon)) {
+            // The position is the same, but it could still be in a different
+            // location on the path. Perform more thorough checks now:
+            var c1 = this.getCurve(),
+                c2 = loc.getCurve(),
+                abs = Math.abs,
+                // We need to wrap diff around the path's beginning / end:
+                diff = abs(
+                    ((c1.isLast() && c2.isFirst() ? -1 : c1.getIndex())
+                            + this.getParameter()) -
+                    ((c2.isLast() && c1.isFirst() ? -1 : c2.getIndex())
+                            + loc.getParameter()));
+            res = (diff < /*#=*/Numerical.CURVETIME_EPSILON
+                // If diff isn't close enough, compare the actual offsets of
+                // both locations to determine if they're in the same spot,
+                // taking into account the wrapping around path ends too.
+                // This is necessary in order to handle very short consecutive
+                // curves (length ~< 1e-7), which would lead to diff > 1.
+                || ((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;
     },
 
     /**
@@ -313,37 +356,207 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
         return '{ ' + parts.join(', ') + ' }';
     },
 
-    statics: {
-        sort: function(locations) {
-            var tolerance = /*#=*/Numerical.TOLERANCE;
-            locations.sort(function compare(l1, l2) {
-                var curve1 = l1._curve,
-                    curve2 = l2._curve,
-                    path1 = curve1._path,
-                    path2 = curve2._path;
-                // Sort by path-id, curve, parameter, curve2, parameter2 so we
-                // can easily remove duplicates with calls to equals() after.
-                return path1 === path2
-                    ? curve1 === curve2
-                        ? Math.abs(l1._parameter - l2._parameter) < tolerance
-                            ? l1._curve2 === l2._curve2
-                                ? l1._parameter2 - l2._parameter2
-                                : l1._curve2.getIndex() - l2._curve2.getIndex()
-                            : l1._parameter - l2._parameter
-                        : curve1.getIndex() - curve2.getIndex()
-                    // Sort by path id to group all locs on the same path.
-                    : path1._id - path2._id;
+
+    /**
+     * {@grouptitle Tests}
+     * Checks if the location is an intersection with another curve and is
+     * merely touching the other curve, as opposed to crossing it.
+     *
+     * @return {Boolean} {@true if the location is an intersection that is
+     * merely touching another curve}
+     * @see #isCrossing()
+     */
+    isTouching: function() {
+        var inter = this._intersection;
+        if (inter && this.getTangent().isCollinear(inter.getTangent())) {
+            // Only consider two straight curves as touching if their lines
+            // don't intersect.
+            var curve1 = this.getCurve(),
+                curve2 = inter.getCurve();
+            return !(curve1.isStraight() && curve2.isStraight()
+                    && curve1.getLine().intersect(curve2.getLine()));
+        }
+        return false;
+    },
+
+    /**
+     * Checks if the location is an intersection with another curve and is
+     * crossing the other curve, as opposed to just touching it.
+     *
+     * @return {Boolean} {@true if the location is an intersection that is
+     * crossing another curve}
+     * @see #isTouching()
+     */
+    isCrossing: function(_report) {
+        // Implementation based on work by Andy Finnell:
+        // http://losingfight.com/blog/2011/07/09/how-to-implement-boolean-operations-on-bezier-paths-part-3/
+        // https://bitbucket.org/andyfinnell/vectorboolean
+        var inter = this._intersection;
+        if (!inter)
+            return false;
+        // TODO: Make getCurve() and getParameter() sync work in boolean ops
+        // before and after splitting!!!
+        var t1 = this._parameter,
+            t2 = inter._parameter,
+            tMin = /*#=*/Numerical.CURVETIME_EPSILON,
+            tMax = 1 - tMin;
+        // If the intersection is in the middle of the path, it is either a
+        // tangent or a crossing, no need for the detailed corner check below.
+        // But we do need a check for the edge case of tangents?
+        if (t1 >= tMin && t1 <= tMax || t2 >= tMin && t2 <= tMax)
+            return !this.isTouching();
+        // Values for getTangentAt() that are almost 0 and 1.
+        // NOTE: Even though getTangentAt() has code to support 0 and 1 instead
+        // of tMin and tMax, we still need to use this instead, as other issues
+        // emerge from switching to 0 and 1 in edge cases.
+        // NOTE: VectorBoolean has code that slowly shifts these points inwards
+        // until the resulting tangents are not ambiguous. Do we need this too?
+        var c2 = this._curve,
+            c1 = c2.getPrevious(),
+            c4 = inter._curve,
+            c3 = c4.getPrevious(),
+            PI = Math.PI;
+        if (!c1 || !c3)
+            return false;
+
+        if (_report) {
+            new Path.Circle({
+                center: this.getPoint(),
+                radius: 10,
+                strokeColor: 'red'
+            });
+            new Path({
+                segments: [c1.getSegment1(), c1.getSegment2(), c2.getSegment2()],
+                strokeColor: 'red',
+                strokeWidth: 4
+            });
+            new Path({
+                segments: [c3.getSegment1(), c3.getSegment2(), c4.getSegment2()],
+                strokeColor: 'orange',
+                strokeWidth: 4
             });
         }
+
+        function isInRange(angle, min, max) {
+            return min < max
+                ? angle > min && angle < max
+                // The range wraps around -PI / PI:
+                : angle > min && angle <= PI || angle >= -PI && angle < max;
+        }
+
+        // Calculate angles for all four tangents at the intersection point
+        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();
+
+        // Count how many times curve2 angles appear between the curve1 angles
+        // If each pair of angles split the other two, then the edges cross.
+        return (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2))
+            && (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1));
+    },
+
+    /**
+     * Checks if the location is an intersection with another curve and is
+     * part of an overlap between the two involved paths.
+     *
+     * @return {Boolean} {@true if the location is an intersection that is
+     * part of an overlap between the two involved paths}
+     * @see #isCrossing()
+     * @see #isTouching()
+     */
+    isOverlap: function() {
+        return !!this._overlap;
     }
 }, Base.each(Curve.evaluateMethods, function(name) {
     // Produce getters for #getTangent() / #getNormal() / #getCurvature()
-    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);
+    };
+}, {
+    // Do not override the existing #getPoint():
+    preserve: true
+}),
+new function() { // Scope for statics
+
+    function insert(locations, loc, merge) {
+        // Insert-sort by path-id, curve, parameter so we can easily merge
+        // duplicates with calls to equals() after.
+        var length = locations.length,
+            l = 0,
+            r = length - 1,
+            abs = Math.abs;
+
+        function search(index, dir) {
+            // If we reach the beginning/end of the list, also compare with the
+            // location at the other end, as paths are circular lists.
+            // NOTE: When merging, the locations array will only contain
+            // locations on the same path, so it is fine that check for the end
+            // to address circularity. See PathItem#getIntersections()
+            for (var i = index + dir; i >= -1 && i <= length; i += dir) {
+                // Wrap the index around, to match the other ends:
+                var loc2 = locations[((i % length) + length) % length];
+                // Once we're outside the spot, we can stop searching.
+                if (!loc.getPoint().isClose(loc2.getPoint(),
+                        /*#=*/Numerical.GEOMETRIC_EPSILON))
+                    break;
+                if (loc.equals(loc2))
+                    return loc2;
+            }
+            return null;
+        }
+
+        while (l <= r) {
+            var m = (l + r) >>> 1,
+                loc2 = locations[m],
+                found;
+            // See if the two locations are actually the same, and merge if
+            // they are. If they aren't check the other neighbors with search()
+            if (merge && (found = loc.equals(loc2) ? loc2
+                    : (search(m, -1) || search(m, 1)))) {
+                // We're done, don't insert, merge with the found location
+                // instead, and carry over overlap:
+                if (loc._overlap) {
+                    found._overlap = found._intersection._overlap = true;
+                }
+                return found;
+            }
+        var path1 = loc.getPath(),
+            path2 = loc2.getPath(),
+            // NOTE: equals() takes the intersection location into account,
+            // while this calculation of diff doesn't!
+            diff = path1 === path2
+                //Sort by both index and parameter. The two values added
+                // together provides a convenient sorting index.
+                ? (loc.getIndex() + loc.getParameter())
+                - (loc2.getIndex() + loc2.getParameter())
+                // Sort by path id to group all locs on same path.
+                : path1._id - path2._id;
+            if (diff < 0) {
+                r = m - 1;
+            } else {
+                l = m + 1;
+            }
+        }
+        // We didn't merge with a preexisting location, insert it now.
+        locations.splice(l, 0, loc);
+        return loc;
     }
-}, {}));
+
+    return { statics: {
+        insert: insert,
+
+        expand: function(locations) {
+            // Create a copy since insert() keeps modifying the array and
+            // inserting at sorted indices.
+            var expanded = locations.slice();
+            for (var i = 0, l = locations.length; i < l; i++) {
+                insert(expanded, locations[i]._intersection, false);
+            }
+            return expanded;
+        }
+    }};
+});
diff --git a/src/path/Path.js b/src/path/Path.js
index b61c3c0d..0dbc3874 100644
--- a/src/path/Path.js
+++ b/src/path/Path.js
@@ -150,7 +150,9 @@ var Path = PathItem.extend(/** @lends Path# */{
             if (parent)
                 parent._currentPath = undefined;
             // Clockwise state becomes undefined as soon as geometry changes.
-            this._length = this._clockwise = undefined;
+            // Also clear cached mono curves used for winding calculations.
+            this._length = this._area = this._clockwise = this._monoCurves =
+                    undefined;
             if (flags & /*#=*/ChangeFlag.SEGMENTS) {
                 this._version++; // See CurveLocation
             } else if (this._curves) {
@@ -159,10 +161,6 @@ var Path = PathItem.extend(/** @lends Path# */{
                for (var i = 0, l = this._curves.length; i < l; i++)
                     this._curves[i]._changed();
             }
-            // Clear cached curves used for winding direction and containment
-            // calculation.
-            // NOTE: This is only needed with __options.booleanOperations
-            this._monoCurves = undefined;
         } else if (flags & /*#=*/ChangeFlag.STROKE) {
             // TODO: We could preserve the purely geometric bounds that are not
             // affected by stroke: _bounds.bounds and _bounds.handleBounds
@@ -368,40 +366,6 @@ var Path = PathItem.extend(/** @lends Path# */{
         return this._segments.length === 0;
     },
 
-    /**
-     * Checks if this path consists of only linear curves. This can mean that
-     * the curves have no handles defined, or that the handles run collinear
-     * with the line.
-     *
-     * @return {Boolean} {@true if the path is entirely linear}
-     * @see Segment#isLinear()
-     * @see Curve#isLinear()
-     */
-    isLinear: function() {
-        var segments = this._segments;
-        for (var i = 0, l = segments.length; i < l; i++) {
-            if (!segments[i].isLinear())
-                return false;
-        }
-        return true;
-    },
-
-    /**
-     * Checks if none of the curves in the path define any curve handles.
-     *
-     * @return {Boolean} {@true if the path contains no curve handles}
-     * @see Segment#hasHandles()
-     * @see Curve#hasHandles()
-     */
-    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++)
@@ -410,10 +374,10 @@ var Path = PathItem.extend(/** @lends Path# */{
     },
 
     /**
-     * Private method that adds a segment to the segment list. It assumes that
-     * the passed object is a segment already and does not perform any checks.
-     * If a curves list was requested, it will kept in sync with the segments
-     * list automatically.
+     * Private method that adds segments to the segment list. It assumes that
+     * the passed object is an array of segments already and does not perform
+     * any checks. If a curves list was requested, it will be kept in sync with
+     * the segments list automatically.
      */
     _add: function(segs, index) {
         // Local short-cuts:
@@ -421,7 +385,7 @@ var Path = PathItem.extend(/** @lends Path# */{
             curves = this._curves,
             amount = segs.length,
             append = index == null,
-            index = append ? segments.length : index;
+            from = append ? segments.length : index;
         // Scan through segments to add first, convert if necessary and set
         // _path and _index references on them.
         for (var i = 0; i < amount; i++) {
@@ -431,7 +395,7 @@ var Path = PathItem.extend(/** @lends Path# */{
             if (segment._path)
                 segment = segs[i] = segment.clone();
             segment._path = this;
-            segment._index = index + i;
+            segment._index = from + i;
             // If parts of this segment are selected, adjust the internal
             // _selectedSegmentState now
             if (segment._selectionState)
@@ -442,20 +406,15 @@ var Path = PathItem.extend(/** @lends Path# */{
             segments.push.apply(segments, segs);
         } else {
             // Insert somewhere else
-            segments.splice.apply(segments, [index, 0].concat(segs));
+            segments.splice.apply(segments, [from, 0].concat(segs));
             // Adjust the indices of the segments above.
-            for (var i = index + amount, l = segments.length; i < l; i++)
+            for (var i = from + amount, l = segments.length; i < l; i++)
                 segments[i]._index = i;
         }
         // Keep the curves list in sync all the time in case it was requested
         // already.
-        if (curves || segs._curves) {
-            if (!curves)
-                curves = this._curves = [];
-            // We need to step one index down from the inserted segment to
-            // get its curve, except for the first segment.
-            var from = index > 0 ? index - 1 : index,
-                start = from,
+        if (curves) {
+            var start = from,
                 to = Math.min(from + amount, this._countCurves());
             if (segs._curves) {
                 // Reuse removed curves.
@@ -810,34 +769,91 @@ var Path = PathItem.extend(/** @lends Path# */{
     clear: '#removeSegments',
 
     /**
-     * The approximate length of the path in points.
+     * Checks if any of the curves in the path have curve handles set.
+     *
+     * @return {Boolean} {@true if the path has curve handles set}
+     * @see Segment#hasHandles()
+     * @see Curve#hasHandles()
+     */
+    hasHandles: function() {
+        var segments = this._segments;
+        for (var i = 0, l = segments.length; i < l; i++) {
+            if (segments[i].hasHandles())
+                return true;
+        }
+        return false;
+    },
+
+    /**
+     * Clears the path's handles by setting their coordinates to zero,
+     * turning the path into a polygon (or a polyline if it isn't closed).
+     */
+    clearHandles: function() {
+        var segments = this._segments;
+        for (var i = 0, l = segments.length; i < l; i++)
+            segments[i].clearHandles();
+    },
+
+    /**
+     * The approximate length of the path.
      *
      * @type Number
      * @bean
      */
     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;
     },
 
     /**
-     * The area of the path in square points. Self-intersecting paths can
-     * contain sub-areas that cancel each other out.
+     * The area that the path's geometry is covering. Self-intersecting paths
+     * can contain sub-areas that cancel each other out.
      *
      * @type Number
      * @bean
      */
     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;
+    },
+
+    /**
+     * Specifies whether the path is oriented clock-wise.
+     *
+     * @type Boolean
+     * @bean
+     */
+    isClockwise: function() {
+        if (this._clockwise !== undefined)
+            return this._clockwise;
+        return this.getArea() >= 0;
+    },
+
+    setClockwise: function(clockwise) {
+        // Only revers the path if its clockwise orientation is not the same
+        // as what it is now demanded to be.
+        // On-the-fly conversion to boolean:
+        if (this.isClockwise() != (clockwise = !!clockwise))
+            this.reverse();
+        // Reverse only flips _clockwise state if it was already set, so let's
+        // always set this here now.
+        this._clockwise = clockwise;
     },
 
     /**
@@ -1009,10 +1025,9 @@ var Path = PathItem.extend(/** @lends Path# */{
     reduce: function() {
         var curves = this.getCurves();
         for (var i = curves.length - 1; i >= 0; i--) {
-            var curve = curves[i],
-                next;
-            if (curve.isLinear() && (curve.getLength() === 0
-                    || (next = curve.getNext()) && curve.isCollinear(next)))
+            var curve = curves[i];
+            if (!curve.hasHandles() && (curve.getLength() === 0
+                    || curve.isCollinear(curve.getNext())))
                 curve.remove();
         }
         return this;
@@ -1174,7 +1189,8 @@ var Path = PathItem.extend(/** @lends Path# */{
      *
      * @param {Number} index the index of the curve in the {@link Path#curves}
      * array at which to split
-     * @param {Number} parameter the parameter at which the curve will be split
+     * @param {Number} parameter the curve-time parameter at which the curve
+     * will be split
      * @return {Path} the newly created path after splitting, if any
      */
     split: function(index, parameter) {
@@ -1191,7 +1207,7 @@ var Path = PathItem.extend(/** @lends Path# */{
             index = arg.index;
             parameter = arg.parameter;
         }
-        var tMin = /*#=*/Numerical.TOLERANCE,
+        var tMin = /*#=*/Numerical.CURVETIME_EPSILON,
             tMax = 1 - tMin;
         if (parameter >= tMax) {
             // t == 1 is the same as t == 0 and index ++
@@ -1235,29 +1251,6 @@ var Path = PathItem.extend(/** @lends Path# */{
         return null;
     },
 
-    /**
-     * Specifies whether the path is oriented clock-wise.
-     *
-     * @type Boolean
-     * @bean
-     */
-    isClockwise: function() {
-        if (this._clockwise !== undefined)
-            return this._clockwise;
-        return Path.isClockwise(this._segments);
-    },
-
-    setClockwise: function(clockwise) {
-        // Only revers the path if its clockwise orientation is not the same
-        // as what it is now demanded to be.
-        // On-the-fly conversion to boolean:
-        if (this.isClockwise() != (clockwise = !!clockwise))
-            this.reverse();
-        // Reverse only flips _clockwise state if it was already set, so let's
-        // always set this here now.
-        this._clockwise = clockwise;
-    },
-
     /**
      * Reverses the orientation of the path, by reversing all its segments.
      */
@@ -1414,15 +1407,47 @@ var Path = PathItem.extend(/** @lends Path# */{
             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 = /*#=*/Numerical.KAPPA;
+            // Look at handle length and the distance to the imaginary corner
+            // point and see if it their relation is kappa.
+            if (handle1.isOrthogonal(handle2)) {
+                var pt1 = seg1._point,
+                    pt2 = seg2._point,
+                    // Find the corner point by intersecting the lines described
+                    // by both handles:
+                    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) {
@@ -1974,7 +1999,7 @@ var Path = PathItem.extend(/** @lends Path# */{
      * Returns the nearest location on the path to the specified point.
      *
      * @function
-     * @param point {Point} the point for which we search the nearest location
+     * @param {Point} point the point for which we search the nearest location
      * @return {CurveLocation} the location on the path that's the closest to
      * the specified point
      */
@@ -1997,7 +2022,7 @@ var Path = PathItem.extend(/** @lends Path# */{
      * Returns the nearest point on the path to the specified point.
      *
      * @function
-     * @param point {Point} the point for which we search the nearest point
+     * @param {Point} point the point for which we search the nearest point
      * @return {Point} the point on the path that's the closest to the specified
      * point
      *
@@ -2028,8 +2053,8 @@ var Path = PathItem.extend(/** @lends Path# */{
     getNearestPoint: function(/* point */) {
         return this.getNearestLocation.apply(this, arguments).getPoint();
     }
-}), new function() { // Scope for drawing
-
+}),
+new function() { // Scope for drawing
     // Note that in the code below we're often accessing _x and _y on point
     // objects that were read from segments. This is because the SegmentPoint
     // class overrides the plain x / y properties with getter / setters and
@@ -2228,8 +2253,8 @@ var Path = PathItem.extend(/** @lends Path# */{
             drawHandles(ctx, this._segments, matrix, paper.settings.handleSize);
         }
     };
-}, new function() { // Path Smoothing
-
+},
+new function() { // Path Smoothing
     /**
      * Solves a tri-diagonal system for one of coordinates (x or y) of first
      * bezier control points.
@@ -2354,7 +2379,8 @@ var Path = PathItem.extend(/** @lends Path# */{
             }
         }
     };
-}, new function() { // PostScript-style drawing commands
+},
+new function() { // PostScript-style drawing commands
     /**
      * Helper method that returns the current segment and checks if a moveTo()
      * command is required first.
@@ -2477,7 +2503,6 @@ var Path = PathItem.extend(/** @lends Path# */{
                     x = pt.x,
                     y = pt.y,
                     abs = Math.abs,
-                    epsilon = /*#=*/Numerical.EPSILON,
                     rx = abs(radius.width),
                     ry = abs(radius.height),
                     rxSq = rx * rx,
@@ -2494,7 +2519,7 @@ var Path = PathItem.extend(/** @lends Path# */{
                 }
                 factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) /
                         (rxSq * ySq + rySq * xSq);
-                if (abs(factor) < epsilon)
+                if (abs(factor) < /*#=*/Numerical.EPSILON)
                     factor = 0;
                 if (factor < 0)
                     throw new Error(
@@ -2664,21 +2689,6 @@ var Path = PathItem.extend(/** @lends Path# */{
 
 // Mess with indentation in order to get more line-space below:
 statics: {
-    /**
-     * Determines whether the segments describe a path in clockwise or counter-
-     * clockwise orientation.
-     *
-     * @private
-     */
-    isClockwise: function(segments) {
-        var sum = 0;
-        // TODO: Check if this works correctly for all open paths.
-        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;
-    },
-
     /**
      * Returns the bounding rectangle of the item excluding stroke width.
      *
diff --git a/src/path/PathItem.Boolean.js b/src/path/PathItem.Boolean.js
index 8d44ed92..71ad94d7 100644
--- a/src/path/PathItem.Boolean.js
+++ b/src/path/PathItem.Boolean.js
@@ -13,23 +13,18 @@
 /*
  * Boolean Geometric Path Operations
  *
- * This is mostly written for clarity and compatibility, not optimised for
- * performance, and has to be tested heavily for stability.
- *
  * Supported
  *  - Path and CompoundPath items
  *  - Boolean Union
  *  - Boolean Intersection
  *  - Boolean Subtraction
- *  - Resolving a self-intersecting Path
- *
- * Not supported yet
- *  - Boolean operations on self-intersecting Paths
+ *  - Boolean Exclusion
+ *  - Resolving a self-intersecting Path items
+ *  - Boolean operations on self-intersecting Paths items
  *
  * @author Harikrishnan Gopalakrishnan
  * http://hkrish.com/playground/paperjs/booleanStudy.html
  */
-
 PathItem.inject(new function() {
     var operators = {
         unite: function(w) {
@@ -49,53 +44,90 @@ PathItem.inject(new function() {
         }
     };
 
+    // Creates a cloned version of the path that we can modify freely, with its
+    // matrix applied to its geometry. Calls #reduce() to simplify compound
+    // paths and remove empty curves, and #reorient() to make sure all paths
+    // have correct winding direction.
+    function preparePath(path) {
+        return path.clone(false).reduce().resolveCrossings()
+                .transform(null, true, true);
+    }
+
+    function finishBoolean(ctor, paths, path1, path2, reduce) {
+        var result = new ctor(Item.NO_INSERT);
+        result.addChildren(paths, true);
+        // See if the item can be reduced to just a simple Path.
+        if (reduce)
+            result = result.reduce();
+        // Insert the resulting path above whichever of the two paths appear
+        // further up in the stack.
+        result.insertAbove(path2 && path1.isSibling(path2)
+                && path1.getIndex() < path2.getIndex()
+                    ? path2 : path1);
+        // Copy over the left-hand item's style and we're done.
+        // TODO: Consider using Item#_clone() for this, but find a way to not
+        // clone children / name (content).
+        result.setStyle(path1._style);
+        return result;
+    }
+
+    var scaleFactor = 1;
+    var textAngle = 0;
+    var fontSize = 5;
+
+    var segmentOffset;
+    var pathIndices;
+    var pathIndex;
+    var pathCount;
+
     // Boolean operators return true if a curve with the given winding
     // contribution contributes to the final result or not. They are called
     // for each curve in the graph after curves in the operands are
     // split at intersections.
     function computeBoolean(path1, path2, operation) {
-        // Creates a cloned version of the path that we can modify freely, with
-        // its matrix applied to its geometry. Calls #reduce() to simplify
-        // compound paths and remove empty curves, and #reorient() to make sure
-        // all paths have correct winding direction.
-        function preparePath(path) {
-            return path.clone(false).reduce().reorient().transform(null, true,
-                    true);
-        }
+        scaleFactor = Base.pick(window.scaleFactor, scaleFactor);
+        textAngle = Base.pick(window.textAngle, 0);
 
+        segmentOffset = {};
+        pathIndices = {};
+
+        var reportSegments = window.reportSegments;
+        var reportWindings = window.reportWindings;
+        var reportIntersections = window.reportIntersections;
+        if (path2) {
+            window.reportSegments = false;
+            window.reportWindings = false;
+            window.reportIntersections = false;
+        }
         // We do not modify the operands themselves, but create copies instead,
         // fas produced by the calls to preparePath().
         // Note that the result paths might not belong to the same type
         // i.e. subtraction(A:Path, B:Path):CompoundPath etc.
         var _path1 = preparePath(path1),
             _path2 = path2 && path1 !== path2 && preparePath(path2);
+        window.reportSegments = reportSegments;
+        window.reportWindings = reportWindings;
+        window.reportIntersections = reportIntersections;
         // Give both paths the same orientation except for subtraction
         // and exclusion, where we need them at opposite orientation.
         if (_path2 && /^(subtract|exclude)$/.test(operation)
                 ^ (_path2.isClockwise() !== _path1.isClockwise()))
             _path2.reverse();
-        // Split curves at intersections on both paths. Note that for self
-        // intersection, _path2 will be null and getIntersections() handles it.
-        splitPath(Curve.filterIntersections(
-                _path1._getIntersections(_path2, null, []), true));
+        // Split curves at crossings and overlaps on both paths. Note that for
+        // self-intersection, path2 is null and getIntersections() handles it.
+        // console.time('intersection');
+        var intersections = CurveLocation.expand(
+            _path1.getIntersections(_path2, function(inter) {
+                // Only handle overlaps when not self-intersecting
+                return _path2 && inter.isOverlap() || inter.isCrossing();
+            })
+        );
+        // console.timeEnd('intersection');
+        splitPath(intersections);
 
-        /*
-        console.time('inter');
-        var locations = _path1._getIntersections(_path2, null, []);
-        console.timeEnd('inter');
-        if (_path2 && false) {
-            console.time('self');
-            _path1._getIntersections(null, null, locations);
-            _path2._getIntersections(null, null, locations);
-            console.timeEnd('self');
-        }
-        splitPath(Curve.filterIntersections(locations, true));
-        */
-        var chain = [],
-            segments = [],
+        var segments = [],
             // Aggregate of all curves in both operands, monotonic in y
-            monoCurves = [],
-            tolerance = /*#=*/Numerical.TOLERANCE;
+            monoCurves = [];
 
         function collect(paths) {
             for (var i = 0, l = paths.length; i < l; i++) {
@@ -111,143 +143,109 @@ PathItem.inject(new function() {
             collect(_path2._children || [_path2]);
         // Propagate the winding contribution. Winding contribution of curves
         // does not change between two intersections.
-        // First, sort all segments with an intersection to the beginning.
-        segments.sort(function(a, b) {
-            var _a = a._intersection,
-                _b = b._intersection;
-            return !_a && !_b || _a && _b ? 0 : _a ? -1 : 1;
-        });
+        // First, propagate winding contributions for curve chains starting in
+        // all intersections:
+        for (var i = 0, l = intersections.length; i < l; i++) {
+            propagateWinding(intersections[i]._segment, _path1, _path2,
+                    monoCurves, operation);
+        }
+        // Now process the segments that are not part of any intersecting chains
         for (var i = 0, l = segments.length; i < l; i++) {
             var segment = segments[i];
-            if (segment._winding != null)
-                continue;
-            // Here we try to determine the most probable winding number
-            // contribution for this curve-chain. Once we have enough confidence
-            // in the winding contribution, we can propagate it until the
-            // intersection or end of a curve chain.
-            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);
-            // Calculate the average winding among three evenly distributed
-            // points along this curve chain as a representative winding number.
-            // This selection gives a better chance of returning a correct
-            // winding than equally dividing the curve chain, with the same
-            // (amortised) time.
-            for (var j = 0; j < 3; j++) {
-                // Try the points at 1/4, 2/4 and 3/4 of the total length:
-                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 the selected location on the curve falls onto its
-                        // beginning or end, use the curve's center instead.
-                        if (length < tolerance
-                                || curveLength - length < tolerance)
-                            length = curveLength / 2;
-                        var curve = node.segment.getCurve(),
-                            pt = curve.getPointAt(length),
-                            hor = isHorizontal(curve),
-                            path = getMainPath(curve);
-                        // While subtracting, we need to omit this curve if this
-                        // curve is contributing to the second operand and is
-                        // outside the first operand.
-                        windingSum += operation === 'subtract' && _path2
-                            && (path === _path1 && _path2._getWinding(pt, hor)
-                            || path === _path2 && !_path1._getWinding(pt, hor))
-                            ? 0
-                            : getWinding(pt, monoCurves, hor);
-                        break;
-                    }
-                    length -= curveLength;
-                }
-            }
-            // Assign the average winding to the entire curve chain.
-            var winding = Math.round(windingSum / 3);
-            for (var j = chain.length - 1; j >= 0; j--) {
-                var seg = chain[j].segment,
-                    inter = seg._intersection,
-                    wind = winding;
-                if (inter && inter._overlap) {
-                    switch (operation) {
-                    case 'unite':
-                        if (wind === 1)
-                            wind = 2;
-                        break;
-                    case 'intersect':
-                        if (wind === 2)
-                            wind = 1;
-                        break;
-                    case 'subtract':
-                        // When subtracting, we need to reverse the winding
-                        // number along overlaps.
-                        // Calculate the new winding number based on current
-                        // number and role in the operation.
-                        var path = getMainPath(seg),
-                            newWind = wind === 0 && path === _path1 ? 1
-                                    : wind === 1 && path === _path2 ? 2
-                                    : null;
-                        if (newWind != null) {
-                            // Check against the winding of the intersecting
-                            // path, to exclude islands in compound paths, where
-                            // the reversal of winding numbers below in overlaps
-                            // is not required:
-                            var pt = inter._segment._path.getInteriorPoint();
-                            if (getWinding(pt, monoCurves) === 1)
-                                wind = newWind;
-                        }
-                        break;
-                    }
-                }
-                seg._winding = wind;
+            if (segment._winding == null) {
+                propagateWinding(segment, _path1, _path2, monoCurves,
+                        operation);
             }
         }
-        // Trace closed contours and insert them into the result.
-        var result = new CompoundPath(Item.NO_INSERT);
-        result.addChildren(tracePaths(segments, monoCurves, operation, !_path2),
-                true);
-        // See if the CompoundPath can be reduced to just a simple Path.
-        result = result.reduce();
-        result.insertAbove(path1);
-        // Copy over the left-hand item's style and we're done.
-        // TODO: Consider using Item#_clone() for this, but find a way to not
-        // clone children / name (content).
-        result.setStyle(path1._style);
-        return result;
+        return finishBoolean(CompoundPath, tracePaths(segments, operation),
+                path1, path2, true);
+    }
+
+    function logIntersection(inter) {
+        var other = inter._intersection;
+        var log = ['Intersection', inter._id, 'id', inter.getPath()._id,
+            'i', inter.getIndex(), 't', inter.getParameter(),
+            'o', inter.isOverlap(), 'p', inter.getPoint(),
+            'Other', other._id, 'id', other.getPath()._id,
+            'i', other.getIndex(), 't', other.getParameter(),
+            'o', other.isOverlap(), 'p', other.getPoint()];
+        console.log(log.map(function(v) {
+            return v == null ? '-' : v
+        }).join(' '));
+    }
+
+    /*
+     * Creates linked lists between intersections through their _next property.
+     *
+     * @private
+     */
+    function linkIntersections(from, to) {
+        // Only create the link if it's not already in the existing chain, to
+        // avoid endless recursions. First walk to the beginning of the chain,
+        // and abort if we find `to`.
+        var prev = from;
+        while (prev) {
+            if (prev === to)
+                return;
+            prev = prev._prev;
+        }
+        // Now walk to the end of the existing chain to find an empty spot, but
+        // stop if we find `to`, to avoid adding it again.
+        while (from._next && from._next !== to)
+            from = from._next;
+        // If we're reached the end of the list, we can add it.
+        if (!from._next) {
+            // Go back to beginning of the other chain, and link the two up.
+            while (to._prev)
+                to = to._prev;
+            from._next = to;
+            to._prev = from;
+        }
     }
 
     /**
-     * Private method for splitting a PathItem at the given intersections.
-     * The routine works for both self intersections and intersections
-     * between PathItems.
+     * Splits a path-item at the given locations.
      *
-     * @param {CurveLocation[]} intersections Array of CurveLocation objects
+     * @param {CurveLocation[]} locations an array of the locations to split the
+     * path-item at.
+     * @private
      */
-    function splitPath(intersections) {
-        // TODO: Make public in API, since useful!
-        var tMin = /*#=*/Numerical.TOLERANCE,
-            tMax = 1 - tMin,
-            isStraight = false,
-            straightSegments = [];
+    function splitPath(locations) {
+        if (window.reportIntersections) {
+            console.log('Crossings', locations.length / 2);
+            locations.forEach(function(inter) {
+                if (inter._other)
+                    return;
+                logIntersection(inter);
+                new Path.Circle({
+                    center: inter.point,
+                    radius: 2 * scaleFactor,
+                    strokeColor: 'red',
+                    strokeScaling: false
+                });
+            });
+        }
 
-        for (var i = intersections.length - 1, curve, prev; i >= 0; i--) {
-            var loc = intersections[i],
-                t = loc._parameter;
-            // Check if we are splitting same curve multiple times, but avoid
-            // dividing with zero.
-            if (prev && prev._curve === loc._curve && prev._parameter > 0) {
-                // Scale parameter after previous split.
-                t /= prev._parameter;
-            } else {
-                curve = loc._curve;
-                isStraight = curve.isStraight();
+        // TODO: Make public in API, since useful!
+        var tMin = /*#=*/Numerical.CURVETIME_EPSILON,
+            tMax = 1 - tMin,
+            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) {
+                // This is a new curve, update noHandles setting.
+                noHandles = !curve.hasHandles();
+            } else if (prevT > 0) {
+                // Scale parameter when we are splitting same curve multiple
+                // times, but avoid dividing by zero.
+                t /= prevT;
             }
             var segment;
             if (t < tMin) {
@@ -255,43 +253,52 @@ PathItem.inject(new function() {
             } else if (t > tMax) {
                 segment = curve._segment2;
             } else {
-                // Split the curve at t, passing true for ignoreStraight to not
-                // force the result of splitting straight curves straight.
-                var newCurve = curve.divide(t, true, true);
-                segment = newCurve._segment1;
-                curve = newCurve.getPrevious();
-                // Keep track of segments of once straight curves, so they can
-                // be set back straight at the end.
-                if (isStraight)
-                    straightSegments.push(segment);
+                // Split the curve at t, passing true for _setHandles to always
+                // set the handles on the sub-curves even if the original curve
+                // had no handles.
+                segment = curve.divide(t, true, true)._segment1;
+                // Keep track of segments of curves without handles, so they can
+                // be cleared again at the end.
+                if (noHandles)
+                    clearSegments.push(segment);
             }
-            // Link the new segment with the intersection on the other curve
-            segment._intersection = loc.getIntersection();
-            loc._segment = segment;
-            prev = loc;
-        }
-        // Reset linear segments if they were part of a linear curve
-        // and if we are done with the entire curve.
-        for (var i = 0, l = straightSegments.length; i < l; i++) {
-            var segment = straightSegments[i];
-            // TODO: Implement Segment#makeStraight(),
-            // or #adjustHandles({ straight: true }))
-            segment._handleIn.set(0, 0);
-            segment._handleOut.set(0, 0);
-        }
-    }
+            loc._setSegment(segment);
 
-    function getMainPath(item) {
-        var path = item._path,
-            parent = path._parent;
-        return parent instanceof CompoundPath ? parent : path;
-    }
+            // Create links from the new segment to the intersection on the
+            // other curve, as well as from there back. If there are multiple
+            // intersections on the same segment, we create linked lists between
+            // the intersections through linkIntersections(), linking both ways.
+            var inter = segment._intersection,
+                dest = loc._intersection;
+            if (inter) {
+                linkIntersections(inter, dest);
+                // Each time we add a new link to the linked list, we need to
+                // add links from all the other entries to the new entry.
+                var other = inter;
+                while (other) {
+                    linkIntersections(other._intersection, inter);
+                    other = other._next;
+                }
+            } else {
+                segment._intersection = dest;
+            }
+            prevCurve = curve;
+            prevT = origT;
+        }
+        // Clear segment handles if they were part of a curve with no handles,
+        // once we are done with the entire curve.
+        for (var i = 0, l = clearSegments.length; i < l; i++) {
+            clearSegments[i].clearHandles();
+        }
 
-    function isHorizontal(curve) {
-        // Determine if the curve is a horizontal linear curve by checking the
-        // slope of it's tangent.
-        return curve.isLinear() && Math.abs(curve.getTangentAt(0.5, true).y)
-                < /*#=*/Numerical.TOLERANCE;
+        if (window.reportIntersections) {
+            console.log('Split Crossings');
+            locations.forEach(function(inter) {
+                if (!inter._other) {
+                    logIntersection(inter);
+                }
+            });
+        }
     }
 
     /**
@@ -299,8 +306,8 @@ PathItem.inject(new function() {
      * with respect to a given set of monotone curves.
      */
     function getWinding(point, curves, horizontal, testContains) {
-        var tolerance = /*#=*/Numerical.TOLERANCE,
-            tMin = tolerance,
+        var epsilon = /*#=*/Numerical.WINDING_EPSILON,
+            tMin = /*#=*/Numerical.CURVETIME_EPSILON,
             tMax = 1 - tMin,
             px = point.x,
             py = point.y,
@@ -314,8 +321,8 @@ PathItem.inject(new function() {
         if (horizontal) {
             var yTop = -Infinity,
                 yBottom = Infinity,
-                yBefore = py - tolerance,
-                yAfter = py + tolerance;
+                yBefore = py - epsilon,
+                yAfter = py + epsilon;
             // Find the closest top and bottom intercepts for the same vertical
             // line.
             for (var i = 0, l = curves.length; i < l; i++) {
@@ -335,14 +342,15 @@ PathItem.inject(new function() {
             // half of closest top and bottom intercepts.
             yTop = (yTop + py) / 2;
             yBottom = (yBottom + py) / 2;
-            // TODO: Don't we need to pass on testContains here?
             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;
             // Find the winding number for right side of the curve, inclusive of
             // the curve itself, while tracing along its +-x direction.
             var startCounted = false,
@@ -383,7 +391,7 @@ PathItem.inject(new function() {
                         // curve merely touches the ray towards +-x direction,
                         // but proceeds to the same side of the ray.
                         // This essentially is not a crossing.
-                        if (Numerical.isZero(slope) && !Curve.isLinear(values)
+                        if (Numerical.isZero(slope) && !Curve.isStraight(values)
                                 // Does the slope over curve beginning change?
                                 || t < tMin && slope * Curve.getTangent(
                                     curve.previous.values, 1).y < 0
@@ -416,6 +424,65 @@ PathItem.inject(new function() {
         return Math.max(abs(windLeft), abs(windRight));
     }
 
+    function propagateWinding(segment, path1, path2, monoCurves, operation) {
+        // Here we try to determine the most probable winding number
+        // contribution for the curve-chain starting with this segment. Once we
+        // have enough confidence in the winding contribution, we can propagate
+        // it until the next intersection or end of a curve chain.
+        var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
+            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);
+        // Calculate the average winding among three evenly distributed
+        // points along this curve chain as a representative winding number.
+        // This selection gives a better chance of returning a correct
+        // winding than equally dividing the curve chain, with the same
+        // (amortised) time.
+        for (var i = 0; i < 3; i++) {
+            // Try the points at 1/4, 2/4 and 3/4 of the total length:
+            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 the selected location on the curve falls onto its
+                    // beginning or end, use the curve's center instead.
+                    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;
+                    // While subtracting, we need to omit this curve if this
+                    // curve is contributing to the second operand and is
+                    // outside the first operand.
+                    windingSum += operation === 'subtract' && path2
+                        && (path === path1 && path2._getWinding(pt, hor)
+                        || path === path2 && !path1._getWinding(pt, hor))
+                        ? 0
+                        : getWinding(pt, monoCurves, hor);
+                    break;
+                }
+                length -= curveLength;
+            }
+        }
+        // Assign the average winding to the entire curve chain.
+        var winding = Math.round(windingSum / 3);
+        for (var j = chain.length - 1; j >= 0; j--)
+            chain[j].segment._winding = winding;
+    }
+
     /**
      * Private method to trace closed contours from a set of segments according
      * to a set of constraints-winding contribution and a custom operator.
@@ -428,128 +495,345 @@ PathItem.inject(new function() {
      * not
      * @return {Path[]} the contours traced
      */
-    function tracePaths(segments, monoCurves, operation) {
-        var paths = [],
-            operator = operators[operation],
-            tolerance = /*#=*/Numerical.TOLERANCE,
-            // Values for getTangentAt() that are almost 0 and 1.
-            // NOTE: Even though getTangentAt() supports 0 and 1 instead of
-            // tMin and tMax, we still need to use this instead, as other issues
-            // emerge from switching to 0 and 1 in edge cases.
-            tMin = tolerance,
-            tMax = 1 - tMin;
-        for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
-            seg = startSeg = segments[i];
-            if (seg._visited || !operator(seg._winding))
-                continue;
-            var path = new Path(Item.NO_INSERT),
+    function tracePaths(segments, operation) {
+        pathIndex = 0;
+        pathCount = 1;
+
+        function labelSegment(seg, text, color) {
+            var point = seg.point;
+            var key = Math.round(point.x / (10 * scaleFactor))
+                + ',' + Math.round(point.y  / (10 * scaleFactor));
+            var offset = segmentOffset[key] || 0;
+            segmentOffset[key] = offset + 1;
+            var size = fontSize * scaleFactor;
+            var text = new PointText({
+                point: point.add(
+                        new Point(size, size / 2).add(0, offset * size * 1.2)
+                        .rotate(textAngle)),
+                content: text,
+                justification: 'left',
+                fillColor: color,
+                fontSize: fontSize
+            });
+            // TODO! PointText should have pivot in #point by default!
+            text.pivot = text.globalToLocal(text.point);
+            text.scale(scaleFactor);
+            text.rotate(textAngle);
+            new Path.Line({
+                from: text.point,
+                to: seg.point,
+                strokeColor: color,
+                strokeScaling: false
+            });
+            return text;
+        }
+
+        function drawSegment(seg, other, text, index, color) {
+            if (!window.reportSegments)
+                return;
+            new Path.Circle({
+                center: seg.point,
+                radius: fontSize / 2 * scaleFactor,
+                strokeColor: color,
+                strokeScaling: false
+            });
+            labelSegment(seg, '#' + pathCount + '.'
+                            + (path ? path._segments.length + 1 : 1)
+                            + ' (' + (index + 1) + '): ' + text
+                    + '   id: ' + seg._path._id + '.' + seg._index
+                    + (other ? ' -> ' + other._path._id + '.' + other._index : '')
+                    + '   v: ' + (seg._visited ? 1 : 0)
+                    + '   p: ' + seg._point
+                    + '   op: ' + isValid(seg)
+                    + '   ov: ' + !!(inter && inter.isOverlap())
+                    + '   wi: ' + seg._winding
+                    + '   mu: ' + !!(inter && inter._next)
+                    , color);
+        }
+
+        for (var i = 0, j = 0;
+                i < (window.reportWindings ? segments.length : 0);
+                i++, j++) {
+            var seg = segments[i];
+                path = seg._path,
+                id = path._id,
+                point = seg.point,
                 inter = seg._intersection,
-                startInterSeg = inter && inter._segment,
-                added = false, // Whether a first segment as added already
-                dir = 1;
-            do {
-                var handleIn = dir > 0 ? seg._handleIn : seg._handleOut,
-                    handleOut = dir > 0 ? seg._handleOut : seg._handleIn,
-                    interSeg;
-                // If the intersection segment is valid, try switching to
-                // it, with an appropriate direction to continue traversal.
-                // Else, stay on the same contour.
-                if (added && (!operator(seg._winding))
-                        && (inter = seg._intersection)
-                        && (interSeg = inter._segment)
-                        && interSeg !== startSeg) {
-                    if (interSeg._path === seg._path) {
-                        // Switch to the intersection segment, if we are
-                        // resolving self-Intersections.
-                        seg._visited = interSeg._visited;
-                        seg = interSeg;
-                        dir = 1;
-                    } else if (inter._overlap && operation !== 'intersect') {
-                        // Switch to the overlapping intersection segment
-                        // if its winding number along the curve is 1, to
-                        // leave the overlapping area.
-                        // NOTE: We cannot check the next (overlapping)
-                        // segment since its winding number will always be 2
-                        var curve = interSeg.getCurve();
-                        if (getWinding(curve.getPointAt(0.5, true),
-                                monoCurves, isHorizontal(curve)) === 1) {
-                            seg._visited = interSeg._visited;
-                            seg = interSeg;
-                            dir = 1;
-                        }
+                ix = inter,
+                ixs = ix && ix._segment,
+                n1x = inter && inter._next,
+                n1xs = n1x && n1x._segment,
+                n2x = n1x && n1x._next,
+                n2xs = n2x && n2x._segment,
+                n3x = n2x && n2x._next,
+                n3xs = n3x && n3x._segment,
+                item = path._parent instanceof CompoundPath ? path._parent : path;
+            if (!(id in pathIndices)) {
+                pathIndices[id] = ++pathIndex;
+                j = 0;
+            }
+            labelSegment(seg, '#' + pathIndex + '.' + (j + 1)
+                    + '   id: ' + seg._path._id + '.' + seg._index
+                    + '   ix: ' + (ixs && ixs._path._id + '.' + ixs._index
+                        + '(' + ix._id + ')' || '--')
+                    + '   n1x: ' + (n1xs && n1xs._path._id + '.' + n1xs._index
+                        + '(' + n1x._id + ')' || '--')
+                    + '   n2x: ' + (n2xs && n2xs._path._id + '.' + n2xs._index
+                        + '(' + n2x._id + ')' || '--')
+                    + '   n3x: ' + (n3xs && n3xs._path._id + '.' + n3xs._index
+                        + '(' + n3x._id + ')' || '--')
+                    + '   pt: ' + seg._point
+                    + '   ov: ' + !!(inter && inter.isOverlap())
+                    + '   wi: ' + seg._winding
+                    , item.strokeColor || item.fillColor || 'black');
+        }
+
+        var paths = [],
+            start,
+            otherStart,
+            operator = operators[operation],
+            // Adjust winding contributions for specific operations on overlaps:
+            overlapWinding = {
+                unite: { 1: 2 },
+                intersect: { 2: 1 }
+            }[operation];
+
+        function isValid(seg, unadjusted) {
+            if (seg._visited)
+                return false;
+            if (!operator) // For self-intersection, we're always valid!
+                return true;
+            var winding = seg._winding,
+                inter = seg._intersection;
+            if (inter && !unadjusted && overlapWinding && inter.isOverlap())
+                winding = overlapWinding[winding] || winding;
+            return operator(winding);
+        }
+
+        function isStart(seg) {
+            return seg === start || seg === otherStart;
+        }
+
+        // If there are multiple possible intersections, find the one
+        // that's either connecting back to start or is not visited yet,
+        // and will be part of the boolean result:
+        function findBestIntersection(inter, strict) {
+            if (!inter._next)
+                return inter;
+            while (inter) {
+                var seg = inter._segment,
+                    nextSeg = seg.getNext(),
+                    nextInter = nextSeg._intersection;
+                if (window.reportSegments) {
+                    console.log('getIntersection(' + strict + ')'
+                            + ', seg: ' + seg._path._id + '.' + seg._index
+                            + ', next: ' + nextSeg._path._id + '.'
+                                + nextSeg._index
+                            + ', seg vis:' + !!seg._visited
+                            + ', next vis:' + !!nextSeg._visited
+                            + ', next start:' + isStart(nextSeg)
+                            + ', seg wi:' + seg._winding
+                            + ', next wi:' + nextSeg._winding
+                            + ', seg op:' + isValid(seg, true)
+                            + ', next op:' + (!(strict && nextInter
+                                && nextInter.isOverlap())
+                                && isValid(nextSeg, true)
+                                || !strict && nextInter
+                                    && isValid(nextInter._segment, true))
+                            + ', seg ov: ' + !!(seg._intersection
+                                && seg._intersection.isOverlap())
+                            + ', next ov: ' + !!(nextSeg._intersection
+                                && nextSeg._intersection.isOverlap())
+                            + ', more: ' + (!!inter._next));
+                }
+                // See if this segment and the next are both not visited yet, or
+                // are bringing us back to the beginning, and are both part of
+                // the boolean result.
+                // Handling overlaps correctly here is a bit tricky business,
+                // and requires two passes, first with `strict = true`, then
+                // `false`: In strict mode, the current segment and the next
+                // segment are both checked for validity, and only the current
+                // one is allowed to be an overlap (passing true for
+                // `unadjusted` in isValid()). If this pass does not yield a
+                // result, the non-strict mode is used, in which invalid current
+                // segments are tolerated, and overlaps for the next segment are
+                // allowed as long as they are valid when not adjusted.
+                if (isStart(nextSeg)
+                    || !seg._visited && !nextSeg._visited
+                    // Self-intersections (!operator) don't need isValid() calls
+                    && (!operator
+                        // We need to use the unadjusted winding here since an
+                        // overlap crossing might have brought us here, in which
+                        // case isValid(seg, false) might be false.
+                        || (!strict || isValid(seg, true))
+                        // Do not consider nextSeg in strict mode if it is part
+                        // of an overlap, in order to give non-overlapping
+                        // options that might follow the priority over overlaps.
+                        && (!(strict && nextInter && nextInter.isOverlap())
+                            && isValid(nextSeg, true)
+                            // If the next segment isn't valid, its intersection
+                            // to which we may switch might be, so check that.
+                            || !strict && nextInter
+                            && isValid(nextInter._segment, true))
+                    ))
+                    return inter;
+                // If it's no match, continue with the next linked intersection.
+                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;
+            // Do not start a chain with already visited segments, and segments
+            // that are not going to be part of the resulting operation.
+            if (!isValid(seg))
+                continue;
+            start = otherStart = null;
+            while (!finished) {
+                var inter = seg._intersection;
+                // Once we started a chain, see if there are multiple
+                // intersections, and if so, pick the best one:
+                if (inter && window.reportSegments) {
+                    console.log('-----\n'
+                            + '#' + pathCount + '.'
+                                + (path ? path._segments.length + 1 : 1)
+                            + ', Before getIntersection()'
+                            + ', seg: ' + seg._path._id + '.' + seg._index
+                            + ', other: ' + inter._segment._path._id + '.'
+                                + inter._segment._index);
+                }
+                inter = inter && (findBestIntersection(inter, true)
+                        || findBestIntersection(inter, false)) || inter;
+                var other = inter && inter._segment;
+                // A switched intersection means we may have changed the segment
+                // Point to the other segment in the selected intersection.
+                if (inter && window.reportSegments) {
+                    console.log('After getIntersection()'
+                            + ', seg: '
+                                + seg._path._id + '.' + seg._index
+                            + ', other: ' + inter._segment._path._id + '.'
+                                + inter._segment._index);
+                }
+                var handleIn = path && seg._handleIn;
+                if (!path || !other) {
+                    // Just add the first segment and all segments that have no
+                    // intersection.
+                    drawSegment(seg, null, 'add', i, 'black');
+                } else if (isValid(other)) {
+                    // The other segment is part of the boolean result, and we
+                    // are at crossing, switch over.
+                    drawSegment(seg, other, 'cross', i, 'green');
+                    seg = other;
+                } else if (inter.isOverlap() && operation !== 'intersect') {
+                    // Switch to the overlapping intersecting segment if it is
+                    // part of the boolean result. Do not adjust for overlap!
+                    if (isValid(other, true)) {
+                        drawSegment(seg, other, 'overlap-cross', i, 'orange');
+                        seg = other;
                     } else {
-                        var c1 = seg.getCurve();
-                        if (dir > 0)
-                            c1 = c1.getPrevious();
-                        var t1 = c1.getTangentAt(dir < 0 ? tMin : tMax, true),
-                            // Get both curves at the intersection
-                            // (except the entry curves).
-                            c4 = interSeg.getCurve(),
-                            c3 = c4.getPrevious(),
-                            // Calculate their winding values and tangents.
-                            t3 = c3.getTangentAt(tMax, true),
-                            t4 = c4.getTangentAt(tMin, true),
-                            // Cross product of the entry and exit tangent
-                            // vectors at the intersection, will let us select
-                            // the correct contour to traverse next.
-                            w3 = t1.cross(t3),
-                            w4 = t1.cross(t4);
-                        if (Math.abs(w3 * w4) > /*#=*/Numerical.EPSILON) {
-                            // Do not attempt to switch contours if we aren't
-                            // sure that there is a possible candidate.
-                            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 we didn't find a suitable direction for next
-                            // contour to traverse, stay on the same contour.
-                            if (nextSeg._visited && seg._path !== nextSeg._path
-                                        || !operator(nextSeg._winding)) {
-                                dir = 1;
-                            } else {
-                                // Switch to the intersection segment.
-                                seg._visited = interSeg._visited;
-                                seg = interSeg;
-                                if (nextSeg._visited)
-                                    dir = 1;
-                            }
-                        } else {
-                            dir = 1;
+                        drawSegment(seg, other, 'overlap-stay', i, 'orange');
+                    }
+                } else if (operation === 'exclude') {
+                    // We need to handle exclusion separately, as we want to
+                    // switch at each crossing.
+                    drawSegment(seg, other, 'exclude-cross', i, 'green');
+                    seg = other;
+                } else {
+                    // Keep on truckin'
+                    drawSegment(seg, null, 'stay', i, 'blue');
+                }
+                if (seg._visited) {
+                    if (isStart(seg)) {
+                        finished = true;
+                        drawSegment(seg, null, 'done', i, 'red');
+                    } else if (inter) {
+                        // See if any of the intersections is the start segment,
+                        // and if so finish the path.
+                        var found = findStartSegment(inter, true)
+                            || findStartSegment(inter, false);
+                        if (found) {
+                            seg = found;
+                            finished = true;
+                            drawSegment(seg, null, 'done multiple', i, 'red');
                         }
                     }
-                    handleOut = dir > 0 ? seg._handleOut : seg._handleIn;
+                    if (!finished) {
+                        // We didn't manage to switch, so stop right here.
+                        console.error('Visited segment encountered, aborting #'
+                                + pathCount + '.'
+                                + (path ? path._segments.length + 1 : 1)
+                                + ', id: ' + seg._path._id + '.' + seg._index
+                                + ', multiple: ' + !!(inter && inter._next));
+                    }
+                    break;
                 }
-                // Add the current segment to the path, and mark the added
-                // segment as visited.
-                path.add(new Segment(seg._point, added && handleIn, handleOut));
-                added = true;
+                if (!path) {
+                    path = new Path(Item.NO_INSERT);
+                    start = seg;
+                    otherStart = other;
+                }
+                if (window.reportSegments) {
+                    console.log('Adding', seg._path._id + '.' + seg._index);
+                }
+                // Add the segment to the path, and mark it as visited.
+                path.add(new Segment(seg._point, handleIn, seg._handleOut));
                 seg._visited = true;
-                // Move to the next segment according to the traversal direction
-                seg = dir > 0 ? seg.getNext() : seg. getPrevious();
-            } while (seg && !seg._visited
-                    && seg !== startSeg && seg !== startInterSeg
-                    && (seg._intersection || operator(seg._winding)));
+                seg = seg.getNext();
+                if (isStart(seg)) {
+                    drawSegment(seg, null, 'done', i, 'red');
+                    finished = true;
+                }
+            }
+            if (!path)
+                continue;
             // Finish with closing the paths if necessary, correctly linking up
             // curves etc.
-            if (seg && (seg === startSeg || seg === startInterSeg)) {
-                path.firstSegment.setHandleIn((seg === startInterSeg
-                        ? startInterSeg : seg)._handleIn);
+            if (finished) {
+                path.firstSegment.setHandleIn(seg._handleIn);
                 path.setClosed(true);
+                if (window.reportSegments) {
+                    console.log('Boolean operation completed',
+                            '#' + pathCount + '.' +
+                            (path ? path._segments.length + 1 : 1));
+                }
             } else {
-                // Boolean operation results in open path, removing for now.
-                // TODO: We shouldn't even get here maybe? How can this be
-                // prevented?
+                var colors = ['cyan', 'green', 'orange', 'yellow'];
+                var color = new Color(colors[pathCount % (colors.length - 1)]);
+                console.error('%cBoolean operation results in open path',
+                        'background: ' + color.toCSS() + '; color: #fff;',
+                        'segs =',
+                        path._segments.length, 'length = ', path.getLength(),
+                        '#' + pathCount + '.' +
+                        (path ? path._segments.length + 1 : 1));
+                paper.project.activeLayer.addChild(path);
+                color.alpha = 0.5;
+                path.strokeColor = color;
+                path.strokeWidth = 3;
+                path.strokeScaling = false;
                 path = null;
             }
             // Add the path to the result, while avoiding stray segments and
-            // incomplete paths. The amount of segments for valid paths depend
-            // on their geometry:
-            // - Closed paths with only straight lines need more than 2 segments
-            // - Closed paths with curves can consist of only one segment
-            if (path && path._segments.length > path.isLinear() ? 2 : 0)
+            // paths that are incomplete or cover no area.
+            // As an optimization, only check paths with 4 or less segments
+            // for their area, and assume that they cover an area when more.
+            if (path && (path._segments.length > 4
+                    || !Numerical.isZero(path.getArea()))) {
                 paths.push(path);
+                path = null;
+            }
+            pathCount++;
         }
         return paths;
     }
@@ -619,6 +903,9 @@ PathItem.inject(new function() {
          */
         exclude: function(path) {
             return computeBoolean(this, path, 'exclude');
+            // return finishBoolean(CompoundPath,
+            //         [this.subtract(path), path.subtract(this)],
+            //         this, path, true);
         },
 
         /**
@@ -629,7 +916,23 @@ PathItem.inject(new function() {
          * @return {Group} the resulting group item
          */
         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.reorient();
+            splitPath(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).reorient();
         }
     };
 });
@@ -676,8 +979,8 @@ Path.inject(/** @lends Path# */{
                 y1 = v[3],
                 y2 = v[5],
                 y3 = v[7];
-            if (Curve.isLinear(v)) {
-                // Handling linear curves is easy.
+            if (Curve.isStraight(v)) {
+                // Handling straight curves is easy.
                 insertCurve(v);
             } else {
                 // Split the curve at y extrema, to get bezier curves with clear
@@ -685,20 +988,20 @@ Path.inject(/** @lends Path# */{
                 var a = 3 * (y1 - y2) - y0 + y3,
                     b = 2 * (y0 + y2) - 4 * y1,
                     c = y1 - y0,
-                    tolerance = /*#=*/Numerical.TOLERANCE,
-                    roots = [];
-                // Keep then range to 0 .. 1 (excluding) in the search for y
-                // extrema.
-                var count = Numerical.solveQuadratic(a, b, c, roots, tolerance,
-                        1 - tolerance);
-                if (count === 0) {
+                    tMin = /*#=*/Numerical.CURVETIME_EPSILON,
+                    tMax = 1 - tMin,
+                    roots = [],
+                    // Keep then range to 0 .. 1 (excluding) in the search for y
+                    // extrema.
+                    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) {
                         // If there are two extrema, renormalize t to the range
                         // of the second range and split again.
                         t = (roots[1] - t) / (1 - t);
diff --git a/src/path/PathItem.js b/src/path/PathItem.js
index b44c23b9..442468f5 100644
--- a/src/path/PathItem.js
+++ b/src/path/PathItem.js
@@ -32,10 +32,12 @@ var PathItem = Item.extend(/** @lends PathItem# */{
      * supported.
      *
      * @param {PathItem} path the other item to find the intersections with
-     * @param {Boolean} [sorted=false] specifies whether the returned
-     * {@link CurveLocation} objects should be sorted by path and offset
+     * @param {Function} [include] a callback function that can be used to
+     * filter out undesired locations right while they are collected.
+     * When defined, it shall return {@true to include a location}.
      * @return {CurveLocation[]} the locations of all intersection between the
      * paths
+     * @see #getCrossings(path)
      * @example {@paperscript} // Finding the intersections between two paths
      * var path = new Path.Rectangle(new Point(30, 25), new Size(50, 50));
      * path.strokeColor = 'black';
@@ -59,88 +61,100 @@ var PathItem = Item.extend(/** @lends PathItem# */{
      *     }
      * }
      */
-    getIntersections: function(path, _matrix) {
+    getIntersections: function(path, include, _matrix, _returnFirst) {
         // NOTE: For self-intersection, path is null. This means you can also
         // just call path.getIntersections() without an argument to get self
         // intersections.
         // NOTE: The hidden argument _matrix is used internally to override the
         // passed path's transformation matrix.
-        return Curve.filterIntersections(this._getIntersections(
-                this !== path ? path : null, _matrix, []));
-    },
-
-    _getIntersections: function(path, matrix, locations, returnFirst) {
-        var curves1 = this.getCurves(),
-            curves2 = path ? path.getCurves() : curves1,
+        var self = this === path || !path, // self-intersections?
             matrix1 = this._matrix.orNullIfIdentity(),
-            matrix2 = path ? (matrix || path._matrix).orNullIfIdentity()
-                : matrix1,
-            length1 = curves1.length,
-            length2 = path ? curves2.length : length1,
-            values2 = [],
-            tMin = /*#=*/Numerical.TOLERANCE,
-            tMax = 1 - tMin;
+            matrix2 = self ? matrix1
+                : (_matrix || path._matrix).orNullIfIdentity();
         // First check the bounds of the two paths. If they don't intersect,
         // we don't need to iterate through their curves.
-        if (path && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
+        if (!self && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
             return [];
+        var curves1 = this.getCurves(),
+            curves2 = self ? curves1 : path.getCurves(),
+            length1 = curves1.length,
+            length2 = self ? length1 : curves2.length,
+            values2 = [],
+            arrays = [],
+            locations,
+            path;
+        // Cache values for curves2 as we re-iterate them for each in curves1.
         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) {
-                // First check for self-intersections within the same curve
-                var seg1 = curve1.getSegment1(),
-                    seg2 = curve1.getSegment2(),
-                    h1 = seg1._handleOut,
-                    h2 = seg2._handleIn;
-                // Check if extended handles of endpoints of this curve
-                // intersects each other. We cannot have a self intersection
-                // within this curve if they don't intersect due to convex-hull
-                // property.
-                if (new Line(seg1._point.subtract(h1), h1.multiply(2), true)
-                        .intersect(new Line(seg2._point.subtract(h2),
-                        h2.multiply(2), true), false)) {
-                    // Self intersecting is found by dividing the curve in two
-                    // and and then applying the normal curve intersection code.
-                    var parts = Curve.subdivide(values1);
-                    Curve.getIntersections(
-                        parts[0], parts[1], curve1, curve1, locations,
-                        function(loc) {
-                            if (loc._parameter <= tMax) {
-                                // Since the curve was split above, we need to
-                                // adjust the parameters for both locations.
-                                loc._parameter /= 2;
-                                loc._parameter2 = 0.5 + loc._parameter2 / 2;
-                                return true;
-                            }
-                        }
-                    );
-                }
+                values1 = self ? values2[i] : curve1.getValues(matrix1),
+                path1 = curve1.getPath();
+            // NOTE: Due to the nature of Curve._getIntersections(), we need to
+            // use separate location arrays per path1, to make sure the
+            // circularity checks are not getting confused by locations on
+            // separate paths. We are flattening the separate arrays at the end.
+            if (path1 !== path) {
+                path = path1;
+                locations = [];
+                arrays.push(locations);
+            }
+            if (self) {
+                // First check for self-intersections within the same curve.
+                Curve._getSelfIntersection(values1, curve1, locations, {
+                    include: include,
+                    // Only possible if there is only one closed curve:
+                    startConnected: length1 === 1 &&
+                            curve1.getPoint1().equals(curve1.getPoint2())
+                });
             }
             // Check for intersections with other curves. For self intersection,
             // we can start at i + 1 instead of 0
-            for (var j = path ? 0 : i + 1; j < length2; j++) {
+            for (var j = self ? i + 1 : 0; j < length2; j++) {
                 // There might be already one location from the above
                 // self-intersection check:
-                if (returnFirst && locations.length)
-                    break;
-                Curve.getIntersections(
-                    values1, values2[j], curve1, curves2[j], locations,
-                    // Avoid end point intersections on consecutive curves when
-                    // self intersecting.
-                    !path && (j === i + 1 || j === length2 - 1 && i === 0)
-                        && function(loc) {
-                            var t = loc._parameter;
-                            return t >= tMin && t <= tMax;
-                        }
+                if (_returnFirst && locations.length)
+                    return locations;
+                var curve2 = curves2[j];
+                // Avoid end point intersections on consecutive curves when
+                // self intersecting.
+                Curve._getIntersections(
+                    values1, values2[j], curve1, curve2, locations,
+                    {
+                        include: include,
+                        // Do not compare indices here to determine connection,
+                        // since one array of curves can contain curves from
+                        // separate sup-paths of a compound path.
+                        startConnected: self && curve1.getPrevious() === curve2,
+                        endConnected: self && curve1.getNext() === curve2
+                    }
                 );
             }
         }
+        // Now flatten the list of location arrays to one array and return it.
+        locations = [];
+        for (var i = 0, l = arrays.length; i < l; i++) {
+            locations.push.apply(locations, arrays[i]);
+        }
         return locations;
     },
 
+    /**
+     * Returns all crossings between two {@link PathItem} items as an array
+     * of {@link CurveLocation} objects. {@link CompoundPath} items are also
+     * supported.
+     * Crossings are intersections where the paths actually are crossing each
+     * other, as opposed to simply touching.
+     *
+     * @param {PathItem} path the other item to find the crossings with
+     * @see #getIntersections(path)
+     */
+    getCrossings: function(path) {
+        return this.getIntersections(path, function(inter) {
+            return inter.isCrossing();
+        });
+    },
+
     _asPathItem: function() {
         // See Item#_asPathItem()
         return this;
diff --git a/src/path/PathIterator.js b/src/path/PathIterator.js
index 1a997798..e610cf53 100644
--- a/src/path/PathIterator.js
+++ b/src/path/PathIterator.js
@@ -58,7 +58,7 @@ var PathIterator = Base.extend({
                     // appears to offer a good trade-off between speed and
                     // precision for display purposes.
                     && !Curve.isFlatEnough(curve, tolerance || 0.25)) {
-                var split = Curve.subdivide(curve),
+                var split = Curve.subdivide(curve, 0.5),
                     halfT = (minT + maxT) / 2;
                 // Recursively subdivide and compute parts again.
                 computeParts(split[0], index, minT, halfT);
diff --git a/src/path/Segment.js b/src/path/Segment.js
index 3690b02c..c42bbb76 100644
--- a/src/path/Segment.js
+++ b/src/path/Segment.js
@@ -119,7 +119,7 @@ var Segment = Base.extend(/** @lends Segment# */{
             // Nothing
         } else if (count === 1) {
             // Note: This copies from existing segments through accessors.
-            if (arg0.point) {
+            if ('point' in arg0) {
                 point = arg0.point;
                 handleIn = arg0.handleIn;
                 handleOut = arg0.handleOut;
@@ -145,9 +145,10 @@ var Segment = Base.extend(/** @lends Segment# */{
     },
 
     _serialize: function(options) {
-        // If it is straight, only serialize point, otherwise handles too.
-        return Base.serialize(this.isStraight() ? this._point
-                : [this._point, this._handleIn, this._handleOut],
+        // If it is has no handles, only serialize point, otherwise handles too.
+        return Base.serialize(this.hasHandles()
+                ? [this._point, this._handleIn, this._handleOut]
+                : this._point,
                 options, true);
     },
 
@@ -229,87 +230,27 @@ var Segment = Base.extend(/** @lends Segment# */{
     },
 
     /**
-     * Checks whether the segment has curve handles defined, meaning it is not
-     * a straight segment.
+     * Checks if the segment has any curve handles set.
      *
-     * @return {Boolean} {@true if the segment has handles defined}
+     * @return {Boolean} {@true if the segment has handles set}
+     * @see Segment#getHandleIn()
+     * @see Segment#getHandleOut()
      * @see Curve#hasHandles()
      * @see Path#hasHandles()
      */
     hasHandles: function() {
-        return !this.isStraight();
+        return !this._handleIn.isZero() || !this._handleOut.isZero();
     },
 
     /**
-     * Checks whether the segment is straight, meaning it has no curve handles
-     * defined. If two straight segments follow each each other in a path, the
-     * curve between them will appear as a straight line.
-     * Note that this is not the same as {@link #isLinear()}, which performs a
-     * full linearity check that includes handles running collinear to the line
-     * direction.
-     *
-     * @return {Boolean} {@true if the segment is straight}
-     * @see Curve#isStraight()
+     * Clears the segment's handles by setting their coordinates to zero,
+     * turning the segment into a corner.
      */
-    isStraight: function() {
-        return this._handleIn.isZero() && this._handleOut.isZero();
+    clearHandles: function() {
+        this._handleIn.set(0, 0);
+        this._handleOut.set(0, 0);
     },
 
-    /**
-     * Checks if the curve that starts in this segment appears as a line. This
-     * can mean that it has no handles defined, or that the handles run
-     * collinear with the line.
-     *
-     * @return {Boolean} {@true if the curve is linear}
-     * @see Curve#isLinear()
-     * @see Path#isLinear()
-     */
-    isLinear: function() {
-        return Segment.isLinear(this, this.getNext());
-    },
-
-    /**
-     * Checks if the the two segments are the beginning of two lines that are
-     * collinear, meaning they run in parallel.
-     *
-     * @param {Segment} the other segment to check against
-     * @return {Boolean} {@true if the two lines are collinear}
-     * @see Curve#isCollinear(curve)
-     */
-    isCollinear: function(segment) {
-        return Segment.isCollinear(this, this.getNext(),
-                segment, segment.getNext());
-    },
-
-    // TODO: Remove version with typo after a while (deprecated June 2015)
-    isColinear: '#isCollinear',
-
-    /**
-     * Checks if the segment is connecting two lines that are orthogonal,
-     * meaning they connect at an 90° angle.
-     *
-     * @return {Boolean} {@true if the two lines connected by this segment are
-     * orthogonal}
-     */
-    isOrthogonal: function() {
-        return Segment.isOrthogonal(this.getPrevious(), this, this.getNext());
-    },
-
-    /**
-     * Checks if the segment is the beginning of an orthogonal arc, as used in
-     * the construction of circles and ellipses.
-     *
-     * @return {Boolean} {@true if the segment is the beginning of an orthogonal
-     * arc}
-     * @see Curve#isOrthogonalArc()
-     */
-    isOrthogonalArc: function() {
-        return Segment.isOrthogonalArc(this, this.getNext());
-    },
-
-    // TODO: Remove a while (deprecated August 2015)
-    isArc: '#isOrthogonalArc',
-
     _selectionState: 0,
 
     /**
@@ -452,10 +393,45 @@ var Segment = Base.extend(/** @lends Segment# */{
     },
 
     /**
-     * Returns the reversed the segment, without modifying the segment itself.
+     * Checks if the this is the first segment in the {@link Path#segments}
+     * array.
+     *
+     * @return {Boolean} {@true if this is the first segment}
+     */
+    isFirst: function() {
+        return this._index === 0;
+    },
+
+    /**
+     * Checks if the this is the last segment in the {@link Path#segments}
+     * array.
+     *
+     * @return {Boolean} {@true if this is the last segment}
+     */
+    isLast: function() {
+        var path = this._path;
+        return path && this._index === path._segments.length - 1 || false;
+    },
+
+    /**
+     * Reverses the {@link #handleIn} and {@link #handleOut} vectors of this
+     * segment. Note: the actual segment is modified, no copy is created.
      * @return {Segment} the reversed segment
      */
     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);
+    },
+
+    /**
+     * Returns the reversed the segment, without modifying the segment itself.
+     * @return {Segment} the reversed segment
+     */
+    reversed: function() {
         return new Segment(this._point, this._handleOut, this._handleIn);
     },
 
@@ -564,53 +540,5 @@ var Segment = Base.extend(/** @lends Segment# */{
             }
         }
         return coords;
-    },
-
-   statics: {
-        // These statics are shared between Segment and Curve, for versions of
-        // these methods that are implemented in both places.
-
-        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) {
-            // TODO: This assumes isStraight(), while isLinear() allows handles!
-            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) {
-            // TODO: This assumes isStraight(), while isLinear() allows handles!
-            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 = /*#=*/Numerical.KAPPA;
-            // Look at handle length and the distance to the imaginary corner
-            // point and see if it their relation is kappa.
-            if (handle1.isOrthogonal(handle2)) {
-                var pt1 = seg1._point,
-                    pt2 = seg2._point,
-                    // Find the corner point by intersecting the lines described
-                    // by both handles:
-                    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;
-        },
     }
 });
diff --git a/src/project/Project.js b/src/project/Project.js
index b5a96710..e9316366 100644
--- a/src/project/Project.js
+++ b/src/project/Project.js
@@ -304,7 +304,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
      * and may contain a combination of the following values:
      *
      * @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
-     * {Number} the tolerance of the hit-test in points
+     * {Number} the tolerance of the hit-test
      * @option options.class {Function} only hit-test again a certain item class
      * and its sub-classes: {@code Group, Layer, Path, CompoundPath,
      * Shape, Raster, PlacedSymbol, PointText}, etc
diff --git a/src/project/Symbol.js b/src/project/Symbol.js
index 6e8d25f7..736b53df 100644
--- a/src/project/Symbol.js
+++ b/src/project/Symbol.js
@@ -133,7 +133,7 @@ var Symbol = Base.extend(/** @lends Symbol# */{
     /**
      * Places in instance of the symbol in the project.
      *
-     * @param [position] The position of the placed symbol
+     * @param {Point} [position] the position of the placed symbol
      * @return {PlacedSymbol}
      */
     place: function(position) {
diff --git a/src/style/Color.js b/src/style/Color.js
index 3e1155f2..07d9966d 100644
--- a/src/style/Color.js
+++ b/src/style/Color.js
@@ -1112,7 +1112,8 @@ var Color = Base.extend(new function() {
             }
         }
     });
-}, new function() {
+},
+new function() {
     var operators = {
         add: function(a, b) {
             return a + b;
diff --git a/src/style/Style.js b/src/style/Style.js
index 0ebe5bff..d4d0db31 100644
--- a/src/style/Style.js
+++ b/src/style/Style.js
@@ -309,7 +309,7 @@ var Style = Base.extend(new function() {
     /**
      * @private
      * @bean
-     * @deprecated use {@link #getFontFamily()} instead.
+     * @deprecated use {@link #fontFamily} instead.
      */
     getFont: '#getFontFamily',
     setFont: '#setFontFamily',
@@ -582,7 +582,7 @@ var Style = Base.extend(new function() {
      */
 
     /**
-     * The font size of text content, as {@Number} in pixels, or as {@String}
+     * The font size of text content, as a number in pixels, or as a string
      * with optional units {@code 'px'}, {@code 'pt'} and {@code 'em'}.
      *
      * @name Style#fontSize
@@ -592,12 +592,12 @@ var Style = Base.extend(new function() {
 
     /**
      *
-     * The font-family to be used in text content, as one {@String}.
-     * @deprecated use {@link #fontFamily} instead.
+     * The font-family to be used in text content, as one string.
      *
      * @name Style#font
      * @default 'sans-serif'
      * @type String
+     * @deprecated use {@link #fontFamily} instead.
      */
 
     /**
diff --git a/src/text/TextItem.js b/src/text/TextItem.js
index 824bf561..8aeb6168 100644
--- a/src/text/TextItem.js
+++ b/src/text/TextItem.js
@@ -120,7 +120,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
      */
 
     /**
-     * The font size of text content, as {@Number} in pixels, or as {@String}
+     * The font size of text content, as a number in pixels, or as a string
      * with optional units {@code 'px'}, {@code 'pt'} and {@code 'em'}.
      *
      * @name TextItem#fontSize
@@ -130,7 +130,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
 
     /**
      *
-     * The font-family to be used in text content, as one {@String}.
+     * The font-family to be used in text content, as one string.
      * @deprecated use {@link #fontFamily} instead.
      *
      * @name TextItem#font
@@ -159,7 +159,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
     /**
      * @private
      * @bean
-     * @deprecated use {@link #getStyle()} instead.
+     * @deprecated use {@link #style} instead.
      */
     getCharacterStyle: '#getStyle',
     setCharacterStyle: '#setStyle',
@@ -167,7 +167,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
     /**
      * @private
      * @bean
-     * @deprecated use {@link #getStyle()} instead.
+     * @deprecated use {@link #style} instead.
      */
     getParagraphStyle: '#getStyle',
     setParagraphStyle: '#setStyle'
diff --git a/src/util/Numerical.js b/src/util/Numerical.js
index a970267a..fc0f7159 100644
--- a/src/util/Numerical.js
+++ b/src/util/Numerical.js
@@ -60,12 +60,15 @@ var Numerical = new function() {
     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 /** @lends Numerical */{
-        TOLERANCE: TOLERANCE,
+        TOLERANCE: 1e-6,
         // Precision when comparing against 0
         // References:
         //  http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
@@ -78,28 +81,49 @@ var Numerical = new function() {
          */
         EPSILON: EPSILON,
         /**
-         * MACHINE_EPSILON for a double precision (Javascript Number) is
-         * 2.220446049250313e-16. (try this in the js console)
+         * The machine epsilon for a double precision (Javascript Number) is
+         * 2.220446049250313e-16. (try this in the js console:
          *     (function(){for(var e=1;1<1+e/2;)e/=2;return e}())
          *
-         * Here the constant MACHINE_EPSILON refers to the constants 'δ' and 'ε'
-         * such that, the error introduced by addition, multiplication
-         * on a 64bit float (js Number) will be less than δ and ε. That is to
-         * say, for all X and Y representable by a js Number object, S and P
-         * be their 'exact' sum and product respectively, then
+         * The constant MACHINE_EPSILON here refers to the constants δ and ε
+         * such that, the error introduced by addition, multiplication on a
+         * 64bit float (js Number) will be less than δ and ε. That is to say,
+         * for all X and Y representable by a js Number object, S and P be their
+         * 'exact' sum and product respectively, then
          * |S - (x+y)| <= δ|S|, and |P - (x*y)| <= ε|P|.
-         * This amounts to about half of the actual MACHINE_EPSILON
+         * This amounts to about half of the actual machine epsilon.
          */
         MACHINE_EPSILON: MACHINE_EPSILON,
+        /**
+         * The epsilon to be used when handling curve-time parameters. This
+         * cannot be smaller, because errors add up to around 8e-7 in the bezier
+         * fat-line clipping code as a result of recursive sub-division.
+         */
+        CURVETIME_EPSILON: 8e-7,
+        /**
+         * The epsilon to be used when performing "geometric" checks, such as
+         * point distances and examining cross products to check for
+         * collinearity.
+         */
+        GEOMETRIC_EPSILON: 4e-7, // NOTE: 1e-7 doesn't work in some edge-cases!
+        /**
+         * The epsilon to be used when performing winding contribution checks.
+         */
+        WINDING_EPSILON: 2e-7, // NOTE: 1e-7 doesn't work in some edge-cases!
+        /**
+         * The epsilon to be used when performing "trigonometric" checks, such
+         * as examining cross products to check for collinearity.
+         */
+        TRIGONOMETRIC_EPSILON: 1e-7,
         // Kappa, see: http://www.whizkidtech.redprince.net/bezier/circle/kappa/
         KAPPA: 4 * (sqrt(2) - 1) / 3,
 
         /**
-         * Check if the value is 0, within a tolerance defined by
+         * Checks if the value is 0, within a tolerance defined by
          * Numerical.EPSILON.
          */
         isZero: function(val) {
-            return abs(val) <= EPSILON;
+            return val >= -EPSILON && val <= EPSILON;
         },
 
         /**
@@ -158,7 +182,7 @@ var Numerical = new function() {
          *
          * References:
          *  Kahan W. - "To Solve a Real Cubic Equation"
-         *   http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
+         *  http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
          *  Blinn J. - "How to solve a Quadratic Equation"
          *
          * @param {Number} a the quadratic term
@@ -174,10 +198,14 @@ var Numerical = new function() {
          */
         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;
+            // a, b, c are expected to be the coefficients of the equation:
+            // Ax² - 2Bx + C == 0, so we take b = -B/2:
+            b /= -2;
             D = b * b - a * c; // Discriminant
             // If the discriminant is very small, we can try to pre-condition
             // the coefficients, so that we may get better accuracy
@@ -188,8 +216,8 @@ var Numerical = new function() {
                     // We multiply with a factor to normalize the coefficients.
                     // The factor is just the nearest exponent of 10, big enough
                     // to raise all the coefficients to nearly [-1, +1] range.
-                    var mult = pow(10, abs(
-                        Math.floor(Math.log(gmC) * Math.LOG10E)));
+                    var mult = pow(10,
+                            abs(Math.floor(Math.log(gmC) * Math.LOG10E)));
                     if (!isFinite(mult))
                         mult = 0;
                     a *= mult;
@@ -204,29 +232,25 @@ var Numerical = new function() {
                 if (abs(B) < EPSILON)
                     return abs(c) < EPSILON ? -1 : 0;
                 x1 = -c / B;
-            } else {
-                // No real roots if D < 0
-                if (D >= -MACHINE_EPSILON) {
-                    D = D < 0 ? 0 : D;
-                    var R = sqrt(D);
-                    // Try to minimise floating point noise.
-                    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;
-                    }
-                    // Do we actually have two real roots?
-                    // count = D > MACHINE_EPSILON ? 2 : 1;
+            } else if (D >= -MACHINE_EPSILON) { // No real roots if D < 0
+                var Q = D < 0 ? 0 : sqrt(D),
+                    R = b + (b < 0 ? -Q : Q);
+                // Try to minimize floating point noise.
+                if (R === 0) {
+                    x1 = c / a;
+                    x2 = -x1;
+                } else {
+                    x1 = R / a;
+                    x2 = c / R;
                 }
             }
-            if (isFinite(x1) && (min == null || x1 >= min && x1 <= max))
-                roots[count++] = x1;
+            // We need to include EPSILON in the comparisons with min / max,
+            // as some solutions are ever so lightly out of bounds.
+            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;
         },
 
@@ -321,8 +345,8 @@ var Numerical = new function() {
             // The cubic has been deflated to a quadratic.
             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;
         }
     };
diff --git a/src/view/CanvasView.js b/src/view/CanvasView.js
index 996ad97e..910ffbe0 100644
--- a/src/view/CanvasView.js
+++ b/src/view/CanvasView.js
@@ -144,8 +144,8 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
         project._needsUpdate = false;
         return true;
     }
-}, new function() { // Item based mouse handling:
-
+},
+new function() { // Item based mouse handling:
     var downPoint,
         lastPoint,
         overPoint,
diff --git a/src/view/View.js b/src/view/View.js
index f4708ea8..c40d8769 100644
--- a/src/view/View.js
+++ b/src/view/View.js
@@ -675,8 +675,8 @@ var View = Base.extend(Emitter, /** @lends View# */{
             return new CanvasView(project, element);
         }
     }
-}, new function() {
-    // Injection scope for mouse events on the browser
+},
+new function() { // Injection scope for mouse events on the browser
 /*#*/ if (__options.environment == 'browser') {
     var tool,
         prevFocus,
diff --git a/test/js/helpers.js b/test/js/helpers.js
index c483fb27..cb2d5b03 100644
--- a/test/js/helpers.js
+++ b/test/js/helpers.js
@@ -286,7 +286,7 @@ function asyncTest(testName, expected) {
         var project = new Project();
         expected(function() {
             project.remove();
-            start();
+            QUnit.start();
         });
     });
 }
diff --git a/test/tests/Curve.js b/test/tests/Curve.js
index f125f1c3..87383ec5 100644
--- a/test/tests/Curve.js
+++ b/test/tests/Curve.js
@@ -161,7 +161,7 @@ test('Curve#getParameterAt()', function() {
         var t2 = curve.getParameterAt(o2);
         equals(t1, t2, 'Curve parameter at offset ' + o1
                 + ' should be the same value as at offset' + o2,
-                Numerical.TOLERANCE);
+                Numerical.CURVETIME_EPSILON);
     }
 
     equals(curve.getParameterAt(curve.length + 1), null,
@@ -178,3 +178,48 @@ test('Curve#getLocationAt()', function() {
             'Should return null when offset is out of range.');
 //            'Should return null when point is not on the curve.');
 });
+
+test('Curve#isStraight()', function() {
+    equals(function() {
+        return new Curve([100, 100], null, null, [200, 200]).isStraight();
+    }, true);
+    equals(function() {
+        return new Curve([100, 100], [-50, -50], null, [200, 200]).isStraight();
+    }, false);
+    equals(function() {
+        return new Curve([100, 100], [50, 50], null, [200, 200]).isStraight();
+    }, true);
+    equals(function() {
+        return new Curve([100, 100], [50, 50], [-50, -50], [200, 200]).isStraight();
+    }, true);
+    equals(function() {
+        return new Curve([100, 100], [50, 50], [50, 50], [200, 200]).isStraight();
+    }, false);
+    equals(function() {
+        return new Curve([100, 100], null, [-50, -50], [200, 200]).isStraight();
+    }, true);
+    equals(function() {
+        return new Curve([100, 100], null, [50, 50], [200, 200]).isStraight();
+    }, false);
+    equals(function() {
+        return new Curve([100, 100], null, null, [100, 100]).isStraight();
+    }, true);
+    equals(function() {
+        return new Curve([100, 100], [50, 50], null, [100, 100]).isStraight();
+    }, false);
+    equals(function() {
+        return new Curve([100, 100], null, [-50, -50], [100, 100]).isStraight();
+    }, false);
+});
+
+test('Curve#isLinear()', function() {
+    equals(function() {
+        return new Curve([100, 100], [100 / 3, 100 / 3], [-100 / 3, -100 / 3], [200, 200]).isLinear();
+    }, true);
+    equals(function() {
+        return new Curve([100, 100], null, null, [100, 100]).isLinear();
+    }, true);
+    equals(function() {
+        return new Curve([100, 100], null, null, [200, 200]).isLinear();
+    }, false);
+});
diff --git a/test/tests/Matrix.js b/test/tests/Matrix.js
index 350a1c3d..6a5c1967 100644
--- a/test/tests/Matrix.js
+++ b/test/tests/Matrix.js
@@ -16,8 +16,7 @@ test('Decomposition: rotate()', function() {
         var m = new Matrix().rotate(a),
             s = 'new Matrix().rotate(' + a + ')';
         equals(m.getRotation(), Base.pick(ea, a),
-            s + '.getRotation()',
-            Numerical.TOLERANCE);
+            s + '.getRotation()');
         equals(m.getScaling(), new Point(1, 1),
             s + '.getScaling()');
     }
@@ -42,8 +41,7 @@ test('Decomposition: scale()', function() {
         var m = new Matrix().scale(sx, sy),
             s = 'new Matrix().scale(' + sx + ', ' + sy + ')';
         equals(m.getRotation(), ea || 0,
-                s + '.getRotation()',
-                Numerical.TOLERANCE);
+                s + '.getRotation()');
         equals(m.getScaling(), new Point(Base.pick(ex, sx), Base.pick(ey, sy)),
                 s + '.getScaling()');
     }
@@ -64,8 +62,7 @@ test('Decomposition: rotate() & scale()', function() {
         var m = new Matrix().scale(sx, sy).rotate(a),
             s = 'new Matrix().scale(' + sx + ', ' + sy + ').rotate(' + a + ')';
         equals(m.getRotation(), ea || a,
-                s + '.getRotation()',
-                Numerical.TOLERANCE);
+                s + '.getRotation()');
         equals(m.getScaling(), new Point(Base.pick(ex, sx), Base.pick(ey, sy)),
                 s + '.getScaling()');
     }
diff --git a/test/tests/Path_Boolean.js b/test/tests/Path_Boolean.js
index 4293c6b0..4967693b 100644
--- a/test/tests/Path_Boolean.js
+++ b/test/tests/Path_Boolean.js
@@ -51,5 +51,5 @@ test('ring.subtract(square); #610', function() {
     var ring = outer.subtract(inner);
     var result = ring.subtract(square);
 
-    equals(result.pathData, 'M-10,131.62689c-68.2302,-5.11075 -122,-62.08951 -122,-131.62689c0,-69.53737 53.7698,-126.51614 122,-131.62689l0,32.12064c-50.53323,5.01724 -90,47.65277 -90,99.50625c0,51.85348 39.46677,94.489 90,99.50625z');
+    equals(result.pathData, 'M-132,0c0,-69.53737 53.7698,-126.51614 122,-131.62689l0,32.12064c-50.53323,5.01724 -90,47.65277 -90,99.50625c0,51.85348 39.46677,94.489 90,99.50625l0,32.12064c-68.2302,-5.11075 -122,-62.08951 -122,-131.62689z');
 });
diff --git a/test/tests/Path_Curves.js b/test/tests/Path_Curves.js
index c697e9b1..faebbd47 100644
--- a/test/tests/Path_Curves.js
+++ b/test/tests/Path_Curves.js
@@ -107,10 +107,10 @@ test('Curve list after removing a segment - 2', function() {
     }, 1, 'After removing the last segment, we should be left with one curve');
 });
 
-test('Splitting a straight path should produce straight segments', function() {
-    var path = new Path.Line([0, 0], [50, 50]);
-    var path2 = path.split(0, 0.5);
+test('Splitting a straight path should produce segments without handles', function() {
+    var path1 = new Path.Line([0, 0], [50, 50]);
+    var path2 = path1.split(0, 0.5);
     equals(function() {
-        return path2.firstSegment.isStraight();
+        return !path1.lastSegment.hasHandles() && !path2.firstSegment.hasHandles();
     }, true);
 });
diff --git a/test/tests/Segment.js b/test/tests/Segment.js
index 3c8eb957..b28de7b7 100644
--- a/test/tests/Segment.js
+++ b/test/tests/Segment.js
@@ -43,7 +43,7 @@ test('new Segment(size)', function() {
 
 test('segment.reverse()', function() {
     var segment = new Segment(new Point(10, 10), new Point(5, 5), new Point(15, 15));
-    segment = segment.reverse();
+    segment.reverse();
     equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 15, y: 15 }, handleOut: { x: 5, y: 5 } }');
 });
 
diff --git a/test/tests/TextItem.js b/test/tests/TextItem.js
index b3d218b4..77cbc5c9 100644
--- a/test/tests/TextItem.js
+++ b/test/tests/TextItem.js
@@ -14,7 +14,7 @@ module('TextItem');
 
 test('PointText', function() {
     var text = new PointText({
-        fontFamily: 'Arial',
+        fontFamily: 'Helvetica, Arial',
         fontSize: 14,
         point: [100, 100],
         content: 'Hello World!'
@@ -22,7 +22,7 @@ test('PointText', function() {
     equals(text.fillColor, new Color(0, 0, 0), 'text.fillColor should be black by default');
     equals(text.point, new Point(100, 100), 'text.point');
     equals(text.bounds.point, new Point(100, 87.4), 'text.bounds.point');
-    equals(text.bounds.size, new Size(77, 16.8), 'text.bounds.size', { tolerance: 1.0 });
+    equals(text.bounds.size, new Size(76, 16.8), 'text.bounds.size', { tolerance: 1.0 });
     equals(function() {
         return text.hitTest(text.bounds.center) != null;
     }, true);