zero-length cap fix

Re-land; layout tests are suppressed and gm differences are understood.
A merge conflict prevented a automatic reland.

If the endcap is not butt, draw the endcaps even when the line
has zero length.

If the dash length is zero, generate a zero length line segment.

Treat a move followed by a close as a move followed by a zero-length
line.

TBR=reed@google.com
BUG=422974

Review URL: https://codereview.chromium.org/1314213002
diff --git a/gm/strokes.cpp b/gm/strokes.cpp
index 0bcb039..19ba844 100644
--- a/gm/strokes.cpp
+++ b/gm/strokes.cpp
@@ -8,6 +8,8 @@
 #include "gm.h"
 #include "SkPath.h"
 #include "SkRandom.h"
+#include "SkDashPathEffect.h"
+#include "SkParsePath.h"
 
 #define W   400
 #define H   400
@@ -76,6 +78,76 @@
     typedef skiagm::GM INHERITED;
 };
 
+/* See
+   https://code.google.com/p/chromium/issues/detail?id=422974          and
+   http://jsfiddle.net/1xnku3sg/2/
+ */
+class ZeroLenStrokesGM : public skiagm::GM {
+    SkPath fMoveHfPath, fMoveZfPath, fDashedfPath, fRefPath[4];
+protected:
+    void onOnceBeforeDraw() override {
+
+        SkAssertResult(SkParsePath::FromSVGString("M0,0h0M10,0h0M20,0h0", &fMoveHfPath));
+        SkAssertResult(SkParsePath::FromSVGString("M0,0zM10,0zM20,0z", &fMoveZfPath));
+        SkAssertResult(SkParsePath::FromSVGString("M0,0h25", &fDashedfPath));
+
+        for (int i = 0; i < 3; ++i) {
+            fRefPath[0].addCircle(i * 10.f, 0, 5);
+            fRefPath[1].addCircle(i * 10.f, 0, 10);
+            fRefPath[2].addRect(i * 10.f - 4, -2, i * 10.f + 4, 6);
+            fRefPath[3].addRect(i * 10.f - 10, -10, i * 10.f + 10, 10);
+        }
+    }
+
+    SkString onShortName() override {
+        return SkString("zeroPath");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(W, H*2);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        SkPaint fillPaint, strokePaint, dashPaint;
+        fillPaint.setAntiAlias(true);
+        strokePaint = fillPaint;
+        strokePaint.setStyle(SkPaint::kStroke_Style);
+        for (int i = 0; i < 2; ++i) {
+            fillPaint.setAlpha(255);
+            strokePaint.setAlpha(255);
+            strokePaint.setStrokeWidth(i ? 8.f : 10.f);
+            strokePaint.setStrokeCap(i ? SkPaint::kSquare_Cap : SkPaint::kRound_Cap);
+            canvas->save();
+            canvas->translate(10 + i * 100.f, 10);
+            canvas->drawPath(fMoveHfPath, strokePaint);
+            canvas->translate(0, 20);
+            canvas->drawPath(fMoveZfPath, strokePaint);
+            dashPaint = strokePaint;
+            const SkScalar intervals[] = { 0, 10 };
+            dashPaint.setPathEffect(SkDashPathEffect::Create(intervals, 2, 0))->unref();
+            SkPath fillPath;
+            dashPaint.getFillPath(fDashedfPath, &fillPath);
+            canvas->translate(0, 20);
+            canvas->drawPath(fDashedfPath, dashPaint);
+            canvas->translate(0, 20);
+            canvas->drawPath(fRefPath[i * 2], fillPaint);
+            strokePaint.setStrokeWidth(20);
+            strokePaint.setAlpha(127);
+            canvas->translate(0, 50);
+            canvas->drawPath(fMoveHfPath, strokePaint);
+            canvas->translate(0, 30);
+            canvas->drawPath(fMoveZfPath, strokePaint);
+            canvas->translate(0, 30);
+            fillPaint.setAlpha(127);
+            canvas->drawPath(fRefPath[1 + i * 2], fillPaint);
+            canvas->restore();
+        }
+    }
+
+private:
+    typedef skiagm::GM INHERITED;
+};
+
 class Strokes2GM : public skiagm::GM {
     SkPath fPath;
 protected:
@@ -334,3 +406,5 @@
 static skiagm::GMRegistry R2(F2);
 static skiagm::GMRegistry R3(F3);
 static skiagm::GMRegistry R4(F4);
+
+DEF_GM( return SkNEW(ZeroLenStrokesGM); )
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 1b993fc..0c071ad 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -398,6 +398,15 @@
     /** Cap enum specifies the settings for the paint's strokecap. This is the
         treatment that is applied to the beginning and end of each non-closed
         contour (e.g. lines).
+
+        If the cap is round or square, the caps are drawn when the contour has
+        a zero length. Zero length contours can be created by following moveTo
+        with a lineTo at the same point, or a moveTo followed by a close.
+
+        A dash with an on interval of zero also creates a zero length contour.
+
+        The zero length contour draws the square cap without rotation, since
+        the no direction can be inferred.
     */
     enum Cap {
         kButt_Cap,      //!< begin/end contours with no extension
diff --git a/src/core/SkPathMeasure.cpp b/src/core/SkPathMeasure.cpp
index a5dd840..17ae95e 100644
--- a/src/core/SkPathMeasure.cpp
+++ b/src/core/SkPathMeasure.cpp
@@ -314,7 +314,12 @@
     SkASSERT(startT <= stopT);
 
     if (startT == stopT) {
-        return; // should we report this, to undo a moveTo?
+        /* if the dash as a zero-length on segment, add a corresponding zero-length line.
+           The stroke code will add end caps to zero length lines as appropriate */
+        SkPoint lastPt;
+        SkAssertResult(dst->getLastPt(&lastPt));
+        dst->lineTo(lastPt);
+        return;
     }
 
     SkPoint tmp0[7], tmp1[7];
@@ -568,7 +573,7 @@
     if (stopD > length) {
         stopD = length;
     }
-    if (startD >= stopD) {
+    if (startD > stopD) {
         return false;
     }
 
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index ede3d21..2db5bba 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -121,6 +121,9 @@
                   SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
                   SkPaint::Join, SkScalar resScale);
 
+    bool hasOnlyMoveTo() const { return 0 == fSegmentCount; }
+    SkPoint moveToPt() const { return fFirstPt; }
+
     void moveTo(const SkPoint&);
     void lineTo(const SkPoint&);
     void quadTo(const SkPoint&, const SkPoint&);
@@ -242,7 +245,14 @@
     SkScalar    prevY = fPrevPt.fY;
 
     if (!set_normal_unitnormal(fPrevPt, currPt, fRadius, normal, unitNormal)) {
-        return false;
+        if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper) {
+            return false;
+        }
+        /* Square caps and round caps draw even if the segment length is zero.
+           Since the zero length segment has no direction, set the orientation
+           to upright as the default orientation */
+        normal->set(fRadius, 0);
+        unitNormal->set(1, 0);
     }
 
     if (fSegmentCount == 0) {
@@ -356,7 +366,8 @@
 }
 
 void SkPathStroker::lineTo(const SkPoint& currPt) {
-    if (SkPath::IsLineDegenerate(fPrevPt, currPt, false)) {
+    if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper
+            && SkPath::IsLineDegenerate(fPrevPt, currPt, false)) {
         return;
     }
     SkVector    normal, unitNormal;
@@ -1334,6 +1345,14 @@
                 lastSegment = SkPath::kCubic_Verb;
                 break;
             case SkPath::kClose_Verb:
+                if (stroker.hasOnlyMoveTo() && SkPaint::kButt_Cap != this->getCap()) {
+                    /* If the stroke consists of a moveTo followed by a close, treat it
+                       as if it were followed by a zero-length line. Lines without length
+                       can have square and round end caps. */
+                    stroker.lineTo(stroker.moveToPt());
+                    lastSegment = SkPath::kLine_Verb;
+                    break;
+                }
                 stroker.close(lastSegment == SkPath::kLine_Verb);
                 break;
             case SkPath::kDone_Verb:
diff --git a/src/utils/SkDashPath.cpp b/src/utils/SkDashPath.cpp
index 4b2b33d..de249f6 100644
--- a/src/utils/SkDashPath.cpp
+++ b/src/utils/SkDashPath.cpp
@@ -272,7 +272,7 @@
         while (distance < length) {
             SkASSERT(dlen >= 0);
             addedSegment = false;
-            if (is_even(index) && dlen > 0 && !skipFirstSegment) {
+            if (is_even(index) && !skipFirstSegment) {
                 addedSegment = true;
                 ++segCount;