SkScalerContext handle stroke changing path effect

A path effect is allowed to modify the stroke, which may affect whether
or not the final path is hairline or not. Handle this similarly to
SkPaint::getFillPath by taking an out parameter which states if the path
is hairline (should be zero stroked) or is an actual fill path.

This simplifies the internal handling of hairlines in SkScalerContext as
well as making the result more in line with the way hairlines are
handled elsewhere. It should also make the removal of
SkPaint::kStrokeAndFill_Style simpler.

Change-Id: I8ae8e3d6ee8a9f686f983cbcf723925b11c4a707
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/437020
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index 08c1b96..f53699c 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -189,7 +189,8 @@
         generateMetrics(&glyph);
     } else {
         SkPath devPath;
-        generatingImageFromPath = this->internalGetPath(glyph.getPackedID(), &devPath);
+        bool hairline;
+        generatingImageFromPath = this->internalGetPath(glyph.getPackedID(), &devPath, &hairline);
         if (!generatingImageFromPath) {
             generateMetrics(&glyph);
         } else {
@@ -217,10 +218,8 @@
             const bool notEmptyAndFromLCD = 0 < glyph.fWidth && fromLCD;
             const bool verticalLCD = fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag;
 
-            const bool hasHairline = fRec.fFrameWidth == 0;
-
-            const bool needExtraWidth  = (notEmptyAndFromLCD && !verticalLCD) || hasHairline;
-            const bool needExtraHeight = (notEmptyAndFromLCD &&  verticalLCD) || hasHairline;
+            const bool needExtraWidth  = (notEmptyAndFromLCD && !verticalLCD) || hairline;
+            const bool needExtraHeight = (notEmptyAndFromLCD &&  verticalLCD) || hairline;
             if (needExtraWidth) {
                 glyph.fWidth += 2;
                 glyph.fLeft -= 1;
@@ -455,7 +454,7 @@
 static void generateMask(const SkMask& mask, const SkPath& path,
                          const SkMaskGamma::PreBlend& maskPreBlend,
                          const bool doBGR, const bool doVert, const bool a8FromLCD,
-                         const SkPaint::Style paintStyle) {
+                         const bool hairline) {
     SkASSERT(mask.fFormat == SkMask::kBW_Format ||
              mask.fFormat == SkMask::kA8_Format ||
              mask.fFormat == SkMask::kLCD16_Format);
@@ -473,7 +472,7 @@
     matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft),
                         -SkIntToScalar(mask.fBounds.fTop));
 
-    paint.setStyle(paintStyle);
+    paint.setStroke(hairline);
     paint.setAntiAlias(SkMask::kBW_Format != mask.fFormat);
 
     const bool fromLCD = (mask.fFormat == SkMask::kLCD16_Format) ||
@@ -495,8 +494,8 @@
 
         // LCD hairline doesn't line up with the pixels, so do it the expensive way.
         SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
-        if (paintStyle != SkPaint::kFill_Style) {
-            rec.setStrokeStyle(1.0f, paintStyle == SkPaint::kStrokeAndFill_Style);
+        if (hairline) {
+            rec.setStrokeStyle(1.0f, false);
             rec.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 0.0f);
         }
         if (rec.needToApply() && rec.applyToPath(&strokePath, path)) {
@@ -577,8 +576,9 @@
     } else {
         SkPath devPath;
         SkMask mask = unfilteredGlyph->mask();
+        bool hairline;
 
-        if (!this->internalGetPath(unfilteredGlyph->getPackedID(), &devPath)) {
+        if (!this->internalGetPath(unfilteredGlyph->getPackedID(), &devPath, &hairline)) {
             generateImage(*unfilteredGlyph);
         } else {
             SkASSERT(SkMask::kARGB32_Format != origGlyph.fMaskFormat);
@@ -586,11 +586,7 @@
             const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
             const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
             const bool a8LCD = SkToBool(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag);
-            const bool frameAndFill = SkToBool(fRec.fFlags & kFrameAndFill_Flag);
-            const SkPaint::Style paintStyle = fRec.fFrameWidth != 0 ? SkPaint::kFill_Style
-                                            : frameAndFill          ? SkPaint::kStrokeAndFill_Style
-                                            :                         SkPaint::kStroke_Style;
-            generateMask(mask, devPath, fPreBlend, doBGR, doVert, a8LCD, paintStyle);
+            generateMask(mask, devPath, fPreBlend, doBGR, doVert, a8LCD, hairline);
         }
     }
 
@@ -691,7 +687,9 @@
 }
 
 bool SkScalerContext::getPath(SkPackedGlyphID glyphID, SkPath* path) {
-    return this->internalGetPath(glyphID, path);
+    // TODO: return hairline so user knows to stroke.
+    // Most users get the fill path without path effect and then draw that, so don't need this.
+    return this->internalGetPath(glyphID, path, nullptr);
 }
 
 void SkScalerContext::getFontMetrics(SkFontMetrics* fm) {
@@ -701,7 +699,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath) {
+bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath, bool* hairline) {
     SkPath  path;
     if (!generatePath(glyphID.glyphID(), &path)) {
         return false;
@@ -715,6 +713,10 @@
         }
     }
 
+    if (hairline) {
+        *hairline = false;
+    }
+
     if (fRec.fFrameWidth >= 0 || fPathEffect != nullptr) {
         // need the path in user-space, with only the point-size applied
         // so that our stroking and effects will operate the same way they
@@ -733,7 +735,7 @@
 
         SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
 
-        if (fRec.fFrameWidth >= 0) {
+        if (fRec.fFrameWidth > 0) {
             rec.setStrokeStyle(fRec.fFrameWidth,
                                SkToBool(fRec.fFlags & kFrameAndFill_Flag));
             // glyphs are always closed contours, so cap type is ignored,
@@ -757,6 +759,11 @@
             }
         }
 
+        // The path effect may have modified 'rec', so wait to here to check hairline status.
+        if (hairline && rec.isHairlineStyle()) {
+            *hairline = true;
+        }
+
         // now return stuff to the caller
         if (devPath) {
             localPath.transform(matrix, devPath);
diff --git a/src/core/SkScalerContext.h b/src/core/SkScalerContext.h
index 6b065a4..694fa56 100644
--- a/src/core/SkScalerContext.h
+++ b/src/core/SkScalerContext.h
@@ -414,7 +414,7 @@
     bool fGenerateImageFromPath;
 
     /** Returns false if the glyph has no path at all. */
-    bool internalGetPath(SkPackedGlyphID id, SkPath* devPath);
+    bool internalGetPath(SkPackedGlyphID id, SkPath* devPath, bool* hairline);
     SkGlyph internalMakeGlyph(SkPackedGlyphID packedID, SkMask::Format format);
 
     // SkMaskGamma::PreBlend converts linear masks to gamma correcting masks.