add new patheffect for stroke-and-fill

Change-Id: I2212e547d3d15c65c20f2f8e10fda5f2ef832187
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/291041
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Mike Reed <reed@google.com>
diff --git a/gm/patheffects.cpp b/gm/patheffects.cpp
index 37715f1..695d5e2 100644
--- a/gm/patheffects.cpp
+++ b/gm/patheffects.cpp
@@ -237,3 +237,58 @@
 };
 DEF_GM(return new ComboPathEfectsGM;)
 
+#include "include/effects/SkStrokeAndFillPathEffect.h"
+
+// Test that we can replicate SkPaint::kStrokeAndFill_Style
+// with a patheffect. We expect the 2nd and 3rd columns to draw the same.
+DEF_SIMPLE_GM(stroke_and_fill_patheffect, canvas, 900, 450) {
+    const float kStrokeWidth = 20;
+
+    typedef void (*Maker)(SkPath*);
+    const Maker makers[] = {
+        [](SkPath* path) {
+            path->addOval({0, 0, 100, 100}, SkPathDirection::kCW);
+        },
+        [](SkPath* path) {
+            path->addOval({0, 0, 100, 100}, SkPathDirection::kCCW);
+        },
+        [](SkPath* path) {
+            path->moveTo(0, 0).lineTo(100, 100).lineTo(0, 100).lineTo(100, 0).close();
+        },
+    };
+
+    const struct {
+        SkPaint::Style  fStyle;
+        float           fWidth;
+        bool            fUsePE;
+        bool            fExpectStrokeAndFill;
+    } rec[] = {
+        { SkPaint::kStroke_Style,                   0, false, false },
+        { SkPaint::kFill_Style,                     0,  true, false },
+        { SkPaint::kStroke_Style,                   0,  true, false },
+        { SkPaint::kStrokeAndFill_Style, kStrokeWidth, false, true  },
+        { SkPaint::kStroke_Style,        kStrokeWidth,  true, true  },
+        { SkPaint::kStrokeAndFill_Style, kStrokeWidth,  true, true  },
+    };
+
+    SkPaint paint;
+    canvas->translate(20, 20);
+    for (auto maker : makers) {
+        SkPath path;
+        maker(&path);
+
+        canvas->save();
+        for (const auto& r : rec) {
+            paint.setStyle(r.fStyle);
+            paint.setStrokeWidth(r.fWidth);
+            paint.setPathEffect(r.fUsePE ? SkStrokeAndFillPathEffect::Make() : nullptr);
+            paint.setColor(r.fExpectStrokeAndFill ? SK_ColorGRAY : SK_ColorBLACK);
+
+            canvas->drawPath(path, paint);
+            canvas->translate(150, 0);
+        }
+        canvas->restore();
+
+        canvas->translate(0, 150);
+    }
+}
diff --git a/include/effects/SkStrokeAndFillPathEffect.h b/include/effects/SkStrokeAndFillPathEffect.h
new file mode 100644
index 0000000..fbde649
--- /dev/null
+++ b/include/effects/SkStrokeAndFillPathEffect.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkStrokeAndFillPathEffect_DEFINED
+#define SkStrokeAndFillPathEffect_DEFINED
+
+#include "include/core/SkPaint.h"
+#include "include/core/SkPathEffect.h"
+#include "include/pathops/SkPathOps.h"
+
+class SK_API SkStrokeAndFillPathEffect {
+public:
+    /*  If the paint is set to stroke, this will add the stroke and fill geometries
+     *  together (hoping that the winding-direction works out).
+     *
+     *  If the paint is set to fill, this effect is ignored.
+     *
+     *  Note that if the paint is set to stroke and the stroke-width is 0, then
+     *  this will turn the geometry into just a fill.
+     */
+    static sk_sp<SkPathEffect> Make();
+};
+
+#endif
diff --git a/src/effects/SkOpPE.h b/src/effects/SkOpPE.h
index 1f51579..db87a05 100644
--- a/src/effects/SkOpPE.h
+++ b/src/effects/SkOpPE.h
@@ -65,5 +65,20 @@
     typedef SkPathEffect INHERITED;
 };
 
+class SkStrokeAndFillPE : public SkPathEffect {
+public:
+    SkStrokeAndFillPE() {}
+
+protected:
+    void flatten(SkWriteBuffer&) const override;
+    bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override;
+    // TODO: override onComputeFastBounds (I think)
+
+private:
+    SK_FLATTENABLE_HOOKS(SkStrokeAndFillPE)
+
+    typedef SkPathEffect INHERITED;
+};
+
 #endif
 
diff --git a/src/effects/SkOpPathEffect.cpp b/src/effects/SkOpPathEffect.cpp
index e7b8a60..733bbc4 100644
--- a/src/effects/SkOpPathEffect.cpp
+++ b/src/effects/SkOpPathEffect.cpp
@@ -121,4 +121,43 @@
     return buffer.isValid() ? SkStrokePathEffect::Make(width, join, cap, miter) : nullptr;
 }
 
+//////////////////////////////////////////////////////////////////////////////////////////////////
 
+#include "include/effects/SkStrokeAndFillPathEffect.h"
+#include "src/core/SkPathPriv.h"
+
+sk_sp<SkPathEffect> SkStrokeAndFillPathEffect::Make() {
+    return sk_sp<SkPathEffect>(new SkStrokeAndFillPE);
+}
+
+void SkStrokeAndFillPE::flatten(SkWriteBuffer&) const {}
+
+bool SkStrokeAndFillPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
+                                     const SkRect*) const {
+    // This one is weird, since we exist to allow this paint-style to go away. If we see it,
+    // just let the normal machine run its course.
+    if (rec->getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
+        *dst = src;
+        return true;
+    }
+
+    if (rec->getStyle() == SkStrokeRec::kStroke_Style) {
+        if (!rec->applyToPath(dst, src)) {
+            return false;
+        }
+        // lifted from SkStroke.cpp as an attempt to handle winding directions
+        if (SkPathPriv::CheapIsFirstDirection(src, SkPathPriv::kCCW_FirstDirection)) {
+            dst->reverseAddPath(src);
+        } else {
+            dst->addPath(src);
+        }
+    } else {
+        *dst = src;
+    }
+    rec->setFillStyle();
+    return true;
+}
+
+sk_sp<SkFlattenable> SkStrokeAndFillPE::CreateProc(SkReadBuffer& buffer) {
+    return SkStrokeAndFillPathEffect::Make();
+}
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index e277b2c..559ff0a 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -96,6 +96,7 @@
         SK_REGISTER_FLATTENABLE(SkPath1DPathEffect);
         SK_REGISTER_FLATTENABLE(SkPath2DPathEffect);
         SK_REGISTER_FLATTENABLE(SkStrokePE);
+        SK_REGISTER_FLATTENABLE(SkStrokeAndFillPE);
         SK_REGISTER_FLATTENABLE(SkTrimPE);
         SkPathEffect::RegisterFlattenables();