Reland of ix zero-length tangent (patchset #1 id:1 of https://codereview.chromium.org/1312243002/ )

Reason for revert:
Layout suppression has landed, and verified that Skia gm test changes are correct.

Original issue's description:
> Revert of fix zero-length tangent (patchset #2 id:20001 of https://codereview.chromium.org/1311273002/ )
>
> Reason for revert:
> causes layout test to draw differently -- new drawing is more correct. Reverting until layout test ignore is landed.
>
> Original issue's description:
> > fix zero-length tangent
> >
> > If the end point and the control point are the same, computing
> > the tangent will result in (0, 0). In this case, use the prior
> > control point instead.
> >
> > R=reed@google.com
> >
> > BUG=skia:4191
> >
> > Committed: https://skia.googlesource.com/skia/+/7544124fb8ee744f68f549a353f8a9163cd7432d
>
> TBR=reed@google.com
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=skia:4191
>
> Committed: https://skia.googlesource.com/skia/+/91298b47c547b2ab4697038c04685af957bd1416

TBR=reed@google.com
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=skia:4191

Review URL: https://codereview.chromium.org/1320473002
diff --git a/gm/strokes.cpp b/gm/strokes.cpp
index 7382390..0bcb039 100644
--- a/gm/strokes.cpp
+++ b/gm/strokes.cpp
@@ -266,6 +266,60 @@
     typedef skiagm::GM INHERITED;
 };
 
+// Test stroking for curves that produce degenerate tangents when t is 0 or 1 (see bug 4191)
+class Strokes5GM : public skiagm::GM {
+public:
+    Strokes5GM() {}
+
+protected:
+
+    SkString onShortName() override {
+        return SkString("zero_control_stroke");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(W, H*2);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        SkPaint p;
+        p.setColor(SK_ColorRED);
+        p.setAntiAlias(true);
+        p.setStyle(SkPaint::kStroke_Style);
+        p.setStrokeWidth(40);
+        p.setStrokeCap(SkPaint::kButt_Cap);
+
+        SkPath path;
+        path.moveTo(157.474f,111.753f);
+        path.cubicTo(128.5f,111.5f,35.5f,29.5f,35.5f,29.5f);
+        canvas->drawPath(path, p);
+        path.reset();
+        path.moveTo(250, 50);
+        path.quadTo(280, 80, 280, 80);
+        canvas->drawPath(path, p);
+        path.reset();
+        path.moveTo(150, 50);
+        path.conicTo(180, 80, 180, 80, 0.707f);
+        canvas->drawPath(path, p);
+
+        path.reset();
+        path.moveTo(157.474f,311.753f);
+        path.cubicTo(157.474f,311.753f,85.5f,229.5f,35.5f,229.5f);
+        canvas->drawPath(path, p);
+        path.reset();
+        path.moveTo(280, 250);
+        path.quadTo(280, 250, 310, 280);
+        canvas->drawPath(path, p);
+        path.reset();
+        path.moveTo(180, 250);
+        path.conicTo(180, 250, 210, 280, 0.707f);
+        canvas->drawPath(path, p);
+    }
+
+private:
+    typedef skiagm::GM INHERITED;
+};
+
 
 //////////////////////////////////////////////////////////////////////////////
 
@@ -273,8 +327,10 @@
 static skiagm::GM* F1(void*) { return new Strokes2GM; }
 static skiagm::GM* F2(void*) { return new Strokes3GM; }
 static skiagm::GM* F3(void*) { return new Strokes4GM; }
+static skiagm::GM* F4(void*) { return new Strokes5GM; }
 
 static skiagm::GMRegistry R0(F0);
 static skiagm::GMRegistry R1(F1);
 static skiagm::GMRegistry R2(F2);
 static skiagm::GMRegistry R3(F3);
+static skiagm::GMRegistry R4(F4);
diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp
index 6afd9d7..7462009 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -130,13 +130,6 @@
 #endif
 }
 
-static SkScalar eval_quad_derivative(const SkScalar src[], SkScalar t) {
-    SkScalar A = src[4] - 2 * src[2] + src[0];
-    SkScalar B = src[2] - src[0];
-
-    return 2 * SkScalarMulAdd(A, t, B);
-}
-
 void SkQuadToCoeff(const SkPoint pts[3], SkPoint coeff[3]) {
     Sk2s p0 = from_point(pts[0]);
     Sk2s p1 = from_point(pts[1]);
@@ -157,8 +150,7 @@
         pt->set(eval_quad(&src[0].fX, t), eval_quad(&src[0].fY, t));
     }
     if (tangent) {
-        tangent->set(eval_quad_derivative(&src[0].fX, t),
-                     eval_quad_derivative(&src[0].fY, t));
+        *tangent = SkEvalQuadTangentAt(src, t);
     }
 }
 
@@ -179,6 +171,12 @@
 }
 
 SkVector SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t) {
+    // The derivative equation is 2(b - a +(a - 2b +c)t). This returns a
+    // zero tangent vector when t is 0 or 1, and the control point is equal
+    // to the end point. In this case, use the quad end points to compute the tangent.
+    if ((t == 0 && src[0] == src[1]) || (t == 1 && src[1] == src[2])) {
+        return src[2] - src[0];
+    }
     SkASSERT(src);
     SkASSERT(t >= 0 && t <= SK_Scalar1);
 
@@ -398,8 +396,22 @@
         loc->set(eval_cubic(&src[0].fX, t), eval_cubic(&src[0].fY, t));
     }
     if (tangent) {
-        tangent->set(eval_cubic_derivative(&src[0].fX, t),
-                     eval_cubic_derivative(&src[0].fY, t));
+        // The derivative equation returns a zero tangent vector when t is 0 or 1, and the
+        // adjacent control point is equal to the end point. In this case, use the
+        // next control point or the end points to compute the tangent.
+        if ((t == 0 && src[0] == src[1]) || (t == 1 && src[2] == src[3])) {
+            if (t == 0) {
+                *tangent = src[2] - src[0];
+            } else {
+                *tangent = src[3] - src[1];
+            }
+            if (!tangent->fX && !tangent->fY) {
+                *tangent = src[3] - src[0];
+            }
+        } else {
+            tangent->set(eval_cubic_derivative(&src[0].fX, t),
+                         eval_cubic_derivative(&src[0].fY, t));
+        }
     }
     if (curvature) {
         curvature->set(eval_cubic_2ndDerivative(&src[0].fX, t),
@@ -1176,12 +1188,6 @@
     coeff[2] = wP10;
 }
 
-static SkScalar conic_eval_tan(const SkScalar coord[], SkScalar w, SkScalar t) {
-    SkScalar coeff[3];
-    conic_deriv_coeff(coord, w, coeff);
-    return t * (t * coeff[0] + coeff[1]) + coeff[2];
-}
-
 static bool conic_find_extrema(const SkScalar src[], SkScalar w, SkScalar* t) {
     SkScalar coeff[3];
     conic_deriv_coeff(src, w, coeff);
@@ -1232,8 +1238,7 @@
                 conic_eval_pos(&fPts[0].fY, fW, t));
     }
     if (tangent) {
-        tangent->set(conic_eval_tan(&fPts[0].fX, fW, t),
-                     conic_eval_tan(&fPts[0].fY, fW, t));
+        *tangent = evalTangentAt(t);
     }
 }
 
@@ -1291,6 +1296,12 @@
 }
 
 SkVector SkConic::evalTangentAt(SkScalar t) const {
+    // The derivative equation returns a zero tangent vector when t is 0 or 1,
+    // and the control point is equal to the end point.
+    // In this case, use the conic endpoints to compute the tangent.
+    if ((t == 0 && fPts[0] == fPts[1]) || (t == 1 && fPts[1] == fPts[2])) {
+        return fPts[2] - fPts[0];
+    }
     Sk2s p0 = from_point(fPts[0]);
     Sk2s p1 = from_point(fPts[1]);
     Sk2s p2 = from_point(fPts[2]);
diff --git a/tests/GeometryTest.cpp b/tests/GeometryTest.cpp
index 00fc779..5aa80d0 100644
--- a/tests/GeometryTest.cpp
+++ b/tests/GeometryTest.cpp
@@ -105,6 +105,67 @@
     }
 }
 
+static void test_quad_tangents(skiatest::Reporter* reporter) {
+    SkPoint pts[] = {
+        {10, 20}, {10, 20}, {20, 30},
+        {10, 20}, {15, 25}, {20, 30},
+        {10, 20}, {20, 30}, {20, 30},
+    };
+    int count = (int) SK_ARRAY_COUNT(pts) / 3;
+    for (int index = 0; index < count; ++index) {
+        SkConic conic(&pts[index * 3], 0.707f);
+        SkVector start = SkEvalQuadTangentAt(&pts[index * 3], 0);
+        SkVector mid = SkEvalQuadTangentAt(&pts[index * 3], .5f);
+        SkVector end = SkEvalQuadTangentAt(&pts[index * 3], 1);
+        REPORTER_ASSERT(reporter, start.fX && start.fY);
+        REPORTER_ASSERT(reporter, mid.fX && mid.fY);
+        REPORTER_ASSERT(reporter, end.fX && end.fY);
+        REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
+        REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
+    }
+}
+
+static void test_conic_tangents(skiatest::Reporter* reporter) {
+    SkPoint pts[] = {
+        { 10, 20}, {10, 20}, {20, 30},
+        { 10, 20}, {15, 25}, {20, 30},
+        { 10, 20}, {20, 30}, {20, 30}
+    };
+    int count = (int) SK_ARRAY_COUNT(pts) / 3;
+    for (int index = 0; index < count; ++index) {
+        SkConic conic(&pts[index * 3], 0.707f);
+        SkVector start = conic.evalTangentAt(0);
+        SkVector mid = conic.evalTangentAt(.5f);
+        SkVector end = conic.evalTangentAt(1);
+        REPORTER_ASSERT(reporter, start.fX && start.fY);
+        REPORTER_ASSERT(reporter, mid.fX && mid.fY);
+        REPORTER_ASSERT(reporter, end.fX && end.fY);
+        REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
+        REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
+    }
+}
+
+static void test_cubic_tangents(skiatest::Reporter* reporter) {
+    SkPoint pts[] = {
+        { 10, 20}, {10, 20}, {20, 30}, {30, 40},
+        { 10, 20}, {15, 25}, {20, 30}, {30, 40},
+        { 10, 20}, {20, 30}, {30, 40}, {30, 40},
+    };
+    int count = (int) SK_ARRAY_COUNT(pts) / 4;
+    for (int index = 0; index < count; ++index) {
+        SkConic conic(&pts[index * 3], 0.707f);
+        SkVector start, mid, end;
+        SkEvalCubicAt(&pts[index * 4], 0, NULL, &start, NULL);
+        SkEvalCubicAt(&pts[index * 4], .5f, NULL, &mid, NULL);
+        SkEvalCubicAt(&pts[index * 4], 1, NULL, &end, NULL);
+        REPORTER_ASSERT(reporter, start.fX && start.fY);
+        REPORTER_ASSERT(reporter, mid.fX && mid.fY);
+        REPORTER_ASSERT(reporter, end.fX && end.fY);
+        REPORTER_ASSERT(reporter, SkScalarNearlyZero(start.cross(mid)));
+        REPORTER_ASSERT(reporter, SkScalarNearlyZero(mid.cross(end)));
+    }
+}
+
 DEF_TEST(Geometry, reporter) {
     SkPoint pts[3], dst[5];
 
@@ -129,4 +190,7 @@
     testChopCubic(reporter);
     test_evalquadat(reporter);
     test_conic(reporter);
+    test_cubic_tangents(reporter);
+    test_quad_tangents(reporter);
+    test_conic_tangents(reporter);
 }