SkPDF: when drawing stroked path, draw using SVG rules for zero-length segments

The "zeroPath" and emptystroke GMs capture this issue.

This CL changes the following PDF GMs: emptystroke dashing4
lineclosepath dashing3 zeroPath linepath
complexclip3_complex complexclip3_simple roundrects
degeneratesegments filltypes strokerect pathfill
inverse_paths desk_chalkboard.skp

After this change, all PDF GMs look better (closer to 8888).
The dashing4, emptystroke, and zeroPath GMs still need a lot
of work to make them look right.

BUG=538726

Review URL: https://codereview.chromium.org/1374383004
This commit is contained in:
halcanary 2015-10-06 09:41:47 -07:00 committed by Commit bot
parent 4a33952961
commit 8b2bc252fa
4 changed files with 82 additions and 18 deletions

View File

@ -0,0 +1,54 @@
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkPath.h"
#include "SkStream.h"
#include "gm.h"
// Test how short paths are stroked with various caps
DEF_SIMPLE_GM(path_stroke_with_zero_length, canvas, 240, 120) {
SkPath paths[5];
paths[0].moveTo(30.0f, 0); // single line segment
paths[0].rLineTo(30.0f, 0);
paths[1].moveTo(90.0f, 0); // single line segment with close
paths[1].rLineTo(30.0f, 0);
paths[1].close();
paths[2].moveTo(150.0f, 0); // zero-length line
paths[2].rLineTo(0, 0);
paths[3].moveTo(180.0f, 0); // zero-length line with close
paths[3].rLineTo(0, 0);
paths[3].close();
paths[4].moveTo(210.0f, 0); // close only, no line
paths[4].close();
auto drawPaths = [&](const SkPaint& paint) {
canvas->translate(0, 30.0f);
for (const SkPath& path : paths) {
canvas->drawPath(path, paint);
}
};
SkAutoCanvasRestore autoCanvasRestore(canvas, true);
SkPaint butt;
butt.setStyle(SkPaint::kStroke_Style);
butt.setStrokeWidth(20.0f);
butt.setStrokeCap(SkPaint::kButt_Cap);
drawPaths(butt);
SkPaint round(butt);
round.setStrokeCap(SkPaint::kRound_Cap);
drawPaths(round);
SkPaint square(butt);
square.setStrokeCap(SkPaint::kSquare_Cap);
drawPaths(square);
}

View File

@ -1035,7 +1035,12 @@ void SkPDFDevice::drawPath(const SkDraw& d,
if (!content.entry()) {
return;
}
bool consumeDegeratePathSegments =
paint.getStyle() == SkPaint::kFill_Style ||
(paint.getStrokeCap() != SkPaint::kRound_Cap &&
paint.getStrokeCap() != SkPaint::kSquare_Cap);
SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(),
consumeDegeratePathSegments,
&content.entry()->fContent);
SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
&content.entry()->fContent);

View File

@ -118,25 +118,27 @@ void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) {
// static
void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
SkWStream* content) {
bool doConsumeDegerates, SkWStream* content) {
// Filling a path with no area results in a drawing in PDF renderers but
// Chrome expects to be able to draw some such entities with no visible
// result, so we detect those cases and discard the drawing for them.
// Specifically: moveTo(X), lineTo(Y) and moveTo(X), lineTo(X), lineTo(Y).
enum SkipFillState {
kEmpty_SkipFillState = 0,
kSingleLine_SkipFillState = 1,
kNonSingleLine_SkipFillState = 2,
kEmpty_SkipFillState,
kSingleLine_SkipFillState,
kNonSingleLine_SkipFillState,
};
SkipFillState fillState = kEmpty_SkipFillState;
if (paintStyle != SkPaint::kFill_Style) {
fillState = kNonSingleLine_SkipFillState;
}
//if (paintStyle != SkPaint::kFill_Style) {
// fillState = kNonSingleLine_SkipFillState;
//}
SkPoint lastMovePt = SkPoint::Make(0,0);
SkDynamicMemoryWStream currentSegment;
SkPoint args[4];
SkPath::Iter iter(path, false);
for (SkPath::Verb verb = iter.next(args); verb != SkPath::kDone_Verb; verb = iter.next(args)) {
for (SkPath::Verb verb = iter.next(args, doConsumeDegerates);
verb != SkPath::kDone_Verb;
verb = iter.next(args, doConsumeDegerates)) {
// args gets all the points, even the implicit first point.
switch (verb) {
case SkPath::kMove_Verb:
@ -146,13 +148,11 @@ void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
break;
case SkPath::kLine_Verb:
AppendLine(args[1].fX, args[1].fY, &currentSegment);
if (fillState == kEmpty_SkipFillState) {
if (args[0] != lastMovePt) {
fillState = kSingleLine_SkipFillState;
}
} else if (fillState == kSingleLine_SkipFillState) {
fillState = kNonSingleLine_SkipFillState;
if ((fillState == kEmpty_SkipFillState) && (args[0] != lastMovePt)) {
fillState = kSingleLine_SkipFillState;
break;
}
fillState = kNonSingleLine_SkipFillState;
break;
case SkPath::kQuad_Verb:
append_quad(args, &currentSegment);
@ -165,6 +165,7 @@ void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
for (int i = 0; i < converter.countQuads(); ++i) {
append_quad(&quads[i * 2], &currentSegment);
}
fillState = kNonSingleLine_SkipFillState;
} break;
case SkPath::kCubic_Verb:
AppendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
@ -172,10 +173,10 @@ void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
fillState = kNonSingleLine_SkipFillState;
break;
case SkPath::kClose_Verb:
if (fillState != kSingleLine_SkipFillState) {
ClosePath(&currentSegment);
currentSegment.writeToStream(content);
}
currentSegment.writeToStream(content);
currentSegment.reset();
break;
default:

View File

@ -46,7 +46,11 @@ public:
SkScalar dstX, SkScalar dstY, SkWStream* content);
static void AppendRectangle(const SkRect& rect, SkWStream* content);
static void EmitPath(const SkPath& path, SkPaint::Style paintStyle,
SkWStream* content);
bool doConsumeDegerates, SkWStream* content);
static void EmitPath(const SkPath& path, SkPaint::Style paintStyle,
SkWStream* content) {
SkPDFUtils::EmitPath(path, paintStyle, true, content);
}
static void ClosePath(SkWStream* content);
static void PaintPath(SkPaint::Style style, SkPath::FillType fill,
SkWStream* content);