From 9bd399b5b82cfd68500ce7e1cbf70ab5b64cf562 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BCrg=20Lehni?= <juerg@scratchdisk.com>
Date: Sat, 22 Aug 2015 22:06:42 +0200
Subject: [PATCH] Introduce Curve#isStraight() and use it in splitPath() and
 divide() to keep the result of splitting straight curves straight.

Do not use Curve#isLinear(), as that would include curves with collinear handles, and we don't want to set these straight.
---
 src/path/Curve.js            | 28 +++++++++++++++++++-------
 src/path/PathItem.Boolean.js | 39 +++++++++++++++++-------------------
 src/path/Segment.js          | 11 ++++++----
 3 files changed, 46 insertions(+), 32 deletions(-)

diff --git a/src/path/Curve.js b/src/path/Curve.js
index 5f4e4d8e..80ed67e2 100644
--- a/src/path/Curve.js
+++ b/src/path/Curve.js
@@ -298,8 +298,22 @@ var Curve = Base.extend(/** @lends Curve# */{
      * @see Path#hasHandles()
      */
     hasHandles: function() {
-        return !this._segment1._handleOut.isZero()
-                || !this._segment2._handleIn.isZero();
+        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();
     },
 
     /**
@@ -378,19 +392,19 @@ var Curve = Base.extend(/** @lends Curve# */{
      * is within the valid range, {code null} otherwise.
      */
     // TODO: Rename to divideAt()?
-    divide: function(offset, isParameter, ignoreLinear) {
+    divide: function(offset, isParameter, ignoreStraight) {
         var parameter = this._getParameter(offset, isParameter),
             tolerance = /*#=*/Numerical.TOLERANCE,
             res = null;
         // Only divide if not at the beginning or end.
         if (parameter >= tolerance && parameter <= 1 - tolerance) {
             var parts = Curve.subdivide(this.getValues(), parameter),
-                isLinear = ignoreLinear ? false : this.isLinear(),
+                setHandles = ignoreStraight || this.hasHandles(),
                 left = parts[0],
                 right = parts[1];
 
             // Write back the results:
-            if (!isLinear) {
+            if (setHandles) {
                 this._segment1._handleOut.set(left[2] - left[0],
                         left[3] - left[1]);
                 // segment2 is the end segment. By inserting newSegment
@@ -403,8 +417,8 @@ var Curve = Base.extend(/** @lends Curve# */{
             // Create the new segment, convert absolute -> relative:
             var x = left[6], y = left[7],
                 segment = new Segment(new Point(x, y),
-                        !isLinear && new Point(left[4] - x, left[5] - y),
-                        !isLinear && new Point(right[2] - x, right[3] - y));
+                        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) {
diff --git a/src/path/PathItem.Boolean.js b/src/path/PathItem.Boolean.js
index 271fb2d5..589e1d24 100644
--- a/src/path/PathItem.Boolean.js
+++ b/src/path/PathItem.Boolean.js
@@ -191,14 +191,8 @@ PathItem.inject(new function() {
     function splitPath(intersections) {
         var tMin = /*#=*/Numerical.TOLERANCE,
             tMax = 1 - tMin,
-            linearHandles;
-
-        function setLinear() {
-            // 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 = linearHandles.length; i < l; i++)
-                linearHandles[i].set(0, 0);
-        }
+            isStraight = false,
+            straightSegments = [];
 
         for (var i = intersections.length - 1, curve, prev; i >= 0; i--) {
             var loc = intersections[i],
@@ -210,12 +204,7 @@ PathItem.inject(new function() {
                 t /= prev._parameter;
             } else {
                 curve = loc._curve;
-                if (linearHandles)
-                    setLinear();
-                linearHandles = curve.isLinear() ? [
-                        curve._segment1._handleOut,
-                        curve._segment2._handleIn
-                    ] : null;
+                isStraight = curve.isStraight();
             }
             var segment;
             if (t < tMin) {
@@ -223,22 +212,30 @@ PathItem.inject(new function() {
             } else if (t > tMax) {
                 segment = curve._segment2;
             } else {
-                // Split the curve at t, while ignoring linearity of curves,
-                // passing true for ignoreLinear as we don't want to have
-                // parametrically linear curves reset their handles.
+                // 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();
-                if (linearHandles)
-                    linearHandles.push(segment._handleOut, segment._handleIn);
+                // Keep track of segments of once straight curves, so they can
+                // be set back straight at the end.
+                if (isStraight)
+                    straightSegments.push(segment);
             }
             // Link the new segment with the intersection on the other curve
             segment._intersection = loc.getIntersection();
             loc._segment = segment;
             prev = loc;
         }
-        if (linearHandles)
-            setLinear();
+        // 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);
+        }
     }
 
     /**
diff --git a/src/path/Segment.js b/src/path/Segment.js
index 967b29b9..4f0643df 100644
--- a/src/path/Segment.js
+++ b/src/path/Segment.js
@@ -241,12 +241,15 @@ var Segment = Base.extend(/** @lends Segment# */{
     },
 
     /**
-     * Checks whether the segment is straight, meaning it has no curve
-     * handles defined.
-     * If two straight segments are adjacent to each other, the curve between
-     * them will be a straight line.
+     * 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()
      */
     isStraight: function() {
         return this._handleIn.isZero() && this._handleOut.isZero();