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;