fonts: Handle fallback to using paths for text rendering for remoting.

SkRemoteGlyphCache only sends images for glyphs, even for cases where
the gpu falls back to drawing text as paths. This includes cases in
SkDraw::ShouldDrawTextAsPaths and when the glyph exceeds the max bounds
that can fit on the atlas. Fix this by identifying these cases in the
renderer and sending paths instead.

Note: We still don't handle distance field text correctly.

R=herb@google.com, bsalomon@google.com

Bug: skia:7913
Change-Id: I17d4eccbeaa2e995ae67b61c76cebd27f8280329
Reviewed-on: https://skia-review.googlesource.com/128203
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Khusal Sagar <khushalsagar@chromium.org>
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 3cc63b8..85c2e3b 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -1690,6 +1690,7 @@
     friend class SkPDFDevice;
     friend class SkScalerContext;  // for computeLuminanceColor()
     friend class SkTextBaseIter;
+    friend class SkTextBlobCacheDiffCanvas;
 };
 
 #endif
diff --git a/src/core/SkGlyph.cpp b/src/core/SkGlyph.cpp
index 836a47d..9e040e3 100644
--- a/src/core/SkGlyph.cpp
+++ b/src/core/SkGlyph.cpp
@@ -60,6 +60,11 @@
                                         : width * format_alignment(format);
 }
 
+size_t SkGlyph::formatAlignment() const {
+    auto format = static_cast<SkMask::Format>(fMaskFormat);
+    return format_alignment(format);
+}
+
 size_t SkGlyph::allocImage(SkArenaAlloc* alloc) {
     auto size = this->computeImageSize();
     auto format = static_cast<SkMask::Format>(fMaskFormat);
diff --git a/src/core/SkGlyph.h b/src/core/SkGlyph.h
index dfb51cb..307042f 100644
--- a/src/core/SkGlyph.h
+++ b/src/core/SkGlyph.h
@@ -155,6 +155,7 @@
 
     void initWithGlyphID(SkPackedGlyphID glyph_id);
 
+    size_t formatAlignment() const;
     size_t allocImage(SkArenaAlloc* alloc);
 
     size_t rowBytes() const;
diff --git a/src/core/SkGlyphCache.cpp b/src/core/SkGlyphCache.cpp
index d9d8d41..bf5f77c 100644
--- a/src/core/SkGlyphCache.cpp
+++ b/src/core/SkGlyphCache.cpp
@@ -194,7 +194,9 @@
     return glyph.fImage;
 }
 
-void SkGlyphCache::initializeImage(const volatile void* data, size_t size, SkGlyph* glyph) {
+bool SkGlyphCache::initializeImage(const volatile void* data, size_t size, SkGlyph* glyph) {
+    if (glyph->fImage) return false;
+
     if (glyph->fWidth > 0 && glyph->fWidth < kMaxGlyphWidth) {
         size_t allocSize = glyph->allocImage(&fAlloc);
         // check that alloc() actually succeeded
@@ -204,6 +206,8 @@
             fMemoryUsed += size;
         }
     }
+
+    return true;
 }
 
 const SkPath* SkGlyphCache::findPath(const SkGlyph& glyph) {
@@ -225,6 +229,25 @@
     return glyph.fPathData ? glyph.fPathData->fPath : nullptr;
 }
 
+bool SkGlyphCache::initializePath(SkGlyph* glyph, const volatile void* data, size_t size) {
+    if (glyph->fPathData) return false;
+
+    if (glyph->fWidth) {
+        SkGlyph::PathData* pathData = fAlloc.make<SkGlyph::PathData>();
+        glyph->fPathData = pathData;
+        pathData->fIntercept = nullptr;
+        SkPath* path = new SkPath;
+        if (!path->readFromMemory(const_cast<const void*>(data), size)) {
+            delete path;
+            return false;
+        }
+        pathData->fPath = path;
+        fMemoryUsed += compute_path_size(*path);
+    }
+
+    return true;
+}
+
 #include "../pathops/SkPathOpsCubic.h"
 #include "../pathops/SkPathOpsQuad.h"
 
diff --git a/src/core/SkGlyphCache.h b/src/core/SkGlyphCache.h
index e069efd..2e73557 100644
--- a/src/core/SkGlyphCache.h
+++ b/src/core/SkGlyphCache.h
@@ -88,9 +88,10 @@
     */
     const void* findImage(const SkGlyph&);
 
-    /** Initializes the image associated with the glyph with |data|.
+    /** Initializes the image associated with the glyph with |data|. Returns false if an image
+     * already exists.
      */
-    void initializeImage(const volatile void* data, size_t size, SkGlyph*);
+    bool initializeImage(const volatile void* data, size_t size, SkGlyph*);
 
     /** If the advance axis intersects the glyph's path, append the positions scaled and offset
         to the array (if non-null), and set the count to the updated array length.
@@ -103,6 +104,11 @@
     */
     const SkPath* findPath(const SkGlyph&);
 
+    /** Initializes the path associated with the glyph with |data|. Returns false if a path
+     * already exits or data is invalid.
+     */
+    bool initializePath(SkGlyph*, const volatile void* data, size_t size);
+
     /** Return the vertical metrics for this strike.
     */
     const SkPaint::FontMetrics& getFontMetrics() const {
diff --git a/src/core/SkRemoteGlyphCache.cpp b/src/core/SkRemoteGlyphCache.cpp
index 985c065..c7f1f44 100644
--- a/src/core/SkRemoteGlyphCache.cpp
+++ b/src/core/SkRemoteGlyphCache.cpp
@@ -13,12 +13,18 @@
 #include <tuple>
 
 #include "SkDevice.h"
+#include "SkDraw.h"
 #include "SkFindAndPlaceGlyph.h"
+#include "SkPathEffect.h"
 #include "SkStrikeCache.h"
 #include "SkTextBlobRunIterator.h"
 #include "SkTraceEvent.h"
 #include "SkTypeface_remote.h"
 
+#if SK_SUPPORT_GPU
+#include "GrDrawOpAtlas.h"
+#endif
+
 static SkDescriptor* auto_descriptor_from_desc(const SkDescriptor* source_desc,
                                                SkFontID font_id,
                                                SkAutoDescriptor* ad) {
@@ -54,33 +60,6 @@
     return desc;
 }
 
-template <typename T>
-class ArraySlice final : public std::tuple<const T*, size_t> {
-public:
-    // Additional constructors as needed.
-    ArraySlice(const T* data, size_t size) : fData{data}, fSize{size} { }
-    ArraySlice() : ArraySlice<T>(nullptr, 0) { }
-
-    const T* begin() {
-        return this->data();
-    }
-    const T* end() {
-        return &this->data()[this->size()];
-    }
-
-    const T* data() const {
-        return fData;
-    }
-
-    size_t size() const {
-        return fSize;
-    }
-
-private:
-    const T* fData;
-    size_t   fSize;
-};
-
 // -- Serializer ----------------------------------------------------------------------------------
 
 size_t pad(size_t size, size_t alignment) { return (size + (alignment - 1)) & ~(alignment - 1); }
@@ -113,19 +92,13 @@
         memcpy(result, &desc, desc.getLength());
     }
 
-    template <typename T>
-    T* allocateArray(int count) {
-        auto result = allocate(sizeof(T) * count, alignof(T));
-        return new (result) T[count];
-    }
-
-private:
     void* allocate(size_t size, size_t alignment) {
         size_t aligned = pad(fBuffer->size(), alignment);
         fBuffer->resize(aligned + size);
         return &(*fBuffer)[aligned];
     }
 
+private:
     std::vector<uint8_t>* fBuffer;
 };
 
@@ -157,14 +130,8 @@
         return true;
     }
 
-    template <typename T>
-    ArraySlice<T> readArray(int count) {
-        size_t size = count * sizeof(T);
-        const T* base = (const T*)this->ensureAtLeast(size, alignof(T));
-        if (!base) return ArraySlice<T>();
-
-        ArraySlice<T> result = ArraySlice<T>{base, (uint32_t)count};
-        return result;
+    const volatile void* read(size_t size, size_t alignment) {
+      return this->ensureAtLeast(size, alignment);
     }
 
 private:
@@ -185,6 +152,21 @@
     size_t fBytesRead = 0u;
 };
 
+// Paths use a SkWriter32 which requires 4 byte alignment.
+static const size_t kPathAlignment  = 4u;
+
+bool read_path(Deserializer* deserializer, SkGlyph* glyph, SkGlyphCache* cache) {
+    size_t pathSize = 0u;
+    if (!deserializer->read<size_t>(&pathSize)) return false;
+
+    if (pathSize == 0u) return true;
+
+    auto* path = deserializer->read(pathSize, kPathAlignment);
+    if (!path) return false;
+
+    return cache->initializePath(glyph, path, pathSize);
+}
+
 size_t SkDescriptorMapOperators::operator()(const SkDescriptor* key) const {
     return key->getChecksum();
     }
@@ -281,15 +263,27 @@
         FAIL_AND_RETURN
     }
 
+    if (it.positioning() == SkTextBlob::kDefault_Positioning) {
+        // Default positioning needs advances. Can't do that.
+        TRACE_EVENT0("skia", "kDefault_Positioning");
+        FAIL_AND_RETURN
+    }
+
+    SkMatrix runMatrix{fDeviceMatrix};
+    runMatrix.preConcat(this->getTotalMatrix());
+    runMatrix.preTranslate(position.x(), position.y());
+    runMatrix.preTranslate(it.offset().x(), it.offset().y());
+
+    // If the matrix has perspective, we fall back to using distance field text or paths.
+    // TODO: Add distance field text support, and FallbackTextHelper logic from GrAtlasTextContext.
+    if (SkDraw::ShouldDrawTextAsPaths(runPaint, runMatrix)) {
+        this->processGlyphRunForPaths(it, runPaint);
+        return;
+    }
+
     using PosFn = SkPoint(*)(int index, const SkScalar* pos);
     PosFn posFn;
     switch (it.positioning()) {
-        case SkTextBlob::kDefault_Positioning: {
-            // Default positioning needs advances. Can't do that.
-            TRACE_EVENT0("skia", "kDefault_Positioning");
-            FAIL_AND_RETURN
-        }
-
         case SkTextBlob::kHorizontal_Positioning:
             posFn = [](int index, const SkScalar* pos) {
                 return SkPoint{pos[index], 0};
@@ -308,17 +302,6 @@
             SK_ABORT("unhandled positioning mode");
     }
 
-    SkMatrix blobMatrix{fDeviceMatrix};
-    blobMatrix.preConcat(this->getTotalMatrix());
-    if (blobMatrix.hasPerspective()) {
-        TRACE_EVENT0("skia", "hasPerspective");
-        FAIL_AND_RETURN
-    }
-    blobMatrix.preTranslate(position.x(), position.y());
-
-    SkMatrix runMatrix{blobMatrix};
-    runMatrix.preTranslate(it.offset().x(), it.offset().y());
-
     using MapFn = SkPoint(*)(const SkMatrix& m, SkPoint pt);
     MapFn mapFn;
     switch ((int)runMatrix.getType()) {
@@ -350,35 +333,13 @@
     }
 
     SkScalerContextRec deviceSpecificRec;
-    SkScalerContextRec keyRec;
     SkScalerContextEffects effects;
-
-    SkScalerContext::MakeRecAndEffects(runPaint, &fSurfaceProps, &runMatrix,
-                                       SkScalerContextFlags::kFakeGammaAndBoostContrast,
-                                       &deviceSpecificRec,
-                                       &effects, true);
-
-    SkScalerContext::MakeRecAndEffects(runPaint, &fSurfaceProps, &runMatrix,
-                                       SkScalerContextFlags::kFakeGammaAndBoostContrast,
-                                       &keyRec,
-                                       &effects, false);
-
-
-    TRACE_EVENT1("skia", "RecForDesc", "rec", TRACE_STR_COPY(keyRec.dump().c_str()));
-
-    // TODO: possible perf improvement - move descriptor calculation into getOrCreateCache.
-    auto deviceDescriptor =
-            SkScalerContext::DescriptorGivenRecAndEffects(deviceSpecificRec, effects);
-    auto keyDescriptor =
-            SkScalerContext::DescriptorGivenRecAndEffects(keyRec, effects);
-    auto* glyphCacheState =
-            static_cast<SkStrikeServer*>(fStrikeServer)
-                    ->getOrCreateCache(
-                            runPaint.getTypeface(),
-                            std::move(deviceDescriptor),
-                            std::move(keyDescriptor));
+    auto* glyphCacheState = static_cast<SkStrikeServer*>(fStrikeServer)
+                                    ->getOrCreateCache(runPaint, &fSurfaceProps, &runMatrix,
+                                                       &deviceSpecificRec, &effects);
     SkASSERT(glyphCacheState);
 
+    const bool asPath = false;
     bool isSubpixel =
             SkToBool(deviceSpecificRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag);
     SkAxisAlignment axisAlignment = deviceSpecificRec.computeAxisAlignmentForHText();
@@ -393,18 +354,43 @@
 
         glyphCacheState->addGlyph(runPaint.getTypeface(),
                                   effects,
-                                  SkPackedGlyphID(glyphs[index], subPixelPos.x(), subPixelPos.y()));
+                                  SkPackedGlyphID(glyphs[index], subPixelPos.x(), subPixelPos.y()),
+                                  asPath);
+    }
+}
+
+void SkTextBlobCacheDiffCanvas::processGlyphRunForPaths(const SkTextBlobRunIterator& it,
+                                                        const SkPaint& runPaint) {
+    TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRunForPaths");
+
+    // The code below borrowed from GrAtlasTextContext::DrawBmpPosTextAsPaths.
+    SkPaint pathPaint(runPaint);
+    pathPaint.setupForAsPaths();
+    pathPaint.setStyle(SkPaint::kFill_Style);
+    pathPaint.setPathEffect(nullptr);
+
+    SkScalerContextRec deviceSpecificRec;
+    SkScalerContextEffects effects;
+    auto* glyphCacheState = static_cast<SkStrikeServer*>(fStrikeServer)
+                                    ->getOrCreateCache(pathPaint, &fSurfaceProps, nullptr,
+                                                       &deviceSpecificRec, &effects);
+
+    const bool asPath = true;
+    const SkIPoint subPixelPos{0, 0};
+    const uint16_t* glyphs = it.glyphs();
+    for (uint32_t index = 0; index < it.glyphCount(); index++) {
+        glyphCacheState->addGlyph(runPaint.getTypeface(),
+                                  effects,
+                                  SkPackedGlyphID(glyphs[index], subPixelPos.x(), subPixelPos.y()),
+                                  asPath);
     }
 }
 
 struct StrikeSpec {
     StrikeSpec() {}
-    StrikeSpec(SkFontID typefaceID_, size_t glyphCount_, SkDiscardableHandleId discardableHandleId_)
-            : typefaceID{typefaceID_}
-            , glyphCount{glyphCount_}
-            , discardableHandleId(discardableHandleId_) {}
+    StrikeSpec(SkFontID typefaceID_, SkDiscardableHandleId discardableHandleId_)
+            : typefaceID{typefaceID_}, discardableHandleId(discardableHandleId_) {}
     SkFontID typefaceID = 0u;
-    size_t glyphCount = 0u;
     SkDiscardableHandleId discardableHandleId = 0u;
     /* desc */
     /* n X (glyphs ids) */
@@ -462,11 +448,23 @@
 }
 
 SkStrikeServer::SkGlyphCacheState* SkStrikeServer::getOrCreateCache(
-        SkTypeface* tf,
-        std::unique_ptr<SkDescriptor> deviceDesc,
-        std::unique_ptr<SkDescriptor> keyDesc) {
-    SkASSERT(deviceDesc);
-    SkASSERT(keyDesc);
+        const SkPaint& paint,
+        const SkSurfaceProps* props,
+        const SkMatrix* matrix,
+        SkScalerContextRec* deviceRec,
+        SkScalerContextEffects* effects) {
+    SkScalerContextRec keyRec;
+    SkScalerContext::MakeRecAndEffects(paint, props, matrix,
+                                       SkScalerContextFlags::kFakeGammaAndBoostContrast, deviceRec,
+                                       effects, true);
+    SkScalerContext::MakeRecAndEffects(paint, props, matrix,
+                                       SkScalerContextFlags::kFakeGammaAndBoostContrast, &keyRec,
+                                       effects, false);
+    TRACE_EVENT1("skia", "RecForDesc", "rec", TRACE_STR_COPY(keyRec.dump().c_str()));
+
+    // TODO: possible perf improvement - don't recompute the device desc on cache hit.
+    auto deviceDesc = SkScalerContext::DescriptorGivenRecAndEffects(*deviceRec, *effects);
+    auto keyDesc = SkScalerContext::DescriptorGivenRecAndEffects(keyRec, *effects);
 
     // Already locked.
     if (fLockedDescs.find(keyDesc.get()) != fLockedDescs.end()) {
@@ -490,6 +488,7 @@
         fRemoteGlyphStateMap.erase(it);
     }
 
+    auto* tf = paint.getTypeface();
     const SkFontID typefaceId = tf->uniqueID();
     if (!fCachedTypefaces.contains(typefaceId)) {
         fCachedTypefaces.add(typefaceId);
@@ -499,10 +498,8 @@
 
     auto* keyDescPtr = keyDesc.get();
     auto newHandle = fDiscardableHandleManager->createHandle();
-    auto cacheState = skstd::make_unique<SkGlyphCacheState>(
-            std::move(deviceDesc),
-            std::move(keyDesc),
-            newHandle);
+    auto cacheState = skstd::make_unique<SkGlyphCacheState>(std::move(deviceDesc),
+                                                            std::move(keyDesc), newHandle);
     auto* cacheStatePtr = cacheState.get();
 
     fLockedDescs.insert(keyDescPtr);
@@ -510,10 +507,10 @@
     return cacheStatePtr;
 }
 
-SkStrikeServer::SkGlyphCacheState::SkGlyphCacheState(
-        std::unique_ptr<SkDescriptor> deviceDescriptor,
-        std::unique_ptr<SkDescriptor> keyDescriptor,
-        uint32_t discardable_handle_id)
+SkStrikeServer::SkGlyphCacheState::SkGlyphCacheState(std::unique_ptr<SkDescriptor> deviceDescriptor,
+                                                     std::unique_ptr<SkDescriptor>
+                                                             keyDescriptor,
+                                                     uint32_t discardable_handle_id)
         : fDeviceDescriptor(std::move(deviceDescriptor))
         , fKeyDescriptor(std::move(keyDescriptor))
         , fDiscardableHandleId(discardable_handle_id) {
@@ -525,51 +522,95 @@
 
 void SkStrikeServer::SkGlyphCacheState::addGlyph(SkTypeface* typeface,
                                                  const SkScalerContextEffects& effects,
-                                                 SkPackedGlyphID glyph) {
+                                                 SkPackedGlyphID glyph,
+                                                 bool asPath) {
+    auto* cache = asPath ? &fCachedGlyphPaths : &fCachedGlyphImages;
+    auto* pending = asPath ? &fPendingGlyphPaths : &fPendingGlyphImages;
+
     // Already cached.
-    if (fCachedGlyphs.contains(glyph)) return;
+    if (cache->contains(glyph)) return;
 
     // Serialize and cache. Also create the scalar context to use when serializing
     // this glyph.
-    fCachedGlyphs.add(glyph);
-    fPendingGlyphs.push_back(glyph);
-    if (!fContext) fContext = typeface->createScalerContext(effects, fDeviceDescriptor.get(), false);
+    cache->add(glyph);
+    pending->push_back(glyph);
+    if (!fContext) {
+        fContext = typeface->createScalerContext(effects, fDeviceDescriptor.get(), false);
+    }
 }
 
 void SkStrikeServer::SkGlyphCacheState::writePendingGlyphs(Serializer* serializer) {
     // Write the desc.
-    serializer->emplace<StrikeSpec>(fContext->getTypeface()->uniqueID(), fPendingGlyphs.size(),
-                                    fDiscardableHandleId);
+    serializer->emplace<StrikeSpec>(fContext->getTypeface()->uniqueID(), fDiscardableHandleId);
     serializer->writeDescriptor(*fKeyDescriptor.get());
 
     // Write FontMetrics.
+    // TODO(khushalsagar): Do we need to re-send each time?
     SkPaint::FontMetrics fontMetrics;
     fContext->getFontMetrics(&fontMetrics);
     serializer->write<SkPaint::FontMetrics>(fontMetrics);
 
-    // Write Glyphs.
-    for (const auto& glyphID : fPendingGlyphs) {
+    // Write glyphs images.
+    serializer->emplace<size_t>(fPendingGlyphImages.size());
+    for (const auto& glyphID : fPendingGlyphImages) {
         auto glyph = serializer->emplace<SkGlyph>();
         glyph->initWithGlyphID(glyphID);
         fContext->getMetrics(glyph);
-        auto imageSize = glyph->computeImageSize();
         glyph->fPathData = nullptr;
         glyph->fImage = nullptr;
 
-        if (imageSize > 0) {
-            // Since the allocateArray can move glyph, make one that stays in one place.
-            SkGlyph stationaryGlyph = *glyph;
-            stationaryGlyph.fImage = serializer->allocateArray<uint8_t>(imageSize);
-            fContext->getImage(stationaryGlyph);
+        bool tooLargeForAtlas = false;
+#if SK_SUPPORT_GPU
+        tooLargeForAtlas = GrDrawOpAtlas::GlyphTooLargeForAtlas(glyph->fWidth, glyph->fHeight);
+#endif
+        if (tooLargeForAtlas) {
+            // Add this to the path cache, since we will always fall back to using paths
+            // for this glyph.
+            fCachedGlyphPaths.add(glyphID);
+            writeGlyphPath(glyphID, serializer);
+            continue;
         }
+
+        auto imageSize = glyph->computeImageSize();
+        if (imageSize == 0u) continue;
+
+        // Since the allocate can move glyph, make one that stays in one place.
+        SkGlyph stationaryGlyph = *glyph;
+        stationaryGlyph.fImage = serializer->allocate(imageSize, stationaryGlyph.formatAlignment());
+        fContext->getImage(stationaryGlyph);
     }
+    fPendingGlyphImages.clear();
+
+    // Write glyphs paths.
+    serializer->emplace<size_t>(fPendingGlyphPaths.size());
+    for (const auto& glyphID : fPendingGlyphPaths) {
+        auto glyph = serializer->emplace<SkGlyph>();
+        glyph->initWithGlyphID(glyphID);
+        fContext->getMetrics(glyph);
+        glyph->fPathData = nullptr;
+        glyph->fImage = nullptr;
+        writeGlyphPath(glyphID, serializer);
+    }
+    fPendingGlyphPaths.clear();
 
     // Note that we reset the context after serializing pending glyphs since we
     // don't want to extend the lifetime of the typeface.
-    fPendingGlyphs.clear();
     fContext.reset();
 }
 
+void SkStrikeServer::SkGlyphCacheState::writeGlyphPath(const SkPackedGlyphID& glyphID,
+                                                       Serializer* serializer) const {
+    SkPath path;
+    if (!fContext->getPath(glyphID, &path)) {
+        serializer->write<size_t>(0u);
+        return;
+    }
+
+    size_t pathSize = path.writeToMemory(nullptr);
+    serializer->write<size_t>(pathSize);
+    path.writeToMemory(serializer->allocate(pathSize, kPathAlignment));
+}
+
 // SkStrikeClient -----------------------------------------
 
 class SkStrikeClient::DiscardableStrikePinner : public SkStrikePinner {
@@ -656,20 +697,49 @@
                                                                 fDiscardableHandleManager));
         }
 
-        for (size_t j = 0; j < spec.glyphCount; j++) {
+        size_t glyphImagesCount = 0u;
+        if (!deserializer.read<size_t>(&glyphImagesCount)) READ_FAILURE
+        for (size_t j = 0; j < glyphImagesCount; j++) {
             SkGlyph glyph;
             if (!deserializer.read<SkGlyph>(&glyph)) READ_FAILURE
 
             SkGlyph* allocatedGlyph = strike->getRawGlyphByID(glyph.getPackedID());
+            // Don't override the path, if the glyph has one.
+            auto* glyphPath = allocatedGlyph->fPathData;
             *allocatedGlyph = glyph;
+            allocatedGlyph->fPathData = glyphPath;
 
-            ArraySlice<uint8_t> image;
-            auto imageSize = glyph.computeImageSize();
-            if (imageSize != 0) {
-                image = deserializer.readArray<uint8_t>(imageSize);
-                if (!image.data()) READ_FAILURE
-                strike->initializeImage(image.data(), image.size(), allocatedGlyph);
+            bool tooLargeForAtlas = false;
+#if SK_SUPPORT_GPU
+            tooLargeForAtlas = GrDrawOpAtlas::GlyphTooLargeForAtlas(glyph.fWidth, glyph.fHeight);
+#endif
+            if (tooLargeForAtlas) {
+                if (!read_path(&deserializer, allocatedGlyph, strike.get())) READ_FAILURE
+                continue;
             }
+
+            auto imageSize = glyph.computeImageSize();
+            if (imageSize == 0u) continue;
+
+            auto* image = deserializer.read(imageSize, allocatedGlyph->formatAlignment());
+            if (!image ||
+                !strike->initializeImage(image, imageSize, allocatedGlyph))
+                READ_FAILURE
+        }
+
+        size_t glyphPathsCount = 0u;
+        if (!deserializer.read<size_t>(&glyphPathsCount)) READ_FAILURE
+        for (size_t j = 0; j < glyphPathsCount; j++) {
+            SkGlyph glyph;
+            if (!deserializer.read<SkGlyph>(&glyph)) READ_FAILURE
+
+            SkGlyph* allocatedGlyph = strike->getRawGlyphByID(glyph.getPackedID());
+            // Don't override the image, if the glyph has one.
+            auto* glyphImage = allocatedGlyph->fImage;
+            *allocatedGlyph = glyph;
+            allocatedGlyph->fImage = glyphImage;
+
+            if (!read_path(&deserializer, allocatedGlyph, strike.get())) READ_FAILURE
         }
     }
 
diff --git a/src/core/SkRemoteGlyphCache.h b/src/core/SkRemoteGlyphCache.h
index 36fe593..60d8807 100644
--- a/src/core/SkRemoteGlyphCache.h
+++ b/src/core/SkRemoteGlyphCache.h
@@ -69,6 +69,7 @@
     void processGlyphRun(const SkPoint& position,
                          const SkTextBlobRunIterator& it,
                          const SkPaint& runPaint);
+    void processGlyphRunForPaths(const SkTextBlobRunIterator& it, const SkPaint& runPaint);
 
     const SkMatrix fDeviceMatrix;
     const SkSurfaceProps fSurfaceProps;
@@ -122,9 +123,11 @@
                           SkDiscardableHandleId discardableHandleId);
         ~SkGlyphCacheState();
 
-        void addGlyph(SkTypeface*, const SkScalerContextEffects&, SkPackedGlyphID);
+        void addGlyph(SkTypeface*, const SkScalerContextEffects&, SkPackedGlyphID, bool pathOnly);
         void writePendingGlyphs(Serializer* serializer);
-        bool has_pending_glyphs() const { return !fPendingGlyphs.empty(); }
+        bool has_pending_glyphs() const {
+            return !fPendingGlyphImages.empty() || !fPendingGlyphPaths.empty();
+        }
         SkDiscardableHandleId discardable_handle_id() const { return fDiscardableHandleId; }
         const SkDescriptor& getDeviceDescriptor() {
             return *fDeviceDescriptor;
@@ -135,12 +138,16 @@
         }
 
     private:
+        void writeGlyphPath(const SkPackedGlyphID& glyphID, Serializer* serializer) const;
+
         // The set of glyphs cached on the remote client.
-        SkTHashSet<SkPackedGlyphID> fCachedGlyphs;
+        SkTHashSet<SkPackedGlyphID> fCachedGlyphImages;
+        SkTHashSet<SkPackedGlyphID> fCachedGlyphPaths;
 
         // The set of glyphs which has not yet been serialized and sent to the
         // remote client.
-        std::vector<SkPackedGlyphID> fPendingGlyphs;
+        std::vector<SkPackedGlyphID> fPendingGlyphImages;
+        std::vector<SkPackedGlyphID> fPendingGlyphPaths;
 
         // The device descriptor is used to create the scaler context. The glyphs to have the
         // correct device rendering. The key descriptor is used for communication. The GPU side will
@@ -153,8 +160,9 @@
         std::unique_ptr<SkScalerContext> fContext;
     };
 
-    SkGlyphCacheState* getOrCreateCache(
-            SkTypeface*, std::unique_ptr<SkDescriptor>, std::unique_ptr<SkDescriptor>);
+    SkGlyphCacheState* getOrCreateCache(const SkPaint&, const SkSurfaceProps*, const SkMatrix*,
+                                        SkScalerContextRec* deviceRec,
+                                        SkScalerContextEffects* effects);
 
 private:
     SkDescriptorMap<std::unique_ptr<SkGlyphCacheState>> fRemoteGlyphStateMap;
diff --git a/tests/SkRemoteGlyphCacheTest.cpp b/tests/SkRemoteGlyphCacheTest.cpp
index dbe36d1..811bf1b 100644
--- a/tests/SkRemoteGlyphCacheTest.cpp
+++ b/tests/SkRemoteGlyphCacheTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkDraw.h"
 #include "SkGraphics.h"
 #include "SkMutex.h"
 #include "SkRemoteGlyphCache.h"
@@ -71,9 +72,15 @@
     return builder.make();
 }
 
-SkBitmap RasterBlob(sk_sp<SkTextBlob> blob, int width, int height) {
+#define COMPARE_BLOBS(expected, actual, reporter)                                        \
+    for (int i = 0; i < expected.width(); ++i) {                                         \
+        for (int j = 0; j < expected.height(); ++j) {                                    \
+            REPORTER_ASSERT(reporter, expected.getColor(i, j) == actual.getColor(i, j)); \
+        }                                                                                \
+    }
+
+SkBitmap RasterBlob(sk_sp<SkTextBlob> blob, int width, int height, const SkPaint& paint) {
     auto surface = SkSurface::MakeRasterN32Premul(width, height);
-    SkPaint paint;
     surface->getCanvas()->drawTextBlob(blob.get(), 0u, 0u, paint);
     SkBitmap bitmap;
     bitmap.allocN32Pixels(width, height);
@@ -99,6 +106,7 @@
     sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
     SkStrikeServer server(discardableManager.get());
     SkStrikeClient client(discardableManager);
+    const SkPaint paint;
 
     // Server.
     auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
@@ -108,7 +116,6 @@
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
     SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
-    SkPaint paint;
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
 
     std::vector<uint8_t> serverStrikeData;
@@ -120,13 +127,9 @@
                     client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
     auto clientBlob = buildTextBlob(clientTf, glyphCount);
 
-    SkBitmap expected = RasterBlob(serverBlob, 10, 10);
-    SkBitmap actual = RasterBlob(clientBlob, 10, 10);
-    for (int i = 0; i < expected.width(); ++i) {
-        for (int j = 0; j < expected.height(); ++j) {
-            REPORTER_ASSERT(reporter, expected.getColor(i, j) == actual.getColor(i, j));
-        }
-    }
+    SkBitmap expected = RasterBlob(serverBlob, 10, 10, paint);
+    SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint);
+    COMPARE_BLOBS(expected, actual, reporter);
 }
 
 DEF_TEST(SkRemoteGlyphCache_StrikeLockingServer, reporter) {
@@ -246,3 +249,37 @@
                     client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
     SkStrikeCache::Validate();
 }
+
+DEF_TEST(SkRemoteGlyphCache_DrawTextAsPath, reporter) {
+    sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
+    SkStrikeServer server(discardableManager.get());
+    SkStrikeClient client(discardableManager);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(0);
+    REPORTER_ASSERT(reporter, SkDraw::ShouldDrawTextAsPaths(paint, SkMatrix::I()));
+
+    // Server.
+    auto serverTf = SkTypeface::MakeFromName("monospace", SkFontStyle());
+    auto serverTfData = server.serializeTypeface(serverTf.get());
+
+    int glyphCount = 10;
+    auto serverBlob = buildTextBlob(serverTf, glyphCount);
+    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
+    cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
+
+    std::vector<uint8_t> serverStrikeData;
+    server.writeStrikeData(&serverStrikeData);
+
+    // Client.
+    auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
+    REPORTER_ASSERT(reporter,
+                    client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
+    auto clientBlob = buildTextBlob(clientTf, glyphCount);
+
+    SkBitmap expected = RasterBlob(serverBlob, 10, 10, paint);
+    SkBitmap actual = RasterBlob(clientBlob, 10, 10, paint);
+    COMPARE_BLOBS(expected, actual, reporter);
+    SkStrikeCache::Validate();
+}