Browse Source

fix: Emojis appearing black and white in Chrome 96 (#33683)

Milan Burda 3 years ago
parent
commit
7be0d42658
3 changed files with 1316 additions and 1 deletions
  1. 3 1
      patches/config.json
  2. 1 0
      patches/skia/.patches
  3. 1312 0
      patches/skia/fix_when_a_glyph_has_a_path.patch

+ 3 - 1
patches/config.json

@@ -17,5 +17,7 @@
 
   "src/electron/patches/ReactiveObjC": "src/third_party/squirrel.mac/vendor/ReactiveObjC",
 
-  "src/electron/patches/angle": "src/third_party/angle"
+  "src/electron/patches/angle": "src/third_party/angle",
+
+  "src/electron/patches/skia": "src/third_party/skia"
 }

+ 1 - 0
patches/skia/.patches

@@ -0,0 +1 @@
+fix_when_a_glyph_has_a_path.patch

+ 1312 - 0
patches/skia/fix_when_a_glyph_has_a_path.patch

@@ -0,0 +1,1312 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Ben Wagner <[email protected]>
+Date: Wed, 1 Dec 2021 17:27:08 -0500
+Subject: Fix when a glyph has a path
+
+Always call generateMetrics before generatePath so that generateMetrics
+can determine which glyph representation to use and if that glyph
+representation can be modeled as a path.
+
+Pass an allocator into generateMetrics so that it can set the path to
+not existing. This allows generatePath to continue to work as it used
+to, creating a path if any path is available. However, generateMetrics
+may first set the path to not existing.
+
+Update getPath and internalGetPath to use the path on the glyph if it
+has already been set. Update makeGlyph and internalMakeGlyph to always
+call generateMetrics first (which is now more like initGlyph).
+
+Update the SkGlyph::PathData to indicate that it is a dev-path and not a
+user-path. A user-path will have effects applied to it. A dev-path is
+always a resolved path which is always filled -- unless it is hairline.
+
+Update everything else for the knock on effects and to take advantage of
+this information.
+
+Bug: chromium:1266022
+Change-Id: Id3f3cf5a534ab99f3a5779c910c1d1e191e68b1e
+Reviewed-on: https://skia-review.googlesource.com/c/skia/+/478658
+Reviewed-by: Herb Derby <[email protected]>
+Cherry-Pick: 668995122fdcbd2be11e9d92e8da842a07ead299
+Merge-Approved: https://crbug.com/1266022#c46
+Commit-Queue: Ben Wagner <[email protected]>
+Reviewed-on: https://skia-review.googlesource.com/c/skia/+/482076
+
+diff --git a/BUILD.gn b/BUILD.gn
+index 865a7c6ffd78c39cf247b047e8b55c2e7e67f96a..2791c558878cf1ec3f19de54699cf65900b69db4 100644
+--- a/BUILD.gn
++++ b/BUILD.gn
+@@ -552,7 +552,9 @@ optional("fontmgr_win") {
+     "src/fonts/SkFontMgr_indirect.cpp",
+     "src/ports/SkFontMgr_win_dw.cpp",
+     "src/ports/SkScalerContext_win_dw.cpp",
++    "src/ports/SkScalerContext_win_dw.h",
+     "src/ports/SkTypeface_win_dw.cpp",
++    "src/ports/SkTypeface_win_dw.h",
+   ]
+ }
+ optional("fontmgr_win_factory") {
+diff --git a/bench/PathTextBench.cpp b/bench/PathTextBench.cpp
+index 369f4a079bf70848a84ec3e1fa8a8bef22b3a5e0..75267c406b5fc3c5285df95a51fc2f85289f6407 100644
+--- a/bench/PathTextBench.cpp
++++ b/bench/PathTextBench.cpp
+@@ -49,9 +49,14 @@ private:
+         SkFont defaultFont;
+         SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(defaultFont);
+         auto strike = strikeSpec.findOrCreateStrike();
++        SkArenaAlloc alloc(1 << 12); // This is a mock SkStrikeCache.
+         for (int i = 0; i < kNumGlyphs; ++i) {
+             SkPackedGlyphID id(defaultFont.unicharToGlyph(kGlyphs[i]));
+-            sk_ignore_unused_variable(strike->getScalerContext()->getPath(id, &fGlyphs[i]));
++            SkGlyph glyph = strike->getScalerContext()->makeGlyph(id, &alloc);
++            strike->getScalerContext()->getPath(glyph, &alloc);
++            if (glyph.path()) {
++                fGlyphs[i] = *glyph.path();
++            }
+             fGlyphs[i].setIsVolatile(fUncached);
+         }
+ 
+diff --git a/gm/scaledemoji_rendering.cpp b/gm/scaledemoji_rendering.cpp
+index f1f710377e03e2324998f15454ff198dab6d6eaa..51dec98fa694667f3d4d545f269a9ab5fc54a9d7 100644
+--- a/gm/scaledemoji_rendering.cpp
++++ b/gm/scaledemoji_rendering.cpp
+@@ -48,13 +48,14 @@ protected:
+     void onDraw(SkCanvas* canvas) override {
+ 
+         canvas->drawColor(SK_ColorGRAY);
+-        SkScalar y = 0;
++        SkPaint paint;
++        paint.setColor(SK_ColorCYAN);
+ 
++        SkScalar y = 0;
+         for (const auto& typeface: typefaces) {
+             SkFont font(typeface);
+             font.setEdging(SkFont::Edging::kAlias);
+ 
+-            SkPaint paint;
+             const char*   text = ToolUtils::emoji_sample_text();
+             SkFontMetrics metrics;
+ 
+@@ -63,11 +64,20 @@ protected:
+                 font.getMetrics(&metrics);
+                 // All typefaces should support subpixel mode
+                 font.setSubpixel(true);
++
+                 y += -metrics.fAscent;
+ 
+-                canvas->drawSimpleText(text, strlen(text), SkTextEncoding::kUTF8,
+-                                       10, y, font, paint);
++                SkScalar x = 0;
++                for (bool fakeBold : { false, true }) {
++                    font.setEmbolden(fakeBold);
++                    SkRect bounds;
++                    font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds, &paint);
++                    canvas->drawSimpleText(text, strlen(text), SkTextEncoding::kUTF8,
++                                           x + bounds.left(), y, font, paint);
++                    x += bounds.width() * 1.2;
++                }
+                 y += metrics.fDescent + metrics.fLeading;
++                x = 0;
+             }
+         }
+     }
+diff --git a/samplecode/SamplePathText.cpp b/samplecode/SamplePathText.cpp
+index 20fb5f99933b14c5346cec44a108d8cdcc8b6fd7..1f5cd82897f62e35185e90517bfb1583e240fd60 100644
+--- a/samplecode/SamplePathText.cpp
++++ b/samplecode/SamplePathText.cpp
+@@ -37,12 +37,17 @@ public:
+         SkFont defaultFont;
+         SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(defaultFont);
+         auto strike = strikeSpec.findOrCreateStrike();
++        SkArenaAlloc alloc(1 << 12); // This is a mock SkStrikeCache.
+         SkPath glyphPaths[52];
+         for (int i = 0; i < 52; ++i) {
+             // I and l are rects on OS X ...
+             char c = "aQCDEFGH7JKLMNOPBRZTUVWXYSAbcdefghijk1mnopqrstuvwxyz"[i];
+             SkPackedGlyphID id(defaultFont.unicharToGlyph(c));
+-            sk_ignore_unused_variable(strike->getScalerContext()->getPath(id, &glyphPaths[i]));
++            SkGlyph glyph = strike->getScalerContext()->makeGlyph(id, &alloc);
++            strike->getScalerContext()->getPath(glyph, &alloc);
++            if (glyph.path()) {
++                glyphPaths[i] = *glyph.path();
++            }
+         }
+ 
+         for (int i = 0; i < kNumPaths; ++i) {
+diff --git a/src/core/SkGlyph.cpp b/src/core/SkGlyph.cpp
+index ae2b284c0ed8df6a2162ad689497971ef460f8e4..126ffd7923380df7408335d5e48d1ce159c84a0e 100644
+--- a/src/core/SkGlyph.cpp
++++ b/src/core/SkGlyph.cpp
+@@ -119,6 +119,10 @@ size_t SkGlyph::setMetricsAndImage(SkArenaAlloc* alloc, const SkGlyph& from) {
+         if (from.fImage != nullptr && this->setImage(alloc, from.image())) {
+             return this->imageSize();
+         }
++
++        SkDEBUGCODE(
++            fAdvancesBoundsFormatAndInitialPathDone = from.fAdvancesBoundsFormatAndInitialPathDone;
++        )
+     }
+     return 0;
+ }
+@@ -143,7 +147,7 @@ size_t SkGlyph::imageSize() const {
+     return size;
+ }
+ 
+-void SkGlyph::installPath(SkArenaAlloc* alloc, const SkPath* path) {
++void SkGlyph::installPath(SkArenaAlloc* alloc, const SkPath* path, bool hairline) {
+     SkASSERT(fPathData == nullptr);
+     SkASSERT(!this->setPathHasBeenCalled());
+     fPathData = alloc->make<SkGlyph::PathData>();
+@@ -152,26 +156,23 @@ void SkGlyph::installPath(SkArenaAlloc* alloc, const SkPath* path) {
+         fPathData->fPath.updateBoundsCache();
+         fPathData->fPath.getGenerationID();
+         fPathData->fHasPath = true;
++        fPathData->fHairline = hairline;
+     }
+ }
+ 
+ bool SkGlyph::setPath(SkArenaAlloc* alloc, SkScalerContext* scalerContext) {
+     if (!this->setPathHasBeenCalled()) {
+-        SkPath path;
+-        if (scalerContext->getPath(this->getPackedID(), &path)) {
+-            this->installPath(alloc, &path);
+-        } else {
+-            this->installPath(alloc, nullptr);
+-        }
++        scalerContext->getPath(*this, alloc);
++        SkASSERT(this->setPathHasBeenCalled());
+         return this->path() != nullptr;
+     }
+ 
+     return false;
+ }
+ 
+-bool SkGlyph::setPath(SkArenaAlloc* alloc, const SkPath* path) {
++bool SkGlyph::setPath(SkArenaAlloc* alloc, const SkPath* path, bool hairline) {
+     if (!this->setPathHasBeenCalled()) {
+-        this->installPath(alloc, path);
++        this->installPath(alloc, path, hairline);
+         return this->path() != nullptr;
+     }
+     return false;
+@@ -186,6 +187,12 @@ const SkPath* SkGlyph::path() const {
+     return nullptr;
+ }
+ 
++bool SkGlyph::pathIsHairline() const {
++    // setPath must have been called previously.
++    SkASSERT(this->setPathHasBeenCalled());
++    return fPathData->fHairline;
++}
++
+ static std::tuple<SkScalar, SkScalar> calculate_path_gap(
+         SkScalar topOffset, SkScalar bottomOffset, const SkPath& path) {
+ 
+diff --git a/src/core/SkGlyph.h b/src/core/SkGlyph.h
+index 5c3911f151cf1f76c0f13388b52507d0e1613dab..aeb8017ad114f4aab0ebe435783b5a6f7a31dd74 100644
+--- a/src/core/SkGlyph.h
++++ b/src/core/SkGlyph.h
+@@ -290,7 +290,7 @@ public:
+     // Returns true if this is the first time you called setPath()
+     // and there actually is a path; call path() to get it.
+     bool setPath(SkArenaAlloc* alloc, SkScalerContext* scalerContext);
+-    bool setPath(SkArenaAlloc* alloc, const SkPath* path);
++    bool setPath(SkArenaAlloc* alloc, const SkPath* path, bool hairline);
+ 
+     // Returns true if that path has been set.
+     bool setPathHasBeenCalled() const { return fPathData != nullptr; }
+@@ -298,6 +298,7 @@ public:
+     // Return a pointer to the path if it exists, otherwise return nullptr. Only works if the
+     // path was previously set.
+     const SkPath* path() const;
++    bool pathIsHairline() const;
+ 
+     // Format
+     bool isColor() const { return fMaskFormat == SkMask::kARGB32_Format; }
+@@ -370,12 +371,17 @@ private:
+         Intercept* fIntercept{nullptr};
+         SkPath     fPath;
+         bool       fHasPath{false};
++        // A normal user-path will have patheffects applied to it and eventually become a dev-path.
++        // A dev-path is always a fill-path, except when it is hairline.
++        // The fPath is a dev-path, so sidecar the paths hairline status.
++        // This allows the user to avoid filling paths which should not be filled.
++        bool       fHairline{false};
+     };
+ 
+     size_t allocImage(SkArenaAlloc* alloc);
+ 
+     // path == nullptr indicates that there is no path.
+-    void installPath(SkArenaAlloc* alloc, const SkPath* path);
++    void installPath(SkArenaAlloc* alloc, const SkPath* path, bool hairline);
+ 
+     // The width and height of the glyph mask.
+     uint16_t  fWidth  = 0,
+@@ -402,6 +408,10 @@ private:
+     // Used by the DirectWrite scaler to track state.
+     int8_t    fForceBW = 0;
+ 
++    // An SkGlyph can be created with just a packedID, but generally speaking some glyph factory
++    // needs to actually fill out the glyph before it can be used as part of that system.
++    SkDEBUGCODE(bool fAdvancesBoundsFormatAndInitialPathDone{false};)
++
+     SkPackedGlyphID fID;
+ };
+ 
+diff --git a/src/core/SkRemoteGlyphCache.cpp b/src/core/SkRemoteGlyphCache.cpp
+index 9c2e7bb05b41611151c630323570c3589ab11bf0..a67a8ee0ed5451e0fd3a867ab27a11f4574d7f83 100644
+--- a/src/core/SkRemoteGlyphCache.cpp
++++ b/src/core/SkRemoteGlyphCache.cpp
+@@ -309,19 +309,19 @@ private:
+     // Same thing as MaskSummary, but for paths.
+     struct PathSummary {
+         constexpr static uint16_t kIsPath = 0;
+-        SkGlyphID glyphID;
++        SkPackedGlyphID packedID;
+         // If drawing glyphID can be done with a path, this is 0, otherwise it is the max
+         // dimension of the glyph.
+         uint16_t maxDimensionOrPath;
+     };
+ 
+     struct PathSummaryTraits {
+-        static SkGlyphID GetKey(PathSummary summary) {
+-            return summary.glyphID;
++        static SkPackedGlyphID GetKey(PathSummary summary) {
++            return summary.packedID;
+         }
+ 
+-        static uint32_t Hash(SkGlyphID packedID) {
+-            return SkChecksum::CheapMix(packedID);
++        static uint32_t Hash(SkPackedGlyphID packedID) {
++            return SkChecksum::CheapMix(packedID.value());
+         }
+     };
+ 
+@@ -348,7 +348,7 @@ private:
+ 
+     // The masks and paths that currently reside in the GPU process.
+     SkTHashTable<MaskSummary, SkPackedGlyphID, MaskSummaryTraits> fSentGlyphs;
+-    SkTHashTable<PathSummary, SkGlyphID, PathSummaryTraits> fSentPaths;
++    SkTHashTable<PathSummary, SkPackedGlyphID, PathSummaryTraits> fSentPaths;
+ 
+     // The Masks, SDFT Mask, and Paths that need to be sent to the GPU task for the processed
+     // TextBlobs. Cleared after diffs are serialized.
+@@ -462,6 +462,8 @@ void RemoteStrike::writeGlyphPath(
+     size_t pathSize = path->writeToMemory(nullptr);
+     serializer->write<uint64_t>(pathSize);
+     path->writeToMemory(serializer->allocate(pathSize, kPathAlignment));
++
++    serializer->write<bool>(glyph.pathIsHairline());
+ }
+ 
+ template <typename Rejector>
+@@ -473,7 +475,7 @@ void RemoteStrike::commonMaskLoop(
+                 if (summary == nullptr) {
+                     // Put the new SkGlyph in the glyphs to send.
+                     this->ensureScalerContext();
+-                    fMasksToSend.emplace_back(fContext->makeGlyph(packedID));
++                    fMasksToSend.emplace_back(fContext->makeGlyph(packedID, &fPathAlloc));
+                     SkGlyph* glyph = &fMasksToSend.back();
+ 
+                     MaskSummary newSummary =
+@@ -506,7 +508,7 @@ void RemoteStrike::prepareForMaskDrawing(
+ 
+             // Put the new SkGlyph in the glyphs to send.
+             this->ensureScalerContext();
+-            fMasksToSend.emplace_back(fContext->makeGlyph(packedID));
++            fMasksToSend.emplace_back(fContext->makeGlyph(packedID, &fPathAlloc));
+             SkGlyph* glyph = &fMasksToSend.back();
+ 
+             MaskSummary newSummary =
+@@ -536,25 +538,21 @@ void RemoteStrike::prepareForPathDrawing(
+         SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects) {
+     drawables->forEachGlyphID(
+             [&](size_t i, SkPackedGlyphID packedID, SkPoint position) {
+-                SkGlyphID glyphID = packedID.glyphID();
+-                PathSummary* summary = fSentPaths.find(glyphID);
++                PathSummary* summary = fSentPaths.find(packedID);
+                 if (summary == nullptr) {
+ 
+                     // Put the new SkGlyph in the glyphs to send.
+                     this->ensureScalerContext();
+-                    fPathsToSend.emplace_back(fContext->makeGlyph(SkPackedGlyphID{glyphID}));
++                    fPathsToSend.emplace_back(fContext->makeGlyph(packedID, &fPathAlloc));
+                     SkGlyph* glyph = &fPathsToSend.back();
+ 
+                     uint16_t maxDimensionOrPath = glyph->maxDimension();
+-                    // Only try to get the path if the glyphs is not color.
+-                    if (!glyph->isColor() && !glyph->isEmpty()) {
+-                        glyph->setPath(&fPathAlloc, fContext.get());
+-                        if (glyph->path() != nullptr) {
+-                            maxDimensionOrPath = PathSummary::kIsPath;
+-                        }
++                    glyph->setPath(&fPathAlloc, fContext.get());
++                    if (glyph->path() != nullptr) {
++                        maxDimensionOrPath = PathSummary::kIsPath;
+                     }
+ 
+-                    PathSummary newSummary = {glyph->getGlyphID(), maxDimensionOrPath};
++                    PathSummary newSummary = {packedID, maxDimensionOrPath};
+                     summary = fSentPaths.set(newSummary);
+                 }
+ 
+@@ -952,6 +950,7 @@ bool SkStrikeClientImpl::ReadGlyph(SkTLazy<SkGlyph>& glyph, Deserializer* deseri
+     if (!deserializer->read<uint8_t>(&maskFormat)) return false;
+     if (!SkMask::IsValidFormat(maskFormat)) return false;
+     glyph->fMaskFormat = static_cast<SkMask::Format>(maskFormat);
++    SkDEBUGCODE(glyph->fAdvancesBoundsFormatAndInitialPathDone = true;)
+ 
+     return true;
+ }
+@@ -1067,6 +1066,7 @@ bool SkStrikeClientImpl::readStrikeData(const volatile void* memory, size_t memo
+             SkPath* pathPtr = nullptr;
+             SkPath path;
+             uint64_t pathSize = 0u;
++            bool hairline = false;
+             if (!deserializer.read<uint64_t>(&pathSize)) READ_FAILURE
+ 
+             if (pathSize > 0) {
+@@ -1074,9 +1074,10 @@ bool SkStrikeClientImpl::readStrikeData(const volatile void* memory, size_t memo
+                 if (!pathData) READ_FAILURE
+                 if (!path.readFromMemory(const_cast<const void*>(pathData), pathSize)) READ_FAILURE
+                 pathPtr = &path;
++                if (!deserializer.read<bool>(&hairline)) READ_FAILURE
+             }
+ 
+-            strike->mergePath(allocatedGlyph, pathPtr);
++            strike->mergePath(allocatedGlyph, pathPtr, hairline);
+         }
+     }
+ 
+diff --git a/src/core/SkScalerCache.cpp b/src/core/SkScalerCache.cpp
+index f949446018959f0b34ed6fb118042cee55dab59c..c687644cff695d6e5164a769445be119a14052bd 100644
+--- a/src/core/SkScalerCache.cpp
++++ b/src/core/SkScalerCache.cpp
+@@ -48,7 +48,7 @@ std::tuple<SkGlyphDigest, size_t> SkScalerCache::digest(SkPackedGlyphID packedGl
+         return {*digest, 0};
+     }
+ 
+-    SkGlyph* glyph = fAlloc.make<SkGlyph>(fScalerContext->makeGlyph(packedGlyphID));
++    SkGlyph* glyph = fAlloc.make<SkGlyph>(fScalerContext->makeGlyph(packedGlyphID, &fAlloc));
+     return {this->addGlyph(glyph), sizeof(SkGlyph)};
+ }
+ 
+@@ -68,10 +68,11 @@ std::tuple<const SkPath*, size_t> SkScalerCache::preparePath(SkGlyph* glyph) {
+     return {glyph->path(), delta};
+ }
+ 
+-std::tuple<const SkPath*, size_t> SkScalerCache::mergePath(SkGlyph* glyph, const SkPath* path) {
++std::tuple<const SkPath*, size_t> SkScalerCache::mergePath(
++        SkGlyph* glyph, const SkPath* path, bool hairline) {
+     SkAutoMutexExclusive lock{fMu};
+     size_t pathDelta = 0;
+-    if (glyph->setPath(&fAlloc, path)) {
++    if (glyph->setPath(&fAlloc, path, hairline)) {
+         pathDelta = glyph->path()->approximateBytesUsed();
+     }
+     return {glyph->path(), pathDelta};
+diff --git a/src/core/SkScalerCache.h b/src/core/SkScalerCache.h
+index 5a5123d06002e0ad619c96bd8257fd0453c90cc9..bf5ad0bccb34a33b725fd54820afc56388e4085e 100644
+--- a/src/core/SkScalerCache.h
++++ b/src/core/SkScalerCache.h
+@@ -65,7 +65,7 @@ public:
+ 
+     // If the path has never been set, then add a path to glyph.
+     std::tuple<const SkPath*, size_t> mergePath(
+-            SkGlyph* glyph, const SkPath* path) SK_EXCLUDES(fMu);
++            SkGlyph* glyph, const SkPath* path, bool hairline) SK_EXCLUDES(fMu);
+ 
+     /** Return the number of glyphs currently cached. */
+     int countCachedGlyphs() const SK_EXCLUDES(fMu);
+diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
+index f53699c8e34d7643d43f635e9601a9f8ca8a5a7d..3ba7a25f4ea6c8d80e3c54f4de5e5b7f4582e2bd 100644
+--- a/src/core/SkScalerContext.cpp
++++ b/src/core/SkScalerContext.cpp
+@@ -177,33 +177,35 @@ bool SkScalerContext::GetGammaLUTData(SkScalar contrast, SkScalar paintGamma, Sk
+     return true;
+ }
+ 
+-SkGlyph SkScalerContext::makeGlyph(SkPackedGlyphID packedID) {
+-    return internalMakeGlyph(packedID, fRec.fMaskFormat);
++SkGlyph SkScalerContext::makeGlyph(SkPackedGlyphID packedID, SkArenaAlloc* alloc) {
++    return internalMakeGlyph(packedID, fRec.fMaskFormat, alloc);
+ }
+ 
+-SkGlyph SkScalerContext::internalMakeGlyph(SkPackedGlyphID packedID, SkMask::Format format) {
++SkGlyph SkScalerContext::internalMakeGlyph(SkPackedGlyphID packedID, SkMask::Format format,
++                                           SkArenaAlloc* alloc) {
+     SkGlyph glyph{packedID};
+     glyph.fMaskFormat = format;
+-    bool generatingImageFromPath = fGenerateImageFromPath;
+-    if (!generatingImageFromPath) {
+-        generateMetrics(&glyph);
+-    } else {
+-        SkPath devPath;
+-        bool hairline;
+-        generatingImageFromPath = this->internalGetPath(glyph.getPackedID(), &devPath, &hairline);
+-        if (!generatingImageFromPath) {
+-            generateMetrics(&glyph);
+-        } else {
+-            if (!generateAdvance(&glyph)) {
+-                generateMetrics(&glyph);
+-            }
++    // Must call to allow the subclass to determine the glyph representation to use.
++    this->generateMetrics(&glyph, alloc);
++    SkDEBUGCODE(glyph.fAdvancesBoundsFormatAndInitialPathDone = true;)
++    if (fGenerateImageFromPath) {
++        this->internalGetPath(glyph, alloc);
++        const SkPath* devPath = glyph.path();
++        if (devPath) {
++            bool hairline = glyph.pathIsHairline();
++
++            // generateMetrics may have modified the glyph fMaskFormat.
++            glyph.fMaskFormat = format;
+ 
+-            // If we are going to create the mask, then we cannot keep the color
+-            if (SkMask::kARGB32_Format == glyph.fMaskFormat) {
++            // Only BW, A8, and LCD16 can be produced from paths.
++            if (glyph.fMaskFormat != SkMask::kBW_Format &&
++                glyph.fMaskFormat != SkMask::kA8_Format &&
++                glyph.fMaskFormat != SkMask::kLCD16_Format)
++            {
+                 glyph.fMaskFormat = SkMask::kA8_Format;
+             }
+ 
+-            const SkIRect ir = devPath.getBounds().roundOut();
++            const SkIRect ir = devPath->getBounds().roundOut();
+             if (ir.isEmpty() || !SkRectPriv::Is16Bit(ir)) {
+                 goto SK_ERROR;
+             }
+@@ -548,15 +550,18 @@ static void generateMask(const SkMask& mask, const SkPath& path,
+ }
+ 
+ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
++    SkASSERT(origGlyph.fAdvancesBoundsFormatAndInitialPathDone);
++
+     const SkGlyph* unfilteredGlyph = &origGlyph;
+     // in case we need to call generateImage on a mask-format that is different
+     // (i.e. larger) than what our caller allocated by looking at origGlyph.
+     SkAutoMalloc tmpGlyphImageStorage;
+     SkGlyph tmpGlyph;
++    SkSTArenaAlloc<sizeof(SkGlyph::PathData)> tmpGlyphPathDataStorage;
+     if (fMaskFilter) {
+         // need the original bounds, sans our maskfilter
+         sk_sp<SkMaskFilter> mf = std::move(fMaskFilter);
+-        tmpGlyph = this->internalMakeGlyph(origGlyph.getPackedID(), fRec.fMaskFormat);
++        tmpGlyph = this->makeGlyph(origGlyph.getPackedID(), &tmpGlyphPathDataStorage);
+         fMaskFilter = std::move(mf);
+ 
+         // Use the origGlyph storage for the temporary unfiltered mask if it will fit.
+@@ -574,11 +579,12 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
+     if (!fGenerateImageFromPath) {
+         generateImage(*unfilteredGlyph);
+     } else {
+-        SkPath devPath;
++        SkASSERT(origGlyph.setPathHasBeenCalled());
++        const SkPath* devPath = origGlyph.path();
++        bool hairline = origGlyph.pathIsHairline();
+         SkMask mask = unfilteredGlyph->mask();
+-        bool hairline;
+ 
+-        if (!this->internalGetPath(unfilteredGlyph->getPackedID(), &devPath, &hairline)) {
++        if (!devPath) {
+             generateImage(*unfilteredGlyph);
+         } else {
+             SkASSERT(SkMask::kARGB32_Format != origGlyph.fMaskFormat);
+@@ -586,7 +592,7 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
+             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);
+-            generateMask(mask, devPath, fPreBlend, doBGR, doVert, a8LCD, hairline);
++            generateMask(mask, *devPath, fPreBlend, doBGR, doVert, a8LCD, hairline);
+         }
+     }
+ 
+@@ -686,10 +692,8 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
+     }
+ }
+ 
+-bool SkScalerContext::getPath(SkPackedGlyphID glyphID, SkPath* 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::getPath(SkGlyph& glyph, SkArenaAlloc* alloc) {
++    this->internalGetPath(glyph, alloc);
+ }
+ 
+ void SkScalerContext::getFontMetrics(SkFontMetrics* fm) {
+@@ -699,10 +703,21 @@ void SkScalerContext::getFontMetrics(SkFontMetrics* fm) {
+ 
+ ///////////////////////////////////////////////////////////////////////////////
+ 
+-bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath, bool* hairline) {
+-    SkPath  path;
+-    if (!generatePath(glyphID.glyphID(), &path)) {
+-        return false;
++void SkScalerContext::internalGetPath(SkGlyph& glyph, SkArenaAlloc* alloc) {
++    SkASSERT(glyph.fAdvancesBoundsFormatAndInitialPathDone);
++
++    if (glyph.setPathHasBeenCalled()) {
++        return;
++    }
++
++    SkPath path;
++    SkPath devPath;
++    bool hairline = false;
++
++    SkPackedGlyphID glyphID = glyph.getPackedID();
++    if (!generatePath(glyph, &path)) {
++        glyph.setPath(alloc, (SkPath*)nullptr, hairline);
++        return;
+     }
+ 
+     if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
+@@ -713,22 +728,20 @@ bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath,
+         }
+     }
+ 
+-    if (hairline) {
+-        *hairline = false;
+-    }
+-
+-    if (fRec.fFrameWidth >= 0 || fPathEffect != nullptr) {
++    if (fRec.fFrameWidth < 0 && fPathEffect == nullptr) {
++        devPath.swap(path);
++    } else {
+         // need the path in user-space, with only the point-size applied
+         // so that our stroking and effects will operate the same way they
+         // would if the user had extracted the path themself, and then
+         // called drawPath
+-        SkPath      localPath;
+-        SkMatrix    matrix, inverse;
++        SkPath localPath;
++        SkMatrix matrix;
++        SkMatrix inverse;
+ 
+         fRec.getMatrixFrom2x2(&matrix);
+         if (!matrix.invert(&inverse)) {
+-            // assume devPath is already empty.
+-            return true;
++            glyph.setPath(alloc, &devPath, hairline);
+         }
+         path.transform(inverse, &localPath);
+         // now localPath is only affected by the paint settings, and not the canvas matrix
+@@ -760,24 +773,13 @@ bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath,
+         }
+ 
+         // The path effect may have modified 'rec', so wait to here to check hairline status.
+-        if (hairline && rec.isHairlineStyle()) {
+-            *hairline = true;
++        if (rec.isHairlineStyle()) {
++            hairline = true;
+         }
+ 
+-        // now return stuff to the caller
+-        if (devPath) {
+-            localPath.transform(matrix, devPath);
+-        }
+-    } else {   // nothing tricky to do
+-        if (devPath) {
+-            devPath->swap(path);
+-        }
++        localPath.transform(matrix, &devPath);
+     }
+-
+-    if (devPath) {
+-        devPath->updateBoundsCache();
+-    }
+-    return true;
++    glyph.setPath(alloc, &devPath, hairline);
+ }
+ 
+ 
+@@ -1237,12 +1239,12 @@ std::unique_ptr<SkScalerContext> SkScalerContext::MakeEmpty(
+             glyph->zeroMetrics();
+             return true;
+         }
+-        void generateMetrics(SkGlyph* glyph) override {
++        void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override {
+             glyph->fMaskFormat = fRec.fMaskFormat;
+             glyph->zeroMetrics();
+         }
+         void generateImage(const SkGlyph& glyph) override {}
+-        bool generatePath(SkGlyphID glyph, SkPath* path) override {
++        bool generatePath(const SkGlyph& glyph, SkPath* path) override {
+             path->reset();
+             return false;
+         }
+diff --git a/src/core/SkScalerContext.h b/src/core/SkScalerContext.h
+index 694fa56179fbaa1f9fe7932ee1885ff26204bcc9..99605b48be2cfec240b8dd51bfad932d9b5c5e44 100644
+--- a/src/core/SkScalerContext.h
++++ b/src/core/SkScalerContext.h
+@@ -284,9 +284,9 @@ public:
+     // DEPRECATED
+     bool isVertical() const { return false; }
+ 
+-    SkGlyph     makeGlyph(SkPackedGlyphID);
++    SkGlyph     makeGlyph(SkPackedGlyphID, SkArenaAlloc*);
+     void        getImage(const SkGlyph&);
+-    bool SK_WARN_UNUSED_RESULT getPath(SkPackedGlyphID, SkPath*);
++    void        getPath(SkGlyph&, SkArenaAlloc*);
+     void        getFontMetrics(SkFontMetrics*);
+ 
+     /** Return the size in bytes of the associated gamma lookup table
+@@ -370,7 +370,7 @@ protected:
+      *
+      *  TODO: fMaskFormat is set by internalMakeGlyph later; cannot be set here.
+      */
+-    virtual void generateMetrics(SkGlyph* glyph) = 0;
++    virtual void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) = 0;
+ 
+     /** Generates the contents of glyph.fImage.
+      *  When called, glyph.fImage will be pointing to a pre-allocated,
+@@ -385,9 +385,10 @@ protected:
+ 
+     /** Sets the passed path to the glyph outline.
+      *  If this cannot be done the path is set to empty;
++     *  Does not apply subpixel positioning to the path.
+      *  @return false if this glyph does not have any path.
+      */
+-    virtual bool SK_WARN_UNUSED_RESULT generatePath(SkGlyphID glyphId, SkPath* path) = 0;
++    virtual bool SK_WARN_UNUSED_RESULT generatePath(const SkGlyph&, SkPath*) = 0;
+ 
+     /** Retrieves font metrics. */
+     virtual void generateFontMetrics(SkFontMetrics*) = 0;
+@@ -396,11 +397,13 @@ protected:
+     void forceOffGenerateImageFromPath() { fGenerateImageFromPath = false; }
+ 
+ private:
++    friend class PathText;  // For debug purposes
++    friend class PathTextBench;  // For debug purposes
+     friend class RandomScalerContext;  // For debug purposes
+ 
+-    static SkScalerContextRec PreprocessRec(const SkTypeface& typeface,
+-                                            const SkScalerContextEffects& effects,
+-                                            const SkDescriptor& desc);
++    static SkScalerContextRec PreprocessRec(const SkTypeface&,
++                                            const SkScalerContextEffects&,
++                                            const SkDescriptor&);
+ 
+     // never null
+     sk_sp<SkTypeface> fTypeface;
+@@ -414,8 +417,8 @@ private:
+     bool fGenerateImageFromPath;
+ 
+     /** Returns false if the glyph has no path at all. */
+-    bool internalGetPath(SkPackedGlyphID id, SkPath* devPath, bool* hairline);
+-    SkGlyph internalMakeGlyph(SkPackedGlyphID packedID, SkMask::Format format);
++    void internalGetPath(SkGlyph&, SkArenaAlloc*);
++    SkGlyph internalMakeGlyph(SkPackedGlyphID, SkMask::Format, SkArenaAlloc*);
+ 
+     // SkMaskGamma::PreBlend converts linear masks to gamma correcting masks.
+ protected:
+diff --git a/src/core/SkStrikeCache.h b/src/core/SkStrikeCache.h
+index dbd9eae47b1de9cc15cdf85ec1b3be5285975e6a..cf93e118cd0fbd985e964474e5e3af6060aa6365 100644
+--- a/src/core/SkStrikeCache.h
++++ b/src/core/SkStrikeCache.h
+@@ -55,8 +55,8 @@ public:
+             return glyph;
+         }
+ 
+-        const SkPath* mergePath(SkGlyph* glyph, const SkPath* path) {
+-            auto [glyphPath, increase] = fScalerCache.mergePath(glyph, path);
++        const SkPath* mergePath(SkGlyph* glyph, const SkPath* path, bool hairline) {
++            auto [glyphPath, increase] = fScalerCache.mergePath(glyph, path, hairline);
+             this->updateDelta(increase);
+             return glyphPath;
+         }
+diff --git a/src/core/SkTypeface_remote.cpp b/src/core/SkTypeface_remote.cpp
+index 954a24f68add2b8f5f0e3fe549ff1ff025240060..a59d214e4bcd45fc9c80d968156a313800145fb7 100644
+--- a/src/core/SkTypeface_remote.cpp
++++ b/src/core/SkTypeface_remote.cpp
+@@ -23,7 +23,7 @@ bool SkScalerContextProxy::generateAdvance(SkGlyph* glyph) {
+     return false;
+ }
+ 
+-void SkScalerContextProxy::generateMetrics(SkGlyph* glyph) {
++void SkScalerContextProxy::generateMetrics(SkGlyph* glyph, SkArenaAlloc*) {
+     TRACE_EVENT1("skia", "generateMetrics", "rec", TRACE_STR_COPY(this->getRec().dump().c_str()));
+     if (this->getProxyTypeface()->isLogging()) {
+         SkDebugf("GlyphCacheMiss generateMetrics: %s\n", this->getRec().dump().c_str());
+@@ -47,7 +47,7 @@ void SkScalerContextProxy::generateImage(const SkGlyph& glyph) {
+             SkStrikeClient::CacheMissType::kGlyphImage, fRec.fTextSize);
+ }
+ 
+-bool SkScalerContextProxy::generatePath(SkGlyphID glyphID, SkPath* path) {
++bool SkScalerContextProxy::generatePath(const SkGlyph& glyph, SkPath* path) {
+     TRACE_EVENT1("skia", "generatePath", "rec", TRACE_STR_COPY(this->getRec().dump().c_str()));
+     if (this->getProxyTypeface()->isLogging()) {
+         SkDebugf("GlyphCacheMiss generatePath: %s\n", this->getRec().dump().c_str());
+diff --git a/src/core/SkTypeface_remote.h b/src/core/SkTypeface_remote.h
+index c07daa9a3803a557100f3f6a54b93c485100a454..07c24b6947fdc355b0fb0593da23f5806444f62b 100644
+--- a/src/core/SkTypeface_remote.h
++++ b/src/core/SkTypeface_remote.h
+@@ -29,9 +29,9 @@ public:
+ 
+ protected:
+     bool generateAdvance(SkGlyph* glyph) override;
+-    void generateMetrics(SkGlyph* glyph) override;
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override;
+     void generateImage(const SkGlyph& glyph) override;
+-    bool generatePath(SkGlyphID glyphID, SkPath* path) override;
++    bool generatePath(const SkGlyph& glyphID, SkPath* path) override;
+     void generateFontMetrics(SkFontMetrics* metrics) override;
+     SkTypefaceProxy* getProxyTypeface() const;
+ 
+diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
+index 6fc99eb5aaf7997d85aab6d25fe104b5b3937b66..7acf92a5bf478655773305c8a3b50d04604a6c3a 100644
+--- a/src/ports/SkFontHost_FreeType.cpp
++++ b/src/ports/SkFontHost_FreeType.cpp
+@@ -369,9 +369,9 @@ public:
+ 
+ protected:
+     bool generateAdvance(SkGlyph* glyph) override;
+-    void generateMetrics(SkGlyph* glyph) override;
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override;
+     void generateImage(const SkGlyph& glyph) override;
+-    bool generatePath(SkGlyphID glyphID, SkPath* path) override;
++    bool generatePath(const SkGlyph& glyph, SkPath* path) override;
+     void generateFontMetrics(SkFontMetrics*) override;
+ 
+ private:
+@@ -1071,11 +1071,9 @@ bool SkScalerContext_FreeType::shouldSubpixelBitmap(const SkGlyph& glyph, const
+     return mechanism && policy;
+ }
+ 
+-void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
++void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) {
+     SkAutoMutexExclusive  ac(f_t_mutex());
+ 
+-    glyph->fMaskFormat = fRec.fMaskFormat;
+-
+     if (this->setupSize()) {
+         glyph->zeroMetrics();
+         return;
+@@ -1191,6 +1189,7 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
+ 
+         if (haveLayers) {
+             glyph->fMaskFormat = SkMask::kARGB32_Format;
++            glyph->setPath(alloc, nullptr, false);
+             if (!(bounds.xMin < bounds.xMax && bounds.yMin < bounds.yMax)) {
+                 bounds = { 0, 0, 0, 0 };
+             }
+@@ -1320,11 +1319,12 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
+ }
+ 
+ 
+-bool SkScalerContext_FreeType::generatePath(SkGlyphID glyphID, SkPath* path) {
++bool SkScalerContext_FreeType::generatePath(const SkGlyph& glyph, SkPath* path) {
+     SkASSERT(path);
+ 
+     SkAutoMutexExclusive  ac(f_t_mutex());
+ 
++    SkGlyphID glyphID = glyph.getGlyphID();
+     // FT_IS_SCALABLE is documented to mean the face contains outline glyphs.
+     if (!FT_IS_SCALABLE(fFace) || this->setupSize()) {
+         path->reset();
+diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
+index 1b47a0bbee6a1b60f9f051ede4bc77ce63a6d4bf..8dfa8ac625aff6ec4f37b58de9d59b4d86a2d709 100644
+--- a/src/ports/SkFontHost_win.cpp
++++ b/src/ports/SkFontHost_win.cpp
+@@ -564,9 +564,9 @@ public:
+ 
+ protected:
+     bool generateAdvance(SkGlyph* glyph) override;
+-    void generateMetrics(SkGlyph* glyph) override;
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override;
+     void generateImage(const SkGlyph& glyph) override;
+-    bool generatePath(SkGlyphID glyph, SkPath* path) override;
++    bool generatePath(const SkGlyph& glyph, SkPath* path) override;
+     void generateFontMetrics(SkFontMetrics*) override;
+ 
+ private:
+@@ -801,7 +801,7 @@ bool SkScalerContext_GDI::generateAdvance(SkGlyph* glyph) {
+     return false;
+ }
+ 
+-void SkScalerContext_GDI::generateMetrics(SkGlyph* glyph) {
++void SkScalerContext_GDI::generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) {
+     SkASSERT(fDDC);
+ 
+     glyph->fMaskFormat = fRec.fMaskFormat;
+@@ -844,6 +844,9 @@ void SkScalerContext_GDI::generateMetrics(SkGlyph* glyph) {
+         glyph->fAdvanceY = -SkFIXEDToFloat(fMat22.eM12) * glyph->fAdvanceX;
+         glyph->fAdvanceX *= SkFIXEDToFloat(fMat22.eM11);
+ 
++        // These do not have an outline path at all.
++        glyph->setPath(alloc, nullptr, false);
++
+         return;
+     }
+ 
+@@ -1530,12 +1533,14 @@ DWORD SkScalerContext_GDI::getGDIGlyphPath(SkGlyphID glyph, UINT flags,
+     return total_size;
+ }
+ 
+-bool SkScalerContext_GDI::generatePath(SkGlyphID glyph, SkPath* path) {
++bool SkScalerContext_GDI::generatePath(const SkGlyph& glyph, SkPath* path) {
+     SkASSERT(path);
+     SkASSERT(fDDC);
+ 
+     path->reset();
+ 
++    SkGlyphID glyphID = glyph.getGlyphID();
++
+     // Out of all the fonts on a typical Windows box,
+     // 25% of glyphs require more than 2KB.
+     // 1% of glyphs require more than 4KB.
+@@ -1550,7 +1555,7 @@ bool SkScalerContext_GDI::generatePath(SkGlyphID glyph, SkPath* path) {
+         format |= GGO_UNHINTED;
+     }
+     SkAutoSTMalloc<BUFFERSIZE, uint8_t> glyphbuf(BUFFERSIZE);
+-    DWORD total_size = getGDIGlyphPath(glyph, format, &glyphbuf);
++    DWORD total_size = getGDIGlyphPath(glyphID, format, &glyphbuf);
+     if (0 == total_size) {
+         return false;
+     }
+@@ -1561,7 +1566,7 @@ bool SkScalerContext_GDI::generatePath(SkGlyphID glyph, SkPath* path) {
+     } else {
+         SkAutoSTMalloc<BUFFERSIZE, uint8_t> hintedGlyphbuf(BUFFERSIZE);
+         //GDI only uses hinted outlines when axis aligned.
+-        DWORD hinted_total_size = getGDIGlyphPath(glyph, GGO_NATIVE | GGO_GLYPH_INDEX,
++        DWORD hinted_total_size = getGDIGlyphPath(glyphID, GGO_NATIVE | GGO_GLYPH_INDEX,
+                                                   &hintedGlyphbuf);
+         if (0 == hinted_total_size) {
+             return false;
+diff --git a/src/ports/SkScalerContext_mac_ct.cpp b/src/ports/SkScalerContext_mac_ct.cpp
+index 14e3016928cb12277a323a8b574dce985e2d2765..c373492f2d351af21e4e94b2128144a59c8e3e4a 100644
+--- a/src/ports/SkScalerContext_mac_ct.cpp
++++ b/src/ports/SkScalerContext_mac_ct.cpp
+@@ -269,7 +269,7 @@ bool SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) {
+     return false;
+ }
+ 
+-void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
++void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) {
+     glyph->fMaskFormat = fRec.fMaskFormat;
+ 
+     const CGGlyph cgGlyph = (CGGlyph) glyph->getGlyphID();
+@@ -609,7 +609,7 @@ public:
+  */
+ #define kScaleForSubPixelPositionHinting (4.0f)
+ 
+-bool SkScalerContext_Mac::generatePath(SkGlyphID glyph, SkPath* path) {
++bool SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) {
+     SkScalar scaleX = SK_Scalar1;
+     SkScalar scaleY = SK_Scalar1;
+ 
+@@ -643,7 +643,7 @@ bool SkScalerContext_Mac::generatePath(SkGlyphID glyph, SkPath* path) {
+         xform = CGAffineTransformConcat(fTransform, scale);
+     }
+ 
+-    CGGlyph cgGlyph = SkTo<CGGlyph>(glyph);
++    CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
+     SkUniqueCFRef<CGPathRef> cgPath(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph, &xform));
+ 
+     path->reset();
+diff --git a/src/ports/SkScalerContext_mac_ct.h b/src/ports/SkScalerContext_mac_ct.h
+index a11a76b9b5745c2385f792b254bfc082e22a8800..f9c9c9717c63262c6bee46daacafce885ce6ddcb 100644
+--- a/src/ports/SkScalerContext_mac_ct.h
++++ b/src/ports/SkScalerContext_mac_ct.h
+@@ -45,9 +45,9 @@ public:
+ 
+ protected:
+     bool generateAdvance(SkGlyph* glyph) override;
+-    void generateMetrics(SkGlyph* glyph) override;
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override;
+     void generateImage(const SkGlyph& glyph) override;
+-    bool generatePath(SkGlyphID glyph, SkPath* path) override;
++    bool generatePath(const SkGlyph& glyph, SkPath* path) override;
+     void generateFontMetrics(SkFontMetrics*) override;
+ 
+ private:
+diff --git a/src/ports/SkScalerContext_win_dw.cpp b/src/ports/SkScalerContext_win_dw.cpp
+index d2a45e115056bb40483aac58d88a5195e504a4c5..53a93d4d6e5e2915e6a2b02ce79310a57538f99d 100644
+--- a/src/ports/SkScalerContext_win_dw.cpp
++++ b/src/ports/SkScalerContext_win_dw.cpp
+@@ -548,6 +548,9 @@ HRESULT SkScalerContext_DW::getBoundingBox(SkGlyph* glyph,
+ }
+ 
+ bool SkScalerContext_DW::isColorGlyph(const SkGlyph& glyph) {
++    // One would think that with newer DirectWrite that this could be like isPngGlyph
++    // except test for DWRITE_GLYPH_IMAGE_FORMATS_COLR, but that doesn't seem to work.
++
+     SkTScopedComPtr<IDWriteColorGlyphRunEnumerator> colorLayer;
+     return getColorGlyphRun(glyph, &colorLayer);
+ }
+@@ -593,10 +596,10 @@ bool SkScalerContext_DW::getColorGlyphRun(const SkGlyph& glyph,
+     return true;
+ }
+ 
+-void SkScalerContext_DW::generateColorMetrics(SkGlyph* glyph) {
++bool SkScalerContext_DW::generateColorMetrics(SkGlyph* glyph) {
+     SkTScopedComPtr<IDWriteColorGlyphRunEnumerator> colorLayers;
+     if (!getColorGlyphRun(*glyph, &colorLayers)) {
+-        return;
++        return false;
+     }
+     SkASSERT(colorLayers.get());
+ 
+@@ -604,15 +607,15 @@ void SkScalerContext_DW::generateColorMetrics(SkGlyph* glyph) {
+     BOOL hasNextRun = FALSE;
+     while (SUCCEEDED(colorLayers->MoveNext(&hasNextRun)) && hasNextRun) {
+         const DWRITE_COLOR_GLYPH_RUN* colorGlyph;
+-        HRVM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run");
++        HRBM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run");
+ 
+         SkPath path;
+         SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
+-        HRVM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
++        HRBM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
+             "Could not create geometry to path converter.");
+         {
+             Exclusive l(maybe_dw_mutex(*this->getDWriteTypeface()));
+-            HRVM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
++            HRBM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
+                     colorGlyph->glyphRun.fontEmSize,
+                     colorGlyph->glyphRun.glyphIndices,
+                     colorGlyph->glyphRun.glyphAdvances,
+@@ -638,6 +641,7 @@ void SkScalerContext_DW::generateColorMetrics(SkGlyph* glyph) {
+     glyph->fHeight = ibounds.fBottom - ibounds.fTop;
+     glyph->fLeft = ibounds.fLeft;
+     glyph->fTop = ibounds.fTop;
++    return true;
+ }
+ 
+ namespace {
+@@ -657,15 +661,14 @@ static void ReleaseProc(const void* ptr, void* context) {
+ }
+ }
+ 
+-void SkScalerContext_DW::generatePngMetrics(SkGlyph* glyph) {
++bool SkScalerContext_DW::generatePngMetrics(SkGlyph* glyph) {
+     SkASSERT(isPngGlyph(*glyph));
+-    SkASSERT(glyph->fMaskFormat == SkMask::Format::kARGB32_Format);
+     SkASSERT(this->getDWriteTypeface()->fDWriteFontFace4);
+ 
+     IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get();
+     DWRITE_GLYPH_IMAGE_DATA glyphData;
+     void* glyphDataContext;
+-    HRVM(fontFace4->GetGlyphImageData(glyph->getGlyphID(),
++    HRBM(fontFace4->GetGlyphImageData(glyph->getGlyphID(),
+                                       fTextSizeRender,
+                                       DWRITE_GLYPH_IMAGE_FORMATS_PNG,
+                                       &glyphData,
+@@ -680,7 +683,7 @@ void SkScalerContext_DW::generatePngMetrics(SkGlyph* glyph) {
+ 
+     std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(std::move(data));
+     if (!codec) {
+-        return;
++        return false;
+     }
+ 
+     SkImageInfo info = codec->getInfo();
+@@ -704,10 +707,10 @@ void SkScalerContext_DW::generatePngMetrics(SkGlyph* glyph) {
+     glyph->fHeight = bounds.height();
+     glyph->fLeft = bounds.left();
+     glyph->fTop = bounds.top();
+-    return;
++    return true;
+ }
+ 
+-void SkScalerContext_DW::generateMetrics(SkGlyph* glyph) {
++void SkScalerContext_DW::generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) {
+ 
+      // GetAlphaTextureBounds succeeds but sometimes returns empty bounds like
+      // { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }
+@@ -736,21 +739,20 @@ void SkScalerContext_DW::generateMetrics(SkGlyph* glyph) {
+     glyph->fHeight = 0;
+     glyph->fLeft = 0;
+     glyph->fTop = 0;
+-    glyph->fMaskFormat = fRec.fMaskFormat;
+ 
+     if (!this->generateAdvance(glyph)) {
+         return;
+     }
+ 
+-    if (fIsColorFont && isColorGlyph(*glyph)) {
++    if (fIsColorFont && isColorGlyph(*glyph) && generateColorMetrics(glyph)) {
+         glyph->fMaskFormat = SkMask::kARGB32_Format;
+-        generateColorMetrics(glyph);
++        glyph->setPath(alloc, nullptr, false);
+         return;
+     }
+ 
+-    if (fIsColorFont && isPngGlyph(*glyph)) {
++    if (fIsColorFont && isPngGlyph(*glyph) && generatePngMetrics(glyph)) {
+         glyph->fMaskFormat = SkMask::kARGB32_Format;
+-        generatePngMetrics(glyph);
++        glyph->setPath(alloc, nullptr, false);
+         return;
+     }
+ 
+@@ -1229,20 +1231,22 @@ void SkScalerContext_DW::generateImage(const SkGlyph& glyph) {
+     }
+ }
+ 
+-bool SkScalerContext_DW::generatePath(SkGlyphID glyph, SkPath* path) {
++bool SkScalerContext_DW::generatePath(const SkGlyph& glyph, SkPath* path) {
+     SkASSERT(path);
+     path->reset();
+ 
++    SkGlyphID glyphID = glyph.getGlyphID();
++
+     // DirectWrite treats all out of bounds glyph ids as having the same data as glyph 0.
+     // For consistency with all other backends, treat out of range glyph ids as an error.
+-    if (fGlyphCount <= glyph) {
++    if (fGlyphCount <= glyphID) {
+         return false;
+     }
+ 
+     SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
+     HRBM(SkDWriteGeometrySink::Create(path, &geometryToPath),
+          "Could not create geometry to path converter.");
+-    UINT16 glyphId = SkTo<UINT16>(glyph);
++    UINT16 glyphId = SkTo<UINT16>(glyphID);
+     {
+         Exclusive l(maybe_dw_mutex(*this->getDWriteTypeface()));
+         //TODO: convert to<->from DIUs? This would make a difference if hinting.
+diff --git a/src/ports/SkScalerContext_win_dw.h b/src/ports/SkScalerContext_win_dw.h
+index 8090003f60e3356c38adc81ed0626e03644801fa..8ed5cf9a5233b6a878556675cf1ec91c28d158f8 100644
+--- a/src/ports/SkScalerContext_win_dw.h
++++ b/src/ports/SkScalerContext_win_dw.h
+@@ -28,9 +28,9 @@ public:
+ 
+ protected:
+     bool generateAdvance(SkGlyph* glyph) override;
+-    void generateMetrics(SkGlyph* glyph) override;
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override;
+     void generateImage(const SkGlyph& glyph) override;
+-    bool generatePath(SkGlyphID glyph, SkPath* path) override;
++    bool generatePath(const SkGlyph&, SkPath*) override;
+     void generateFontMetrics(SkFontMetrics*) override;
+ 
+ private:
+@@ -69,11 +69,11 @@ private:
+ 
+     bool getColorGlyphRun(const SkGlyph& glyph, IDWriteColorGlyphRunEnumerator** colorGlyph);
+ 
+-    void generateColorMetrics(SkGlyph* glyph);
++    bool generateColorMetrics(SkGlyph* glyph);
+ 
+     void generateColorGlyphImage(const SkGlyph& glyph);
+ 
+-    void generatePngMetrics(SkGlyph* glyph);
++    bool generatePngMetrics(SkGlyph* glyph);
+ 
+     void generatePngGlyphImage(const SkGlyph& glyph);
+ 
+diff --git a/src/utils/SkCustomTypeface.cpp b/src/utils/SkCustomTypeface.cpp
+index bc2913f105ef961e77d1f3eea7b90dde9d39c212..ba170605103504b5eb5d72ebfe02085292a433b3 100644
+--- a/src/utils/SkCustomTypeface.cpp
++++ b/src/utils/SkCustomTypeface.cpp
+@@ -210,7 +210,7 @@ protected:
+         return true;
+     }
+ 
+-    void generateMetrics(SkGlyph* glyph) override {
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override {
+         glyph->zeroMetrics();
+         this->generateAdvance(glyph);
+         // Always generates from paths, so SkScalerContext::makeGlyph will figure the bounds.
+@@ -218,8 +218,8 @@ protected:
+ 
+     void generateImage(const SkGlyph&) override { SK_ABORT("Should have generated from path."); }
+ 
+-    bool generatePath(SkGlyphID glyph, SkPath* path) override {
+-        this->userTF()->fPaths[glyph].transform(fMatrix, path);
++    bool generatePath(const SkGlyph& glyph, SkPath* path) override {
++        this->userTF()->fPaths[glyph.getGlyphID()].transform(fMatrix, path);
+         return true;
+     }
+ 
+diff --git a/tools/fonts/RandomScalerContext.cpp b/tools/fonts/RandomScalerContext.cpp
+index 023f789e0ef55a63f2ba1ca2d102d858bd7e47b6..813d7a582e7828d0c8eae2311d812d7a252bfe66 100644
+--- a/tools/fonts/RandomScalerContext.cpp
++++ b/tools/fonts/RandomScalerContext.cpp
+@@ -24,9 +24,9 @@ public:
+ 
+ protected:
+     bool     generateAdvance(SkGlyph*) override;
+-    void     generateMetrics(SkGlyph*) override;
++    void     generateMetrics(SkGlyph*, SkArenaAlloc*) override;
+     void     generateImage(const SkGlyph&) override;
+-    bool     generatePath(SkGlyphID, SkPath*) override;
++    bool     generatePath(const SkGlyph&, SkPath*) override;
+     void     generateFontMetrics(SkFontMetrics*) override;
+ 
+ private:
+@@ -34,6 +34,9 @@ private:
+         return static_cast<SkRandomTypeface*>(this->getTypeface());
+     }
+     std::unique_ptr<SkScalerContext> fProxy;
++    // Many of the SkGlyphs returned are the same as those created by the fProxy.
++    // When they are not, the originals are kept here.
++    SkTHashMap<SkPackedGlyphID, SkGlyph> fProxyGlyphs;
+     bool                             fFakeIt;
+ };
+ 
+@@ -49,7 +52,7 @@ RandomScalerContext::RandomScalerContext(sk_sp<SkRandomTypeface>       face,
+ 
+ bool RandomScalerContext::generateAdvance(SkGlyph* glyph) { return fProxy->generateAdvance(glyph); }
+ 
+-void RandomScalerContext::generateMetrics(SkGlyph* glyph) {
++void RandomScalerContext::generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) {
+     // Here we will change the mask format of the glyph
+     // NOTE: this may be overridden by the base class (e.g. if a mask filter is applied).
+     SkMask::Format format = SkMask::kA8_Format;
+@@ -60,22 +63,32 @@ void RandomScalerContext::generateMetrics(SkGlyph* glyph) {
+         case 3: format = SkMask::kBW_Format; break;
+     }
+ 
+-    *glyph = fProxy->internalMakeGlyph(glyph->getPackedID(), format);
++    *glyph = fProxy->internalMakeGlyph(glyph->getPackedID(), format, alloc);
+ 
+     if (fFakeIt || (glyph->getGlyphID() % 4) != 2) {
+         return;
+     }
+ 
+-    SkPath path;
+-    if (!fProxy->getPath(glyph->getPackedID(), &path)) {
++    fProxy->getPath(*glyph, alloc);
++    if (!glyph->path()) {
+         return;
+     }
++
++    // The proxy glyph has a path, but this glyph does not.
++    // Stash the proxy glyph so it can be used later.
++    const SkGlyph* proxyGlyph = fProxyGlyphs.set(glyph->getPackedID(), std::move(*glyph));
++    const SkPath& proxyPath = *proxyGlyph->path();
++
++    *glyph = SkGlyph(glyph->getPackedID());
++    glyph->setPath(alloc, nullptr, false);
+     glyph->fMaskFormat = SkMask::kARGB32_Format;
++    glyph->fAdvanceX = proxyGlyph->fAdvanceX;
++    glyph->fAdvanceY = proxyGlyph->fAdvanceY;
+ 
+     SkRect         storage;
+     const SkPaint& paint = this->getRandomTypeface()->paint();
+     const SkRect&  newBounds =
+-            paint.doComputeFastBounds(path.getBounds(), &storage, SkPaint::kFill_Style);
++            paint.doComputeFastBounds(proxyPath.getBounds(), &storage, SkPaint::kFill_Style);
+     SkIRect ibounds;
+     newBounds.roundOut(&ibounds);
+     glyph->fLeft   = ibounds.fLeft;
+@@ -85,34 +98,18 @@ void RandomScalerContext::generateMetrics(SkGlyph* glyph) {
+ }
+ 
+ void RandomScalerContext::generateImage(const SkGlyph& glyph) {
+-    // TODO: can force down but not up
+-    /*
+-    SkMask::Format format = (SkMask::Format)glyph.fMaskFormat;
+-    switch (glyph.getGlyphID() % 4) {
+-        case 0: format = SkMask::kLCD16_Format; break;
+-        case 1: format = SkMask::kA8_Format; break;
+-        case 2: format = SkMask::kARGB32_Format; break;
+-        case 3: format = SkMask::kBW_Format; break;
+-    }
+-    const_cast<SkGlyph&>(glyph).fMaskFormat = format;
+-    */
+-
+     if (fFakeIt) {
+         sk_bzero(glyph.fImage, glyph.imageSize());
+         return;
+     }
+ 
+-    if (SkMask::kARGB32_Format != glyph.fMaskFormat) {
+-        fProxy->getImage(glyph);
+-        return;
+-    }
+-
+-    // If the format is ARGB, just draw the glyph from path.
+-    SkPath path;
+-    if (!fProxy->getPath(glyph.getPackedID(), &path)) {
++    SkGlyph* proxyGlyph = fProxyGlyphs.find(glyph.getPackedID());
++    if (!proxyGlyph || !proxyGlyph->path()) {
+         fProxy->getImage(glyph);
+         return;
+     }
++    const SkPath& path = *proxyGlyph->path();
++    const bool hairline = proxyGlyph->pathIsHairline();
+ 
+     SkBitmap bm;
+     bm.installPixels(SkImageInfo::MakeN32Premul(glyph.fWidth, glyph.fHeight),
+@@ -122,10 +119,22 @@ void RandomScalerContext::generateImage(const SkGlyph& glyph) {
+ 
+     SkCanvas canvas(bm);
+     canvas.translate(-SkIntToScalar(glyph.fLeft), -SkIntToScalar(glyph.fTop));
+-    canvas.drawPath(path, this->getRandomTypeface()->paint());
++    SkPaint paint = this->getRandomTypeface()->paint();
++    if (hairline) {
++        // We have a device path with effects already applied which is normally a fill path.
++        // However here we do not have a fill path and there is no area to fill.
++        paint.setStyle(SkPaint::kStroke_Style);
++        paint.setStroke(0);
++    }
++    canvas.drawPath(path, paint); //Need to modify the paint if the devPath is hairline
+ }
+ 
+-bool RandomScalerContext::generatePath(SkGlyphID glyph, SkPath* path) {
++bool RandomScalerContext::generatePath(const SkGlyph& glyph, SkPath* path) {
++    SkGlyph* shadowProxyGlyph = fProxyGlyphs.find(glyph.getPackedID());
++    if (shadowProxyGlyph && shadowProxyGlyph->path()) {
++        path->reset();
++        return false;
++    }
+     return fProxy->generatePath(glyph, path);
+ }
+ 
+diff --git a/tools/fonts/TestSVGTypeface.cpp b/tools/fonts/TestSVGTypeface.cpp
+index 5ab2b188ddc9f6847e06d26c486ee53576c0145d..c340c8df2f399122feb9d06f4017109924b72a81 100644
+--- a/tools/fonts/TestSVGTypeface.cpp
++++ b/tools/fonts/TestSVGTypeface.cpp
+@@ -194,12 +194,13 @@ protected:
+         return true;
+     }
+ 
+-    void generateMetrics(SkGlyph* glyph) override {
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) override {
+         SkGlyphID glyphID = glyph->getGlyphID();
+         glyphID           = glyphID < this->getTestSVGTypeface()->fGlyphCount ? glyphID : 0;
+ 
+         glyph->zeroMetrics();
+         glyph->fMaskFormat = SkMask::kARGB32_Format;
++        glyph->setPath(alloc, nullptr, false);
+         this->generateAdvance(glyph);
+ 
+         TestSVGTypeface::Glyph& glyphData = this->getTestSVGTypeface()->fGlyphs[glyphID];
+@@ -247,7 +248,9 @@ protected:
+         glyphData.render(&canvas);
+     }
+ 
+-    bool generatePath(SkGlyphID glyph, SkPath* path) override {
++    bool generatePath(const SkGlyph& glyph, SkPath* path) override {
++        // Should never get here since generateMetrics always sets the path to not exist.
++        SK_ABORT("Path requested, but it should have been indicated that there isn't one.");
+         path->reset();
+         return false;
+     }
+diff --git a/tools/fonts/TestTypeface.cpp b/tools/fonts/TestTypeface.cpp
+index 00c3374f5186fa0f6eb27984d9d514ad81312f98..e88c52c3f37be948a65e35d8935f5938d5b9d219 100644
+--- a/tools/fonts/TestTypeface.cpp
++++ b/tools/fonts/TestTypeface.cpp
+@@ -170,7 +170,7 @@ protected:
+         return true;
+     }
+ 
+-    void generateMetrics(SkGlyph* glyph) override {
++    void generateMetrics(SkGlyph* glyph, SkArenaAlloc*) override {
+         glyph->zeroMetrics();
+         this->generateAdvance(glyph);
+         // Always generates from paths, so SkScalerContext::makeGlyph will figure the bounds.
+@@ -178,8 +178,8 @@ protected:
+ 
+     void generateImage(const SkGlyph&) override { SK_ABORT("Should have generated from path."); }
+ 
+-    bool generatePath(SkGlyphID glyph, SkPath* path) override {
+-        *path = this->getTestTypeface()->getPath(glyph).makeTransform(fMatrix);
++    bool generatePath(const SkGlyph& glyph, SkPath* path) override {
++        *path = this->getTestTypeface()->getPath(glyph.getGlyphID()).makeTransform(fMatrix);
+         return true;
+     }
+