From b854499783643689c608a113a340d7c75460e215 Mon Sep 17 00:00:00 2001
From: Thomas Smith <thomsmit@google.com>
Date: Fri, 29 May 2026 11:35:55 -0400
Subject: [PATCH] Reland "Reconstruct subRun bounds from glyphs"

* Allow subRuns which are drawn with drawable or path rendering to exit gracefully instead of failing.

* Remove erroneous SFINAE

This reverts commit c480ba2eb2eba29a78b28b188cf2e8a3fb44dd49.

Original change's description:
> Revert "Reconstruct subRun bounds from glyphs"
>
> This reverts commit f93ed13d77fb28b1ab5c058c10bda3049ccbc5f5.
>
> Reason for revert: SkRemoteGlyphCacheTest failures and build failures
>
>          ../../../../../skia/tests/SkRemoteGlyphCacheTest.cpp:193:37: error: unused function template 'get_container_ptr' [-Werror,-Wunused-template]
>   193 | const sktext::gpu::SubRunContainer* get_container_ptr(const T& t) {
>       |                                     ^~~~~~~~~~~~~~~~~
> ../../../../../skia/tests/SkRemoteGlyphCacheTest.cpp:212:37: error: unused function template 'get_container' [-Werror,-Wunused-template]
>   212 | const sktext::gpu::SubRunContainer* get_container(T* slugImpl) {
>       |                                     ^~~~~~~~~~~~~
> 2 errors generated.
>
> and
>
>
>               ../../../../../skia/tests/SkRemoteGlyphCacheTest.cpp:660	Dst subrun is null for TransformedMaskSubRun [SkRemoteGlyphCache_SubRunBoundsReconstruction, OpenGL]
>
> Original change's description:
> > Reconstruct subRun bounds from glyphs
> >
> > * Reconstruct the bounds of a subRun after deserialization instead of packaging onto the VertexFiller.
> >
> > * An attacker could create a VertexFiller with creation bounds that did not contain its glyphs but were entirely contained within the current clip, enabling to the glyphs to ignore the creation bounds clip and sample from stale scratch textures
> >
> > Bug: b/513948227
> > Change-Id: Ib4902657e6a50dd5675db4d73a1576b77c4ce88e
> > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1239916
> > Commit-Queue: Thomas Smith <thomsmit@google.com>
> > Reviewed-by: Michael Ludwig <michaelludwig@google.com>
>
> Bug: b/513948227
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Change-Id: Ide85466e17b870d2cc738bd25602d6cbf65d332b
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1249276
> Auto-Submit: Michael Ludwig <michaelludwig@google.com>
> Commit-Queue: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
> Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>

Bug: https://issues.chromium.org/issues/513948227
Change-Id: I7f5bff928f1b58830075c1ec6ca15600d49519c9
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1249536
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Thomas Smith <thomsmit@google.com>
---
 src/text/gpu/GlyphVector.h       |  12 ++
 src/text/gpu/SubRunContainer.cpp | 184 ++++++++++++++++---------
 src/text/gpu/SubRunContainer.h   |   8 +-
 src/text/gpu/VertexFiller.cpp    |  31 -----
 src/text/gpu/VertexFiller.h      |  11 +-
 tests/SkRemoteGlyphCacheTest.cpp | 222 ++++++++++++++++++++++++++++++-
 tools/text/gpu/TextBlobTools.cpp |  13 +-
 tools/text/gpu/TextBlobTools.h   |   2 +
 8 files changed, 378 insertions(+), 105 deletions(-)

--- a/src/text/gpu/GlyphVector.h
+++ b/src/text/gpu/GlyphVector.h
@@ -58,6 +58,7 @@
                             SubRunAllocator* alloc);
 
     SkSpan<const Glyph*> glyphs() const;
+	int glyphCount() const { return SkToInt(fGlyphs.size()); }
 
     static std::optional<GlyphVector> MakeFromBuffer(SkReadBuffer& buffer,
                                                      const SkStrikeClient* strikeClient,
@@ -68,6 +69,14 @@
     // the sub runs.
     int unflattenSize() const { return GlyphVectorSize(fGlyphs.size()); }
 
+    SkPackedGlyphID getPackedGlyphID(int index) const {
+        SkASSERT(index >= 0 && index < this->glyphCount());
+        return fGlyphs[index].packedGlyphID;
+    }
+
+    SkStrikePromise& strikePromise() { return fStrikePromise; }
+    const SkStrikePromise& strikePromise() const { return fStrikePromise; }
+
     void packedGlyphIDToGlyph(StrikeCache* cache, skgpu::MaskFormat);
 
     static size_t GlyphVectorSize(size_t count) {
--- a/src/text/gpu/SubRunContainer.cpp
+++ b/src/text/gpu/SubRunContainer.cpp
@@ -101,11 +101,89 @@
     return accepted.get<0>();
 }
 
-template <typename U>
-SkSpan<const SkPoint> get_positions(SkZip<U, const SkPoint> accepted) {
+template <typename U> SkSpan<const SkPoint> get_positions(SkZip<U, const SkPoint> accepted) {
     return accepted.template get<1>();
 }
 
+SkGlyphRect glyph_bounds(SkGlyphDigest digest, int inset, SkPoint origin) {
+    return digest.bounds().inset(inset, inset).offset(origin);
+}
+
+SkGlyphRect glyph_bounds_from_left_top(SkGlyphDigest digest, int inset, SkPoint leftTop) {
+    SkGlyphRect bounds = digest.bounds();
+    SkPoint offset = leftTop - bounds.leftTop() - SkPoint{SkIntToScalar(inset),
+                                                          SkIntToScalar(inset)};
+    return glyph_bounds(digest, inset, offset);
+}
+
+// Returns the empty span if there is a problem reading the positions.
+SkSpan<SkPoint> make_points_from_buffer(SkReadBuffer& buffer, SubRunAllocator* alloc) {
+    uint32_t glyphCount = buffer.getArrayCount();
+
+    // Zero indicates a problem with serialization.
+    if (!buffer.validate(glyphCount != 0)) { return {}; }
+
+    // Check that the count will not overflow the arena.
+    if (!buffer.validate(glyphCount <= INT_MAX &&
+                         BagOfBytes::WillCountFit<SkPoint>(glyphCount))) { return {}; }
+
+    SkPoint* positionsData = alloc->makePODArray<SkPoint>(glyphCount);
+    if (!buffer.readPointArray({positionsData, glyphCount})) { return {}; }
+    return {positionsData, glyphCount};
+}
+
+SkRect reconstruct_safe_bounds(SkSpan<const SkPoint> leftTop,
+                               GlyphVector* glyphVector,
+                               skglyph::ActionType actionType,
+                               int inset) {
+    int count = leftTop.size();
+
+    StrikeForGPU* strike = glyphVector->strikePromise().strike();
+    StrikeMutationMonitor m{strike};
+
+    SkGlyphRect boundingRect = skglyph::empty_rect();
+    for (int i = 0; i < count; ++i) {
+        SkPackedGlyphID packedID = glyphVector->getPackedGlyphID(i);
+        SkGlyphDigest digest = strike->digestFor(actionType, packedID);
+        boundingRect = skglyph::rect_union(
+                boundingRect, glyph_bounds_from_left_top(digest, inset, leftTop[i]));
+    }
+    return boundingRect.empty() ? SkRect::MakeEmpty() : boundingRect.rect();
+}
+
+void flatten_vertex_filler(SkWriteBuffer &buffer, const VertexFiller& vf) {
+    buffer.writeInt(static_cast<int>(vf.maskFormat()));
+    buffer.writeBool(vf.canDrawDirect());
+    buffer.writeMatrix(vf.creationMatrix());
+    buffer.writePointArray(vf.topLefts());
+}
+
+std::optional<VertexFiller> make_vertex_filler_from_buffer(SkReadBuffer& buffer,
+                                                           SubRunAllocator* alloc,
+                                                           GlyphVector* glyphVector,
+                                                           skglyph::ActionType actionType,
+                                                           int inset) {
+    MaskFormat maskFormat = buffer.read32LE(MaskFormat::kLast);
+
+    const bool canDrawDirect = buffer.readBool();
+
+    SkMatrix creationMatrix;
+    buffer.readMatrix(&creationMatrix);
+
+    SkSpan<SkPoint> leftTop = make_points_from_buffer(buffer, alloc);
+    if (leftTop.empty()) {
+        return std::nullopt;
+    }
+
+    if (!buffer.validate(glyphVector->glyphCount() == (int)leftTop.size())) {
+        return std::nullopt;
+    }
+
+    SkRect safeBounds = reconstruct_safe_bounds(leftTop, glyphVector, actionType, inset);
+
+    return VertexFiller{maskFormat, creationMatrix, safeBounds, leftTop, canDrawDirect};
+}
+
 // -- PathOpSubmitter ------------------------------------------------------------------------------
 // PathOpSubmitter holds glyph ids until ready to draw. During drawing, the glyph ids are
 // converted to SkPaths. PathOpSubmitter can only be serialized when it is holding glyph ids;
@@ -196,7 +274,7 @@
     SkScalar strikeToSourceScale = buffer.readScalar();
     if (!buffer.validate(0 < strikeToSourceScale)) { return std::nullopt; }
 
-    SkSpan<SkPoint> positions = MakePointsFromBuffer(buffer, alloc);
+    SkSpan<SkPoint> positions = make_points_from_buffer(buffer, alloc);
     if (positions.empty()) { return std::nullopt; }
     const int glyphCount = SkCount(positions);
 
@@ -453,7 +531,7 @@
     SkScalar strikeToSourceScale = buffer.readScalar();
     if (!buffer.validate(0 < strikeToSourceScale)) { return std::nullopt; }
 
-    SkSpan<SkPoint> positions = MakePointsFromBuffer(buffer, alloc);
+    SkSpan<SkPoint> positions = make_points_from_buffer(buffer, alloc);
     if (positions.empty()) { return std::nullopt; }
     const int glyphCount = SkCount(positions);
 
@@ -594,6 +672,8 @@
 // -- DirectMaskSubRun -----------------------------------------------------------------------------
 class DirectMaskSubRun final : public AtlasSubRun {
 public:
+    static constexpr int kGlyphInsetting = 0;
+
     DirectMaskSubRun(VertexFiller&& vertexFiller,
                      GlyphVector&& glyphs)
             : AtlasSubRun(std::move(vertexFiller), std::move(glyphs)) {}
@@ -620,14 +700,11 @@
     static SubRunOwner MakeFromBuffer(SkReadBuffer& buffer,
                                       SubRunAllocator* alloc,
                                       const SkStrikeClient* client) {
-        auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
-        if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
-
         auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
         if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
-        if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
-            return nullptr;
-        }
+        auto vertexFiller = make_vertex_filler_from_buffer(buffer, alloc, &glyphVector.value(),
+                                                           skglyph::kDirectMask, kGlyphInsetting);
+        if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
 
         SkASSERT(buffer.isValid());
         return alloc->makeUnique<DirectMaskSubRun>(
@@ -649,7 +726,7 @@
                fVertexFiller.unflattenSize();
     }
 
-    int glyphSrcPadding() const override { return 0; }
+    int glyphSrcPadding() const override { return kGlyphInsetting; }
 
     void testingOnly_packedGlyphIDToGlyph(StrikeCache* cache,
                                           skgpu::MaskFormat maskFormat) const override {
@@ -689,14 +766,16 @@
     }
 
     void doFlatten(SkWriteBuffer& buffer) const override {
-        fVertexFiller.flatten(buffer);
         fGlyphs.flatten(buffer);
+		flatten_vertex_filler(buffer, fVertexFiller);
     }
 };
 
 // -- TransformedMaskSubRun ------------------------------------------------------------------------
 class TransformedMaskSubRun final : public AtlasSubRun {
 public:
+    static constexpr int kGlyphInsetting = 0;
+
     TransformedMaskSubRun(bool isBigEnough,
                           VertexFiller&& vertexFiller,
                           GlyphVector&& glyphs)
@@ -721,7 +800,7 @@
                 std::move(strikePromise), get_packedIDs(accepted), alloc);
 
         return alloc->makeUnique<TransformedMaskSubRun>(
-                initialPositionMatrix.getMaxScale() >= 1,
+                AtlasSubRun::IsBigEnough(initialPositionMatrix),
                 std::move(vertexFiller),
                 std::move(glyphVector));
     }
@@ -729,17 +808,16 @@
     static SubRunOwner MakeFromBuffer(SkReadBuffer& buffer,
                                       SubRunAllocator* alloc,
                                       const SkStrikeClient* client) {
-        auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
-        if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
-
         auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
         if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
-        if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
-            return nullptr;
-        }
+        auto vertexFiller = make_vertex_filler_from_buffer(buffer, alloc, &glyphVector.value(),
+                                                           skglyph::kMask, kGlyphInsetting);
+        if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
+
         const bool isBigEnough = buffer.readBool();
         return alloc->makeUnique<TransformedMaskSubRun>(
-                isBigEnough, std::move(*vertexFiller), std::move(*glyphVector));
+                AtlasSubRun::IsBigEnough(vertexFiller->creationMatrix()),
+                std::move(*vertexFiller), std::move(*glyphVector));
     }
 
     int unflattenSize() const override {
@@ -761,7 +839,7 @@
         fGlyphs.packedGlyphIDToGlyph(cache, maskFormat);
     }
 
-    int glyphSrcPadding() const override { return 1; }
+    int glyphSrcPadding() const override { return 1; } // Padding NOT equal to insetting
 
     void draw(SkCanvas*,
               SkPoint drawOrigin,
@@ -795,9 +873,8 @@
     }
 
     void doFlatten(SkWriteBuffer& buffer) const override {
-        fVertexFiller.flatten(buffer);
         fGlyphs.flatten(buffer);
-        buffer.writeBool(fIsBigEnough);
+		flatten_vertex_filler(buffer, fVertexFiller);
     }
 
 private:
@@ -816,6 +893,8 @@
 
 class SDFTSubRun final : public AtlasSubRun {
 public:
+    static constexpr int kGlyphInsetting = SK_DistanceFieldInset;
+
     SDFTSubRun(bool useLCDText,
                bool antiAliased,
                const SDFTMatrixRange& matrixRange,
@@ -868,16 +947,13 @@
         int useLCD = buffer.readInt();
         int isAntiAliased = buffer.readInt();
         SDFTMatrixRange matrixRange = SDFTMatrixRange::MakeFromBuffer(buffer);
-        auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
-        if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
-        if (!buffer.validate(vertexFiller.value().grMaskType() == MaskFormat::kA8)) {
-            return nullptr;
-        }
+
         auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
         if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
-        if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
-            return nullptr;
-        }
+        auto vertexFiller = make_vertex_filler_from_buffer(buffer, alloc, &glyphVector.value(),
+                                                           skglyph::kSDFT, kGlyphInsetting);
+        if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
+
         return alloc->makeUnique<SDFTSubRun>(useLCD,
                                              isAntiAliased,
                                              matrixRange,
@@ -900,7 +976,7 @@
         fGlyphs.packedGlyphIDToGlyph(cache, maskFormat);
     }
 
-    int glyphSrcPadding() const override { return SK_DistanceFieldInset; }
+    int glyphSrcPadding() const override { return kGlyphInsetting; }
 
     void draw(SkCanvas*,
               SkPoint drawOrigin,
@@ -932,8 +1008,8 @@
         buffer.writeInt(fUseLCDText);
         buffer.writeInt(fAntiAliased);
         fMatrixRange.flatten(buffer);
-        fVertexFiller.flatten(buffer);
         fGlyphs.flatten(buffer);
+		flatten_vertex_filler(buffer, fVertexFiller);
     }
 
 private:
@@ -975,6 +1051,7 @@
 
 namespace sktext::gpu {
 SubRun::~SubRun() = default;
+
 void SubRun::flatten(SkWriteBuffer& buffer) const {
     buffer.writeInt(this->subRunStreamTag());
     this->doFlatten(buffer);
@@ -1106,11 +1183,7 @@
             case GlyphAction::kAccept: {
                 SkPoint mappedPos = creationMatrix.mapPoint(pos);
                 const SkGlyphRect glyphBounds =
-                    digest.bounds()
-                        // The SDFT glyphs have 2-pixel wide padding that should
-                        // not be used in calculating the source rectangle.
-                        .inset(SK_DistanceFieldInset, SK_DistanceFieldInset)
-                        .offset(mappedPos);
+                        glyph_bounds(digest, SDFTSubRun::kGlyphInsetting, mappedPos);
                 boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
                 acceptedBuffer[acceptedSize++] = std::make_tuple(packedID, glyphBounds.leftTop());
                 break;
@@ -1161,7 +1234,9 @@
             case GlyphAction::kAccept: {
                 const SkPoint roundedPos{SkScalarFloorToScalar(mappedPos.x()),
                                          SkScalarFloorToScalar(mappedPos.y())};
-                const SkGlyphRect glyphBounds = digest.bounds().offset(roundedPos);
+                const SkGlyphRect glyphBounds = glyph_bounds(digest,
+                                                             DirectMaskSubRun::kGlyphInsetting,
+                                                             roundedPos);
                 boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
                 acceptedBuffer[acceptedSize++] =
                         std::make_tuple(packedID, glyphBounds.leftTop(), digest.maskFormat());
@@ -1202,7 +1277,8 @@
                 digest.actionFor(kMask)) {
             case GlyphAction::kAccept: {
                 const SkPoint mappedPos = creationMatrix.mapPoint(pos);
-                const SkGlyphRect glyphBounds = digest.bounds().offset(mappedPos);
+                const SkGlyphRect glyphBounds =
+                        glyph_bounds(digest, TransformedMaskSubRun::kGlyphInsetting, mappedPos);
                 boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
                 acceptedBuffer[acceptedSize++] =
                         std::make_tuple(packedID, glyphBounds.leftTop(), digest.maskFormat());
@@ -1250,7 +1326,7 @@
 }
 
 std::tuple<SkZip<const SkGlyphID, const SkPoint>, SkZip<SkGlyphID, SkPoint>>
- prepare_for_drawable_drawing(StrikeForGPU* strike,
+prepare_for_drawable_drawing(StrikeForGPU* strike,
                              SkZip<const SkGlyphID, const SkPoint> source,
                              SkZip<SkGlyphID, SkPoint> acceptedBuffer,
                              SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
@@ -1637,20 +1713,4 @@
     return true;
 }
 
-// Returns the empty span if there is a problem reading the positions.
-SkSpan<SkPoint> MakePointsFromBuffer(SkReadBuffer& buffer, SubRunAllocator* alloc) {
-    uint32_t glyphCount = buffer.getArrayCount();
-
-    // Zero indicates a problem with serialization.
-    if (!buffer.validate(glyphCount != 0)) { return {}; }
-
-    // Check that the count will not overflow the arena.
-    if (!buffer.validate(glyphCount <= INT_MAX &&
-                         BagOfBytes::WillCountFit<SkPoint>(glyphCount))) { return {}; }
-
-    SkPoint* positionsData = alloc->makePODArray<SkPoint>(glyphCount);
-    if (!buffer.readPointArray({positionsData, glyphCount})) { return {}; }
-    return {positionsData, glyphCount};
-}
-
 }  // namespace sktext::gpu
--- a/src/text/gpu/SubRunContainer.h
+++ b/src/text/gpu/SubRunContainer.h
@@ -101,6 +101,7 @@
 
 private:
     friend class SubRunList;
+    friend class TextBlobTools;
     SubRunOwner fNext;
 };
 
@@ -125,6 +126,10 @@
             , fGlyphs{std::move(glyphs)} {}
     ~AtlasSubRun() override = default;
 
+    static bool IsBigEnough(const SkMatrix& matrix) {
+        return matrix.getMaxScale() >= 1.f;
+    }
+
     SkSpan<const Glyph*> glyphs() const { return fGlyphs.glyphs(); }
     int glyphCount() const { return SkCount(fGlyphs.glyphs()); }
     skgpu::MaskFormat maskFormat() const { return fVertexFiller.grMaskType(); }
@@ -264,9 +269,6 @@
     SubRunList fSubRuns;
 };
 
-// Returns the empty span if there is a problem reading the positions.
-SkSpan<SkPoint> MakePointsFromBuffer(SkReadBuffer&, SubRunAllocator*);
-
 }  // namespace sktext::gpu
 
 #endif  // sktext_gpu_SubRunContainer_DEFINED
--- a/src/text/gpu/VertexFiller.cpp
+++ b/src/text/gpu/VertexFiller.cpp
@@ -44,37 +44,6 @@
             maskType, creationMatrix, creationBounds, leftTop, fillerType == kIsDirect};
 }
 
-std::optional<VertexFiller> VertexFiller::MakeFromBuffer(SkReadBuffer &buffer,
-                                                         SubRunAllocator *alloc) {
-    int checkingMaskType = buffer.readInt();
-    if (!buffer.validate(
-            0 <= checkingMaskType && checkingMaskType < skgpu::kMaskFormatCount)) {
-        return std::nullopt;
-    }
-    MaskFormat maskType = (MaskFormat) checkingMaskType;
-
-    const bool canDrawDirect = buffer.readBool();
-
-    SkMatrix creationMatrix;
-    buffer.readMatrix(&creationMatrix);
-
-    SkRect creationBounds = buffer.readRect();
-
-    SkSpan<SkPoint> leftTop = MakePointsFromBuffer(buffer, alloc);
-    if (leftTop.empty()) { return std::nullopt; }
-
-    SkASSERT(buffer.isValid());
-    return VertexFiller{maskType, creationMatrix, creationBounds, leftTop, canDrawDirect};
-}
-
-void VertexFiller::flatten(SkWriteBuffer &buffer) const {
-    buffer.writeInt(static_cast<int>(fMaskType));
-    buffer.writeBool(fCanDrawDirect);
-    buffer.writeMatrix(fCreationMatrix);
-    buffer.writeRect(fCreationBounds);
-    buffer.writePointArray(fLeftTop);
-}
-
 SkMatrix VertexFiller::viewDifference(const SkMatrix &positionMatrix) const {
     if (SkMatrix inverse; fCreationMatrix.invert(&inverse)) {
         return SkMatrix::Concat(positionMatrix, inverse);
--- a/src/text/gpu/VertexFiller.h
+++ b/src/text/gpu/VertexFiller.h
@@ -37,6 +37,7 @@
 namespace sktext::gpu {
 class Glyph;
 class SubRunAllocator;
+class AtlasSubRun;
 
 enum FillerType {
     kIsDirect,
@@ -68,13 +69,8 @@
                              SubRunAllocator *alloc,
                              FillerType fillerType);
 
-    static std::optional<VertexFiller> MakeFromBuffer(SkReadBuffer &buffer,
-                                                      SubRunAllocator *alloc);
-
     int unflattenSize() const { return fLeftTop.size_bytes(); }
 
-    void flatten(SkWriteBuffer &buffer) const;
-
     // These are only available if the Ganesh backend is compiled in (see GaneshVertexFiller.cpp)
     size_t vertexStride(const SkMatrix &matrix) const;
     void fillVertexData(int offset, int count,
@@ -105,7 +101,14 @@
 
     int count() const { return SkCount(fLeftTop); }
 
+	skgpu::MaskFormat maskFormat() const { return fMaskType; }
+	bool canDrawDirect() const { return fCanDrawDirect; }
+    const SkMatrix& creationMatrix() const { return fCreationMatrix; }
+	SkSpan<const SkPoint> topLefts() const { return fLeftTop; }
+
 private:
+    friend class AtlasSubRun;
+
     static std::tuple<bool, SkVector> CanUseDirect(const SkMatrix& creationMatrix,
                                                    const SkMatrix& positionMatrix);
 
--- a/tools/text/gpu/TextBlobTools.cpp
+++ b/tools/text/gpu/TextBlobTools.cpp
@@ -12,12 +12,17 @@
 
 namespace sktext::gpu {
 
-const AtlasSubRun* TextBlobTools::FirstSubRun(const TextBlob* blob) {
-    SkASSERT(blob);
-    if (blob->fSubRuns->fSubRuns.isEmpty()) {
+const AtlasSubRun* TextBlobTools::FirstSubRun(const SubRunContainer* container) {
+    SkASSERT(container);
+    if (container->fSubRuns.isEmpty()) {
         return nullptr;
     }
-    return blob->fSubRuns->fSubRuns.front().testingOnly_atlasSubRun();
+    return container->fSubRuns.front().testingOnly_atlasSubRun();
+}
+
+const AtlasSubRun* TextBlobTools::FirstSubRun(const TextBlob* blob) {
+    SkASSERT(blob);
+    return FirstSubRun(blob->fSubRuns.get());
 }
 
 }  // namespace sktext::gpu
--- a/tools/text/gpu/TextBlobTools.h
+++ b/tools/text/gpu/TextBlobTools.h
@@ -11,10 +11,12 @@
 namespace sktext::gpu {
 class AtlasSubRun;
 class TextBlob;
+class SubRunContainer;
 
 class TextBlobTools final {
 public:
     static const AtlasSubRun* FirstSubRun(const TextBlob*);
+    static const AtlasSubRun* FirstSubRun(const SubRunContainer*);
 
 private:
     TextBlobTools();
