Skip degenerate contours in glyphs.

Stroking in Skia follows the SVG rules of adding end caps to degenerate
contours. Skip all degenerate contours and degenerate curves on contours
to avoid this.

Bug: skia:9820
Change-Id: I320beeeb3728f39c764729454dcb128a05524d35
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/268166
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Herb Derby <herb@google.com>
diff --git a/gm/stroketext.cpp b/gm/stroketext.cpp
index 4ca103f..3705108 100644
--- a/gm/stroketext.cpp
+++ b/gm/stroketext.cpp
@@ -20,6 +20,7 @@
 #include "include/core/SkTypeface.h"
 #include "include/core/SkTypes.h"
 #include "include/effects/SkDashPathEffect.h"
+#include "tools/Resources.h"
 #include "tools/ToolUtils.h"
 
 static void test_nulldev(SkCanvas* canvas) {
@@ -93,3 +94,51 @@
     font.setSize(kAboveThreshold_TextSize);
     draw_text_set(canvas, paint, font);
 }
+
+DEF_SIMPLE_GM_CAN_FAIL(stroketext_native, canvas, msg, 650, 320) {
+    sk_sp<SkTypeface> ttf = MakeResourceAsTypeface("fonts/Stroking.ttf");
+    sk_sp<SkTypeface> otf = MakeResourceAsTypeface("fonts/Stroking.otf");
+    if (!ttf && !otf) {
+        msg->append("No support for ttf or otf.");
+        return skiagm::DrawResult::kSkip;
+    }
+
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(10);
+    p.setStrokeCap(SkPaint::kRound_Cap);
+    p.setStrokeJoin(SkPaint::kRound_Join);
+    p.setARGB(0xff, 0xbb, 0x00, 0x00);
+
+    if (ttf) {
+        /* Stroking.ttf is structured like:
+            nothing U+25CB ○ (nothing inside)
+            something U+25C9 ◉ (a tiny thing inside)
+            - off (point off / empty quad with implicit end) (before U+207B ⁻ / after U+208B ₋)
+            + on  (point on / empty line) (before U+207A ⁺ / after U+208A ₊)
+            0 off off (two implicit quads) (before U+2070 ⁰ / after U+2080 ₀)
+            1 off on  (quad with implicit close around) (before U+00B9 ¹ / after U+2081 ₁)
+            2 on  off (quad with implicit close) (before U+00B2 ² / after U+2082 ₂)
+            3 on  on  (empty line) (before U+00B3 ³ / after U+2083 ₃)
+        */
+        SkFont font(ttf, 100);
+        canvas->drawString("○◉  ⁻₋⁺₊", 10, 100, font, p);
+        canvas->drawString("⁰₀¹₁²₂³₃", 10, 200, font, p);
+    }
+
+    if (otf) {
+        /* Stroking.otf is structured like:
+            nothing U+25CB ○
+            something U+25C9 ◉
+            0 moveto, moveto (before U+2070 ⁰) (nothing there, FreeType ignores these)
+            1 moveto, empty line, moveto (before U+00B9 ¹) (degenerate lineto)
+            3 moveto, empty cubic, moveto (before U+00B3 ³) (degenerate cubicto)
+            f moveto, empty flex, moveto (before U+1DA0 ᶠ) (degenerate flex)
+        */
+        SkFont font(otf, 100);
+        canvas->drawString("○◉  ⁰¹³ᶠ", 10, 300, font, p);
+    }
+
+    return skiagm::DrawResult::kOk;
+}
diff --git a/include/core/SkFont.h b/include/core/SkFont.h
index 71f1ef2..9b45718 100644
--- a/include/core/SkFont.h
+++ b/include/core/SkFont.h
@@ -432,9 +432,9 @@
      */
     void getXPos(const SkGlyphID glyphs[], int count, SkScalar xpos[], SkScalar origin = 0) const;
 
-    /** Returns path corresponding to glyph outline.
-        If glyph has an outline, copies outline to path and returns true.
-        path returned may be empty.
+    /** Modifies path to be the outline of the glyph.
+        If the glyph has an outline, modifies path to be the glyph's outline and returns true.
+        The glyph outline may be empty. Degenerate contours in the glyph outline will be skipped.
         If glyph is described by a bitmap, returns false and ignores path parameter.
 
         @param glyphID  index of glyph
diff --git a/resources/fonts/Stroking.otf b/resources/fonts/Stroking.otf
new file mode 100644
index 0000000..a14c361
--- /dev/null
+++ b/resources/fonts/Stroking.otf
Binary files differ
diff --git a/resources/fonts/Stroking.ttf b/resources/fonts/Stroking.ttf
new file mode 100644
index 0000000..b88c6fa
--- /dev/null
+++ b/resources/fonts/Stroking.ttf
Binary files differ
diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp
index afbfec5..d07434c 100644
--- a/src/ports/SkFontHost_FreeType_common.cpp
+++ b/src/ports/SkFontHost_FreeType_common.cpp
@@ -690,47 +690,81 @@
 
 namespace {
 
-int move_proc(const FT_Vector* pt, void* ctx) {
-    SkPath* path = (SkPath*)ctx;
-    path->close();  // to close the previous contour (if any)
-    path->moveTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
-    return 0;
-}
+class SkFTGeometrySink {
+    SkPath* fPath;
+    bool fStarted;
+    FT_Vector fCurrent;
 
-int line_proc(const FT_Vector* pt, void* ctx) {
-    SkPath* path = (SkPath*)ctx;
-    path->lineTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
-    return 0;
-}
+    void goingTo(const FT_Vector* pt) {
+        if (!fStarted) {
+            fStarted = true;
+            fPath->moveTo(SkFDot6ToScalar(fCurrent.x), -SkFDot6ToScalar(fCurrent.y));
+        }
+        fCurrent = *pt;
+    }
 
-int quad_proc(const FT_Vector* pt0, const FT_Vector* pt1, void* ctx) {
-    SkPath* path = (SkPath*)ctx;
-    path->quadTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
-                 SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y));
-    return 0;
-}
+    bool currentIsNot(const FT_Vector* pt) {
+        return fCurrent.x != pt->x || fCurrent.y != pt->y;
+    }
 
-int cubic_proc(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* ctx) {
-    SkPath* path = (SkPath*)ctx;
-    path->cubicTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
-                  SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y),
-                  SkFDot6ToScalar(pt2->x), -SkFDot6ToScalar(pt2->y));
-    return 0;
-}
+    static int Move(const FT_Vector* pt, void* ctx) {
+        SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
+        if (self.fStarted) {
+            self.fPath->close();
+            self.fStarted = false;
+        }
+        self.fCurrent = *pt;
+        return 0;
+    }
+
+    static int Line(const FT_Vector* pt, void* ctx) {
+        SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
+        if (self.currentIsNot(pt)) {
+            self.goingTo(pt);
+            self.fPath->lineTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
+        }
+        return 0;
+    }
+
+    static int Quad(const FT_Vector* pt0, const FT_Vector* pt1, void* ctx) {
+        SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
+        if (self.currentIsNot(pt0) || self.currentIsNot(pt1)) {
+            self.goingTo(pt1);
+            self.fPath->quadTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
+                               SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y));
+        }
+        return 0;
+    }
+
+    static int Cubic(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* ctx) {
+        SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
+        if (self.currentIsNot(pt0) || self.currentIsNot(pt1) || self.currentIsNot(pt2)) {
+            self.goingTo(pt2);
+            self.fPath->cubicTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
+                                SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y),
+                                SkFDot6ToScalar(pt2->x), -SkFDot6ToScalar(pt2->y));
+        }
+        return 0;
+    }
+
+public:
+    SkFTGeometrySink(SkPath* path) : fPath{path}, fStarted{false}, fCurrent{0,0} {}
+
+    static constexpr const FT_Outline_Funcs Funcs{
+        /*move_to =*/ SkFTGeometrySink::Move,
+        /*line_to =*/ SkFTGeometrySink::Line,
+        /*conic_to =*/ SkFTGeometrySink::Quad,
+        /*cubic_to =*/ SkFTGeometrySink::Cubic,
+        /*shift = */ 0,
+        /*delta =*/ 0,
+    };
+};
 
 }  // namespace
 
 bool SkScalerContext_FreeType_Base::generateGlyphPath(FT_Face face, SkPath* path) {
-    FT_Outline_Funcs    funcs;
-
-    funcs.move_to   = move_proc;
-    funcs.line_to   = line_proc;
-    funcs.conic_to  = quad_proc;
-    funcs.cubic_to  = cubic_proc;
-    funcs.shift     = 0;
-    funcs.delta     = 0;
-
-    FT_Error err = FT_Outline_Decompose(&face->glyph->outline, &funcs, path);
+    SkFTGeometrySink sink{path};
+    FT_Error err = FT_Outline_Decompose(&face->glyph->outline, &SkFTGeometrySink::Funcs, &sink);
 
     if (err != 0) {
         path->reset();
diff --git a/src/ports/SkFontHost_mac.cpp b/src/ports/SkFontHost_mac.cpp
index eec5552..b9bdb72 100644
--- a/src/ports/SkFontHost_mac.cpp
+++ b/src/ports/SkFontHost_mac.cpp
@@ -966,7 +966,6 @@
     void generateFontMetrics(SkFontMetrics*) override;
 
 private:
-    static void CTPathElement(void *info, const CGPathElement *element);
     template<bool APPLY_PREBLEND>
     static void RGBToA8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
                         const SkGlyph& glyph, const uint8_t* table8);
@@ -1499,6 +1498,77 @@
     }
 }
 
+namespace {
+class SkCTPathGeometrySink {
+    SkPath* fPath;
+    bool fStarted;
+    CGPoint fCurrent;
+
+    void goingTo(const CGPoint pt) {
+        if (!fStarted) {
+            fStarted = true;
+            fPath->moveTo(fCurrent.x, -fCurrent.y);
+        }
+        fCurrent = pt;
+    }
+
+    bool currentIsNot(const CGPoint pt) {
+        return fCurrent.x != pt.x || fCurrent.y != pt.y;
+    }
+
+public:
+    SkCTPathGeometrySink(SkPath* path) : fPath{path}, fStarted{false}, fCurrent{0,0} {}
+    static void ApplyElement(void *ctx, const CGPathElement *element) {
+        SkCTPathGeometrySink& self = *(SkCTPathGeometrySink*)ctx;
+        CGPoint* points = element->points;
+
+        switch (element->type) {
+            case kCGPathElementMoveToPoint:
+                self.fStarted = false;
+                self.fCurrent = points[0];
+                break;
+
+            case kCGPathElementAddLineToPoint:
+                if (self.currentIsNot(points[0])) {
+                    self.goingTo(points[0]);
+                    self.fPath->lineTo(points[0].x, -points[0].y);
+                }
+                break;
+
+            case kCGPathElementAddQuadCurveToPoint:
+                if (self.currentIsNot(points[0]) || self.currentIsNot(points[1])) {
+                    self.goingTo(points[1]);
+                    self.fPath->quadTo(points[0].x, -points[0].y,
+                                       points[1].x, -points[1].y);
+                }
+                break;
+
+            case kCGPathElementAddCurveToPoint:
+                if (self.currentIsNot(points[0]) ||
+                    self.currentIsNot(points[1]) ||
+                    self.currentIsNot(points[2]))
+                {
+                    self.goingTo(points[2]);
+                    self.fPath->cubicTo(points[0].x, -points[0].y,
+                                        points[1].x, -points[1].y,
+                                        points[2].x, -points[2].y);
+                }
+                break;
+
+            case kCGPathElementCloseSubpath:
+                if (self.fStarted) {
+                    self.fPath->close();
+                }
+                break;
+
+            default:
+                SkDEBUGFAIL("Unknown path element!");
+                break;
+            }
+    }
+};
+} // namespace
+
 /*
  *  Our subpixel resolution is only 2 bits in each direction, so a scale of 4
  *  seems sufficient, and possibly even correct, to allow the hinted outline
@@ -1547,7 +1617,8 @@
         return false;
     }
 
-    CGPathApply(cgPath.get(), path, SkScalerContext_Mac::CTPathElement);
+    SkCTPathGeometrySink sink(path);
+    CGPathApply(cgPath.get(), &sink, SkCTPathGeometrySink::ApplyElement);
     if (fDoSubPosition) {
         SkMatrix m;
         m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY));
@@ -1609,41 +1680,6 @@
     }
 }
 
-void SkScalerContext_Mac::CTPathElement(void *info, const CGPathElement *element) {
-    SkPath* skPath = (SkPath*)info;
-
-    // Process the path element
-    switch (element->type) {
-        case kCGPathElementMoveToPoint:
-            skPath->moveTo(element->points[0].x, -element->points[0].y);
-            break;
-
-        case kCGPathElementAddLineToPoint:
-            skPath->lineTo(element->points[0].x, -element->points[0].y);
-            break;
-
-        case kCGPathElementAddQuadCurveToPoint:
-            skPath->quadTo(element->points[0].x, -element->points[0].y,
-                           element->points[1].x, -element->points[1].y);
-            break;
-
-        case kCGPathElementAddCurveToPoint:
-            skPath->cubicTo(element->points[0].x, -element->points[0].y,
-                            element->points[1].x, -element->points[1].y,
-                            element->points[2].x, -element->points[2].y);
-            break;
-
-        case kCGPathElementCloseSubpath:
-            skPath->close();
-            break;
-
-        default:
-            SkDEBUGFAIL("Unknown path element!");
-            break;
-        }
-}
-
-
 ///////////////////////////////////////////////////////////////////////////////
 
 // Web fonts added to the CTFont registry do not return their character set.
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index bc0f07f..dbe2faf 100644
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -608,11 +608,11 @@
     TEXTMETRIC fTM;
 };
 
-static FIXED float2FIXED(float x) {
+static FIXED SkFloatToFIXED(float x) {
     return SkFixedToFIXED(SkFloatToFixed(x));
 }
 
-static inline float FIXED2float(FIXED x) {
+static inline float SkFIXEDToFloat(FIXED x) {
     return SkFixedToFloat(SkFIXEDToFixed(x));
 }
 
@@ -729,10 +729,10 @@
         xform.eDy = 0;
 
         // MAT2 is row major, right handed (y up).
-        fMat22.eM11 = float2FIXED(xform.eM11);
-        fMat22.eM12 = float2FIXED(-xform.eM12);
-        fMat22.eM21 = float2FIXED(-xform.eM21);
-        fMat22.eM22 = float2FIXED(xform.eM22);
+        fMat22.eM11 = SkFloatToFIXED(xform.eM11);
+        fMat22.eM12 = SkFloatToFIXED(-xform.eM12);
+        fMat22.eM21 = SkFloatToFIXED(-xform.eM21);
+        fMat22.eM22 = SkFloatToFIXED(xform.eM22);
 
         if (needToRenderWithSkia(fRec)) {
             this->forceGenerateImageFromPath();
@@ -750,10 +750,10 @@
                 SkScalar upem = SkIntToScalar(otm.otmEMSquare);
 
                 SkScalar gdiTextSizeToEMScale = upem / gdiTextSize;
-                fHighResMat22.eM11 = float2FIXED(gdiTextSizeToEMScale);
-                fHighResMat22.eM12 = float2FIXED(0);
-                fHighResMat22.eM21 = float2FIXED(0);
-                fHighResMat22.eM22 = float2FIXED(gdiTextSizeToEMScale);
+                fHighResMat22.eM11 = SkScalarToFIXED(gdiTextSizeToEMScale);
+                fHighResMat22.eM12 = SkScalarToFIXED(0);
+                fHighResMat22.eM21 = SkScalarToFIXED(0);
+                fHighResMat22.eM22 = SkScalarToFIXED(gdiTextSizeToEMScale);
 
                 SkScalar removeEMScale = SkScalarInvert(upem);
                 fHiResMatrix = A;
@@ -852,8 +852,8 @@
         }
 
         // Apply matrix to advance.
-        glyph->fAdvanceY = -FIXED2float(fMat22.eM12) * glyph->fAdvanceX;
-        glyph->fAdvanceX *= FIXED2float(fMat22.eM11);
+        glyph->fAdvanceY = -SkFIXEDToFloat(fMat22.eM12) * glyph->fAdvanceX;
+        glyph->fAdvanceX *= SkFIXEDToFloat(fMat22.eM11);
 
         return;
     }
@@ -1181,6 +1181,8 @@
     }
 }
 
+namespace {
+
 class GDIGlyphbufferPointIter {
 public:
     GDIGlyphbufferPointIter(const uint8_t* glyphbuf, DWORD total_size)
@@ -1323,7 +1325,37 @@
     GDIPolygonCurvePointIter fPointIter;
 };
 
-static void sk_path_from_gdi_path(SkPath* path, const uint8_t* glyphbuf, DWORD total_size) {
+class SkGDIGeometrySink {
+    SkPath* fPath;
+    bool fStarted = false;
+    POINTFX fCurrent;
+
+    void goingTo(const POINTFX pt) {
+        if (!fStarted) {
+            fStarted = true;
+            fPath->moveTo( SkFIXEDToScalar(fCurrent.x),
+                          -SkFIXEDToScalar(fCurrent.y));
+        }
+        fCurrent = pt;
+    }
+
+    bool currentIsNot(const POINTFX pt) {
+        return fCurrent.x.value != pt.x.value || fCurrent.x.fract != pt.x.fract ||
+               fCurrent.y.value != pt.y.value || fCurrent.y.fract != pt.y.fract;
+    }
+
+public:
+    SkGDIGeometrySink(SkPath* path) : fPath(path) {}
+    void process(const uint8_t* glyphbuf, DWORD total_size);
+
+    /** It is possible for the hinted and unhinted versions of the same path to have
+     *  a different number of points due to GDI's handling of flipped points.
+     *  If this is detected, this will return false.
+     */
+    bool process(const uint8_t* glyphbuf, DWORD total_size, GDIGlyphbufferPointIter hintedYs);
+};
+
+void SkGDIGeometrySink::process(const uint8_t* glyphbuf, DWORD total_size) {
     const uint8_t* cur_glyph = glyphbuf;
     const uint8_t* end_glyph = glyphbuf + total_size;
 
@@ -1333,42 +1365,55 @@
         const uint8_t* end_poly = cur_glyph + th->cb;
         const uint8_t* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER);
 
-        path->moveTo(SkFixedToScalar( SkFIXEDToFixed(th->pfxStart.x)),
-                     SkFixedToScalar(-SkFIXEDToFixed(th->pfxStart.y)));
+        fStarted = false;
+        fCurrent = th->pfxStart;
 
         while (cur_poly < end_poly) {
             const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly;
+            const POINTFX* apfx = pc->apfx;
+            const WORD cpfx = pc->cpfx;
 
             if (pc->wType == TT_PRIM_LINE) {
-                for (uint16_t i = 0; i < pc->cpfx; i++) {
-                    path->lineTo(SkFixedToScalar( SkFIXEDToFixed(pc->apfx[i].x)),
-                                 SkFixedToScalar(-SkFIXEDToFixed(pc->apfx[i].y)));
+                for (uint16_t i = 0; i < cpfx; i++) {
+                    POINTFX pnt_b = apfx[i];
+                    if (this->currentIsNot(pnt_b)) {
+                        this->goingTo(pnt_b);
+                        fPath->lineTo( SkFIXEDToScalar(pnt_b.x),
+                                      -SkFIXEDToScalar(pnt_b.y));
+                    }
                 }
             }
 
             if (pc->wType == TT_PRIM_QSPLINE) {
-                for (uint16_t u = 0; u < pc->cpfx - 1; u++) { // Walk through points in spline
-                    POINTFX pnt_b = pc->apfx[u];    // B is always the current point
-                    POINTFX pnt_c = pc->apfx[u+1];
+                for (uint16_t u = 0; u < cpfx - 1; u++) { // Walk through points in spline
+                    POINTFX pnt_b = apfx[u];    // B is always the current point
+                    POINTFX pnt_c = apfx[u+1];
 
-                    if (u < pc->cpfx - 2) {          // If not on last spline, compute C
+                    if (u < cpfx - 2) {          // If not on last spline, compute C
                         pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x),
                                                             SkFIXEDToFixed(pnt_c.x)));
                         pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y),
                                                             SkFIXEDToFixed(pnt_c.y)));
                     }
 
-                    path->quadTo(SkFixedToScalar( SkFIXEDToFixed(pnt_b.x)),
-                                 SkFixedToScalar(-SkFIXEDToFixed(pnt_b.y)),
-                                 SkFixedToScalar( SkFIXEDToFixed(pnt_c.x)),
-                                 SkFixedToScalar(-SkFIXEDToFixed(pnt_c.y)));
+
+                    if (this->currentIsNot(pnt_b) || this->currentIsNot(pnt_c)) {
+                        this->goingTo(pnt_c);
+                        fPath->quadTo( SkFIXEDToScalar(pnt_b.x),
+                                      -SkFIXEDToScalar(pnt_b.y),
+                                       SkFIXEDToScalar(pnt_c.x),
+                                      -SkFIXEDToScalar(pnt_c.y));
+                    }
                 }
             }
+
             // Advance past this TTPOLYCURVE.
-            cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx;
+            cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * cpfx;
         }
         cur_glyph += th->cb;
-        path->close();
+        if (this->fStarted) {
+            fPath->close();
+        }
     }
 }
 
@@ -1377,11 +1422,8 @@
     if (nullptr == pElem) return false; \
 } while(0)
 
-// It is possible for the hinted and unhinted versions of the same path to have
-// a different number of points due to GDI's handling of flipped points.
-// If this is detected, this will return false.
-static bool sk_path_from_gdi_paths(SkPath* path, const uint8_t* glyphbuf, DWORD total_size,
-                                   GDIGlyphbufferPointIter hintedYs) {
+bool SkGDIGeometrySink::process(const uint8_t* glyphbuf, DWORD total_size,
+                                GDIGlyphbufferPointIter hintedYs) {
     const uint8_t* cur_glyph = glyphbuf;
     const uint8_t* end_glyph = glyphbuf + total_size;
 
@@ -1394,30 +1436,36 @@
         const uint8_t* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER);
 
         move_next_expected_hinted_point(hintedYs, hintedPoint);
-        path->moveTo(SkFixedToScalar( SkFIXEDToFixed(th->pfxStart.x)),
-                     SkFixedToScalar(-SkFIXEDToFixed(hintedPoint->y)));
+        fStarted = false;
+        fCurrent = {th->pfxStart.x, hintedPoint->y};
 
         while (cur_poly < end_poly) {
             const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly;
+            const POINTFX* apfx = pc->apfx;
+            const WORD cpfx = pc->cpfx;
 
             if (pc->wType == TT_PRIM_LINE) {
-                for (uint16_t i = 0; i < pc->cpfx; i++) {
+                for (uint16_t i = 0; i < cpfx; i++) {
                     move_next_expected_hinted_point(hintedYs, hintedPoint);
-                    path->lineTo(SkFixedToScalar( SkFIXEDToFixed(pc->apfx[i].x)),
-                                 SkFixedToScalar(-SkFIXEDToFixed(hintedPoint->y)));
+                    POINTFX pnt_b = {apfx[i].x, hintedPoint->y};
+                    if (this->currentIsNot(pnt_b)) {
+                        this->goingTo(pnt_b);
+                        fPath->lineTo( SkFIXEDToScalar(pnt_b.x),
+                                      -SkFIXEDToScalar(pnt_b.y));
+                    }
                 }
             }
 
             if (pc->wType == TT_PRIM_QSPLINE) {
-                POINTFX currentPoint = pc->apfx[0];
+                POINTFX currentPoint = apfx[0];
                 move_next_expected_hinted_point(hintedYs, hintedPoint);
                 // only take the hinted y if it wasn't flipped
                 if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
                     currentPoint.y = hintedPoint->y;
                 }
-                for (uint16_t u = 0; u < pc->cpfx - 1; u++) { // Walk through points in spline
+                for (uint16_t u = 0; u < cpfx - 1; u++) { // Walk through points in spline
                     POINTFX pnt_b = currentPoint;//pc->apfx[u]; // B is always the current point
-                    POINTFX pnt_c = pc->apfx[u+1];
+                    POINTFX pnt_c = apfx[u+1];
                     move_next_expected_hinted_point(hintedYs, hintedPoint);
                     // only take the hinted y if it wasn't flipped
                     if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
@@ -1426,27 +1474,34 @@
                     currentPoint.x = pnt_c.x;
                     currentPoint.y = pnt_c.y;
 
-                    if (u < pc->cpfx - 2) {          // If not on last spline, compute C
+                    if (u < cpfx - 2) {          // If not on last spline, compute C
                         pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x),
                                                             SkFIXEDToFixed(pnt_c.x)));
                         pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y),
                                                             SkFIXEDToFixed(pnt_c.y)));
                     }
 
-                    path->quadTo(SkFixedToScalar( SkFIXEDToFixed(pnt_b.x)),
-                                 SkFixedToScalar(-SkFIXEDToFixed(pnt_b.y)),
-                                 SkFixedToScalar( SkFIXEDToFixed(pnt_c.x)),
-                                 SkFixedToScalar(-SkFIXEDToFixed(pnt_c.y)));
+                    if (this->currentIsNot(pnt_b) || this->currentIsNot(pnt_c)) {
+                        this->goingTo(pnt_c);
+                        fPath->quadTo( SkFIXEDToScalar(pnt_b.x),
+                                      -SkFIXEDToScalar(pnt_b.y),
+                                       SkFIXEDToScalar(pnt_c.x),
+                                      -SkFIXEDToScalar(pnt_c.y));
+                    }
                 }
             }
+
             // Advance past this TTPOLYCURVE.
-            cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx;
+            cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * cpfx;
         }
         cur_glyph += th->cb;
-        path->close();
+        if (this->fStarted) {
+            fPath->close();
+        }
     }
     return true;
 }
+} // namespace
 
 DWORD SkScalerContext_GDI::getGDIGlyphPath(SkGlyphID glyph, UINT flags,
                                            SkAutoSTMalloc<BUFFERSIZE, uint8_t>* glyphbuf)
@@ -1512,7 +1567,8 @@
     }
 
     if (fRec.getHinting() != SkFontHinting::kSlight) {
-        sk_path_from_gdi_path(path, glyphbuf, total_size);
+        SkGDIGeometrySink sink(path);
+        sink.process(glyphbuf, total_size);
     } else {
         //GDI only uses hinted outlines when axis aligned.
         UINT format = GGO_NATIVE | GGO_GLYPH_INDEX;
@@ -1523,11 +1579,14 @@
             return false;
         }
 
-        if (!sk_path_from_gdi_paths(path, glyphbuf, total_size,
-                                    GDIGlyphbufferPointIter(hintedGlyphbuf, hinted_total_size)))
+        SkGDIGeometrySink sinkXBufYIter(path);
+        if (!sinkXBufYIter.process(glyphbuf, total_size,
+                                   GDIGlyphbufferPointIter(hintedGlyphbuf, hinted_total_size)))
         {
+            // Both path and sinkXBufYIter are in the state they were in at the time of failure.
             path->reset();
-            sk_path_from_gdi_path(path, glyphbuf, total_size);
+            SkGDIGeometrySink sink(path);
+            sink.process(glyphbuf, total_size);
         }
     }
     return true;
diff --git a/src/utils/win/SkDWriteGeometrySink.cpp b/src/utils/win/SkDWriteGeometrySink.cpp
index 3979a13..cd5ae7c 100644
--- a/src/utils/win/SkDWriteGeometrySink.cpp
+++ b/src/utils/win/SkDWriteGeometrySink.cpp
@@ -16,7 +16,8 @@
 #include <dwrite.h>
 #include <d2d1.h>
 
-SkDWriteGeometrySink::SkDWriteGeometrySink(SkPath* path) : fRefCount(1), fPath(path) { }
+SkDWriteGeometrySink::SkDWriteGeometrySink(SkPath* path)
+    : fRefCount{1}, fPath{path}, fStarted{false}, fCurrent{0,0} {}
 
 SkDWriteGeometrySink::~SkDWriteGeometrySink() { }
 
@@ -67,15 +68,19 @@
 }
 
 SK_STDMETHODIMP_(void) SkDWriteGeometrySink::BeginFigure(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin) {
-    fPath->moveTo(startPoint.x, startPoint.y);
     if (figureBegin == D2D1_FIGURE_BEGIN_HOLLOW) {
         SkDEBUGFAIL("Invalid D2D1_FIGURE_BEGIN value.");
     }
+    fStarted = false;
+    fCurrent = startPoint;
 }
 
 SK_STDMETHODIMP_(void) SkDWriteGeometrySink::AddLines(const D2D1_POINT_2F *points, UINT pointsCount) {
     for (const D2D1_POINT_2F *end = &points[pointsCount]; points < end; ++points) {
-        fPath->lineTo(points->x, points->y);
+        if (this->currentIsNot(*points)) {
+            this->goingTo(*points);
+            fPath->lineTo(points->x, points->y);
+        }
     }
 }
 
@@ -87,9 +92,9 @@
 typedef struct {
     float x;
     float y;
-} Cubic[4], Quadratic[3];
+} Cubic[4], Point;
 
-static bool check_quadratic(const Cubic& cubic, Quadratic& reduction) {
+static bool check_quadratic(const Cubic& cubic, Point& quadraticP1) {
     float dx10 = cubic[1].x - cubic[0].x;
     float dx23 = cubic[2].x - cubic[3].x;
     float midX = cubic[0].x + dx10 * 3 / 2;
@@ -104,38 +109,38 @@
     if (!approximately_equal(midY, (dy23 * 3 / 2) + cubic[3].y)) {
         return false;
     }
-    reduction[0] = cubic[0];
-    reduction[1].x = midX;
-    reduction[1].y = midY;
-    reduction[2] = cubic[3];
+    quadraticP1 = {midX, midY};
     return true;
 }
 
 SK_STDMETHODIMP_(void) SkDWriteGeometrySink::AddBeziers(const D2D1_BEZIER_SEGMENT *beziers, UINT beziersCount) {
-    SkPoint lastPt;
-    fPath->getLastPt(&lastPt);
-    D2D1_POINT_2F prevPt = { SkScalarToFloat(lastPt.fX), SkScalarToFloat(lastPt.fY) };
-
     for (const D2D1_BEZIER_SEGMENT *end = &beziers[beziersCount]; beziers < end; ++beziers) {
-        Cubic cubic = { { prevPt.x, prevPt.y },
-                        { beziers->point1.x, beziers->point1.y },
-                        { beziers->point2.x, beziers->point2.y },
-                        { beziers->point3.x, beziers->point3.y }, };
-        Quadratic quadratic;
-        if (check_quadratic(cubic, quadratic)) {
-            fPath->quadTo(quadratic[1].x, quadratic[1].y,
-                          quadratic[2].x, quadratic[2].y);
-        } else {
-            fPath->cubicTo(beziers->point1.x, beziers->point1.y,
-                           beziers->point2.x, beziers->point2.y,
-                           beziers->point3.x, beziers->point3.y);
+        if (this->currentIsNot(beziers->point1) ||
+            this->currentIsNot(beziers->point2) ||
+            this->currentIsNot(beziers->point3))
+        {
+            Cubic cubic = { {        fCurrent.x,        fCurrent.y },
+                            { beziers->point1.x, beziers->point1.y },
+                            { beziers->point2.x, beziers->point2.y },
+                            { beziers->point3.x, beziers->point3.y }, };
+            this->goingTo(beziers->point3);
+            Point quadraticP1;
+            if (check_quadratic(cubic, quadraticP1)) {
+                fPath->quadTo(    quadraticP1.x,     quadraticP1.y,
+                              beziers->point3.x, beziers->point3.y);
+            } else {
+                fPath->cubicTo(beziers->point1.x, beziers->point1.y,
+                               beziers->point2.x, beziers->point2.y,
+                               beziers->point3.x, beziers->point3.y);
+            }
         }
-        prevPt = beziers->point3;
     }
 }
 
 SK_STDMETHODIMP_(void) SkDWriteGeometrySink::EndFigure(D2D1_FIGURE_END figureEnd) {
-    fPath->close();
+    if (fStarted) {
+        fPath->close();
+    }
 }
 
 SK_STDMETHODIMP SkDWriteGeometrySink::Close() {
diff --git a/src/utils/win/SkDWriteGeometrySink.h b/src/utils/win/SkDWriteGeometrySink.h
index 019539b..af4909a 100644
--- a/src/utils/win/SkDWriteGeometrySink.h
+++ b/src/utils/win/SkDWriteGeometrySink.h
@@ -20,6 +20,20 @@
 private:
     LONG fRefCount;
     SkPath* fPath;
+    bool fStarted;
+    D2D1_POINT_2F fCurrent;
+
+    void goingTo(const D2D1_POINT_2F pt) {
+        if (!fStarted) {
+            fStarted = true;
+            fPath->moveTo(fCurrent.x, fCurrent.y);
+        }
+        fCurrent = pt;
+    }
+
+    bool currentIsNot(const D2D1_POINT_2F pt) {
+        return fCurrent.x != pt.x || fCurrent.y != pt.y;
+    }
 
 protected:
     explicit SkDWriteGeometrySink(SkPath* path);