Add SkGlyphRunList - v2

Extend the glyph run system with a glyph run list. This
allows the processing of text blobs.

Add original text an cluster to runs for PDF.

PS - the original had read off the end of a buffer problem.

Change-Id: I9430f0c27aaa3d9458bfe3caba5f433b72fdf84c
Reviewed-on: https://skia-review.googlesource.com/136792
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Herb Derby <herb@google.com>
diff --git a/src/core/SkGlyphRun.cpp b/src/core/SkGlyphRun.cpp
index 29c1dfd..6190965 100644
--- a/src/core/SkGlyphRun.cpp
+++ b/src/core/SkGlyphRun.cpp
@@ -19,9 +19,12 @@
 #include "SkPaint.h"
 #include "SkPaintPriv.h"
 #include "SkStrikeCache.h"
+#include "SkTextBlob.h"
+#include "SkTextBlobRunIterator.h"
 #include "SkTo.h"
 #include "SkUtils.h"
 
+namespace {
 static SkTypeface::Encoding convert_encoding(SkPaint::TextEncoding encoding) {
     switch (encoding) {
         case  SkPaint::kUTF8_TextEncoding: return SkTypeface::kUTF8_Encoding;
@@ -30,8 +33,15 @@
         default: return SkTypeface::kUTF32_Encoding;
     }
 }
+}  // namespace
+
 
 // -- SkGlyphSet ----------------------------------------------------------------------------------
+uint32_t SkGlyphSet::uniqueSize() {
+    // The size is how big the vector is grown since being passed into reuse.
+    return fUniqueGlyphIDs->size() - fStartOfUniqueIDs;
+}
+
 uint16_t SkGlyphSet::add(SkGlyphID glyphID) {
     static constexpr SkGlyphID  kUndefGlyph{0};
 
@@ -44,11 +54,13 @@
     }
 
     auto index = fIndices[glyphID];
-    if (index < fUniqueGlyphIDs->size() && (*fUniqueGlyphIDs)[index] == glyphID) {
+
+    // Remember we start at the end of what ever was passed in.
+    if (index < this->uniqueSize() && (*fUniqueGlyphIDs)[fStartOfUniqueIDs + index] == glyphID) {
         return index;
     }
 
-    uint16_t newIndex = SkTo<uint16_t>(fUniqueGlyphIDs->size());
+    uint16_t newIndex = SkTo<uint16_t>(this->uniqueSize());
     fUniqueGlyphIDs->push_back(glyphID);
     fIndices[glyphID] = newIndex;
     return newIndex;
@@ -58,15 +70,12 @@
     SkASSERT(glyphUniverseSize <= (1 << 16));
     fUniverseSize = glyphUniverseSize;
     fUniqueGlyphIDs = uniqueGlyphIDs;
-    // If we're hanging onto these arrays for a long time, we don't want their size to drift
-    // endlessly upwards. It's unusual to see more than 256 unique glyphs used in a run,
-    // or a typeface with more than 4096 possible glyphs.
-    if (fUniqueGlyphIDs->size() > 256) {
-        fUniqueGlyphIDs->resize(256);
-        fUniqueGlyphIDs->shrink_to_fit();
-    }
-    fUniqueGlyphIDs->clear();
 
+    // Capture the vector end to act as the start of a new unique id vector.
+    fStartOfUniqueIDs = uniqueGlyphIDs->size();
+
+    // If we're hanging onto these arrays for a long time, we don't want their size to drift
+    // endlessly upwards. It's unusual to see a typeface with more than 4096 possible glyphs.
     if (glyphUniverseSize < 4096 && fIndices.size() > 4096) {
         fIndices.resize(4096);
         fIndices.shrink_to_fit();
@@ -77,6 +86,21 @@
 }
 
 // -- SkGlyphRun -----------------------------------------------------------------------------------
+SkGlyphRun::SkGlyphRun(SkSpan<uint16_t>   denseIndex,
+                       SkSpan<SkPoint>    positions,
+                       SkSpan<SkGlyphID>  scratchGlyphs,
+                       SkSpan<SkGlyphID>  uniqueGlyphIDs,
+                       SkSpan<const char> text,
+                       SkSpan<uint32_t>   clusters)
+        : fDenseIndex{denseIndex}, fPositions{positions}
+        , fTemporaryShuntGlyphIDs{scratchGlyphs}
+        , fUniqueGlyphIDs{uniqueGlyphIDs}
+        , fText{text}
+        , fClusters{clusters} {
+    SkASSERT(denseIndex.size() == positions.size());
+    SkASSERT(denseIndex.size() == scratchGlyphs.size());
+}
+
 
 void SkGlyphRun::temporaryShuntToDrawPosText(const SkPaint& paint, SkBaseDevice* device) {
 
@@ -93,12 +117,162 @@
     callback(this->runSize(), bytes, pos);
 }
 
+// -- SkGlyphRunList -------------------------------------------------------------------------------
+SkGlyphRunList::SkGlyphRunList(SkSpan<SkGlyphRun> glyphRuns, uint64_t uniqueID)
+        : fUniqueID{uniqueID}
+        , fGlyphRuns{glyphRuns} { }
 
 // -- SkGlyphRunBuilder ----------------------------------------------------------------------------
 void SkGlyphRunBuilder::prepareDrawText(
         const SkPaint& paint, const void* bytes, size_t byteLength, SkPoint origin) {
+    this->initialize();
+    SkSpan<const char> originalText((const char*)bytes, byteLength);
+    if (paint.getTextEncoding() != SkPaint::kUTF8_TextEncoding) {
+        originalText = SkSpan<const char>();
+    }
+    this->drawText(paint, bytes, byteLength, origin, originalText, SkSpan<uint32_t>());
+}
 
-    this->initializeDenseAndUnique(paint, bytes, byteLength);
+void SkGlyphRunBuilder::prepareDrawPosTextH(const SkPaint& paint, const void* bytes,
+                                            size_t byteLength, const SkScalar* xpos,
+                                            SkScalar constY) {
+    this->initialize();
+    this->drawPosTextH(
+            paint, bytes, byteLength, xpos, constY, SkSpan<const char>(), SkSpan<uint32_t>());
+}
+
+void SkGlyphRunBuilder::prepareDrawPosText(const SkPaint& paint, const void* bytes,
+                                           size_t byteLength, const SkPoint* pos) {
+    this->initialize();
+    this->drawPosText(paint, bytes, byteLength, pos, SkSpan<const char>(), SkSpan<uint32_t>());
+}
+
+void SkGlyphRunBuilder::prepareTextBlob(
+        const SkPaint& paint, const SkTextBlob& blob, SkPoint origin) {
+    this->initialize();
+    fUniqueID = blob.uniqueID();
+
+    SkPaint runPaint = paint;
+
+    for (SkTextBlobRunIterator it(&blob); !it.done(); it.next()) {
+        // applyFontToPaint() always overwrites the exact same attributes,
+        // so it is safe to not re-seed the paint for this reason.
+        it.applyFontToPaint(&runPaint);
+
+        // These better be glyphs
+        SkASSERT(runPaint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
+
+        auto text = SkSpan<const char>(it.text(), it.textSize());
+        auto clusters = SkSpan<uint32_t>(it.clusters(), it.glyphCount());
+        size_t glyphLen = it.glyphCount() * sizeof(SkGlyphID);
+        const SkPoint& offset = it.offset();
+
+        switch (it.positioning()) {
+            case SkTextBlob::kDefault_Positioning: {
+                auto dtOrigin = origin + offset;
+                this->drawText(runPaint, it.glyphs(), glyphLen, dtOrigin, text, clusters);
+            }
+            break;
+            case SkTextBlob::kHorizontal_Positioning: {
+                auto constY = origin.y() + offset.y();
+                this->drawPosTextH(
+                        runPaint, it.glyphs(), glyphLen, it.pos(), constY, text, clusters);
+            }
+            break;
+            case SkTextBlob::kFull_Positioning:
+                this->drawPosText(
+                        runPaint, it.glyphs(), glyphLen, (const SkPoint*)it.pos(), text, clusters);
+            break;
+            default:
+                SK_ABORT("unhandled positioning mode");
+        }
+    }
+}
+
+SkGlyphRun* SkGlyphRunBuilder::useGlyphRun() {
+    auto glyphRunList = this->useGlyphRunList();
+    SkASSERT(glyphRunList->size() == 1);
+    return &(*glyphRunList)[0];
+}
+
+SkGlyphRunList* SkGlyphRunBuilder::useGlyphRunList() {
+    new ((void*)&fScratchGlyphRunList) SkGlyphRunList{SkSpan<SkGlyphRun>(fGlyphRuns), fUniqueID};
+    return &fScratchGlyphRunList;
+}
+
+size_t SkGlyphRunBuilder::runSize() const { return fDenseIndex.size() - fLastDenseIndex; }
+
+size_t SkGlyphRunBuilder::uniqueSize() const { return fUniqueGlyphs.size() - fLastUniqueIndex; }
+
+void SkGlyphRunBuilder::initialize() {
+    fUniqueID = 0;
+    fDenseIndex.clear();
+    fPositions.clear();
+    fUniqueGlyphs.clear();
+    fGlyphRuns.clear();
+    fLastDenseIndex = 0;
+    fLastUniqueIndex = 0;
+}
+
+SkGlyphID* SkGlyphRunBuilder::addDenseAndUnique(
+        const SkPaint& paint, const void* bytes, size_t byteLength) {
+
+    size_t runSize = 0;
+    SkGlyphID* glyphIDs = nullptr;
+    auto encoding = paint.getTextEncoding();
+    auto typeface = SkPaintPriv::GetTypefaceOrDefault(paint);
+    if (encoding != SkPaint::kGlyphID_TextEncoding) {
+        auto tfEncoding = convert_encoding(encoding);
+        int utfSize = SkUTFN_CountUnichars(tfEncoding, bytes, byteLength);
+        if (utfSize > 0) {
+            runSize = SkTo<size_t>(utfSize);
+            fScratchGlyphIDs.resize(runSize);
+            typeface->charsToGlyphs(bytes, tfEncoding, fScratchGlyphIDs.data(), runSize);
+            glyphIDs = fScratchGlyphIDs.data();
+        }
+    } else {
+        runSize = byteLength / 2;
+        glyphIDs = (SkGlyphID*)bytes;
+    }
+
+    SkASSERT(glyphIDs != nullptr);
+
+    if (runSize > 0) {
+        fGlyphSet.reuse(typeface->countGlyphs(), &fUniqueGlyphs);
+        for (size_t i = 0; i < runSize; i++) {
+            fDenseIndex.push_back(fGlyphSet.add(glyphIDs[i]));
+        }
+    }
+
+    return glyphIDs;
+}
+
+void SkGlyphRunBuilder::addGlyphRunToList(
+        SkGlyphID* temporaryShuntGlyphIDs, SkSpan<const char> text, SkSpan<uint32_t> clusters) {
+
+    // Ignore empty runs.
+    if (fDenseIndex.size() != fLastDenseIndex) {
+        auto runSize = this->runSize();
+        auto uniqueSize = this->uniqueSize();
+
+        fGlyphRuns.emplace_back(
+                SkSpan<uint16_t>(&fDenseIndex[fLastDenseIndex], runSize),
+                SkSpan<SkPoint>(&fPositions[fLastDenseIndex], runSize),
+                SkSpan<SkGlyphID>(temporaryShuntGlyphIDs, runSize),
+                SkSpan<SkGlyphID>(&fUniqueGlyphs[fLastDenseIndex], uniqueSize),
+                text,
+                clusters);
+
+        fLastDenseIndex = fDenseIndex.size();
+        fLastUniqueIndex = fUniqueGlyphs.size();
+    }
+}
+
+void SkGlyphRunBuilder::drawText(
+        const SkPaint& paint, const void* bytes, size_t byteLength, SkPoint origin,
+        SkSpan<const char> text, SkSpan<uint32_t> clusters) {
+
+    SkGlyphID* temporaryShuntGlyphIDs = this->addDenseAndUnique(paint, bytes, byteLength);
 
     fScratchAdvances.resize(this->uniqueSize());
     {
@@ -118,78 +292,38 @@
         if (paint.getTextAlign() == SkPaint::kCenter_Align) {
             len.scale(SK_ScalarHalf);
         }
-        for (size_t i = 0; i < this->runSize(); i++) {
+        for (size_t i = fLastDenseIndex; i < this->runSize(); i++) {
             fPositions[i] -= len;
         }
     }
 
+    this->addGlyphRunToList(temporaryShuntGlyphIDs, text, clusters);
 }
 
-void SkGlyphRunBuilder::prepareDrawPosTextH(const SkPaint& paint, const void* bytes,
-                                            size_t byteLength, const SkScalar* xpos,
-                                            SkScalar constY) {
+void SkGlyphRunBuilder::drawPosTextH(const SkPaint& paint, const void* bytes,
+                                     size_t byteLength, const SkScalar* xpos,
+                                     SkScalar constY,
+                                     SkSpan<const char> text, SkSpan<uint32_t> clusters) {
 
-    this->initializeDenseAndUnique(paint, bytes, byteLength);
+    SkGlyphID* temporaryShuntGlyphIDs = this->addDenseAndUnique(paint, bytes, byteLength);
 
     for (size_t i = 0; i < runSize(); i++) {
         fPositions.push_back(SkPoint::Make(xpos[i], constY));
     }
+
+    this->addGlyphRunToList(temporaryShuntGlyphIDs, text, clusters);
 }
 
-void SkGlyphRunBuilder::prepareDrawPosText(const SkPaint& paint, const void* bytes,
-                                           size_t byteLength, const SkPoint* pos) {
-    this->initializeDenseAndUnique(paint, bytes, byteLength);
+void SkGlyphRunBuilder::drawPosText(const SkPaint& paint, const void* bytes,
+                                    size_t byteLength, const SkPoint* pos,
+                                    SkSpan<const char> text, SkSpan<uint32_t> clusters) {
+    SkGlyphID* temporaryShuntGlyphIDs = this->addDenseAndUnique(paint, bytes, byteLength);
 
     for (size_t i = 0; i < runSize(); i++) {
         fPositions.push_back(pos[i]);
     }
-}
 
-SkGlyphRun* SkGlyphRunBuilder::useGlyphRun() {
-    fScratchGlyphRun.~SkGlyphRun();
-    new ((void*)&fScratchGlyphRun) SkGlyphRun{SkSpan<uint16_t>(fDenseIndex),
-                                              SkSpan<SkPoint>(fPositions),
-                                              SkSpan<SkGlyphID>(
-                                                      fTemporaryShuntGlyphIDs, fDenseIndex.size()),
-                                              SkSpan<SkGlyphID>(fUniqueGlyphs)};
-    return &fScratchGlyphRun;
-}
-
-void SkGlyphRunBuilder::initializeDenseAndUnique(
-        const SkPaint& paint, const void* bytes, size_t byteLength) {
-
-    fDenseIndex.clear();
-    fPositions.clear();
-    fUniqueGlyphs.clear();
-    fTemporaryShuntGlyphIDs = nullptr;
-
-    size_t runSize = 0;
-    const SkGlyphID* glyphIDs = nullptr;
-    auto encoding = paint.getTextEncoding();
-    auto typeface = SkPaintPriv::GetTypefaceOrDefault(paint);
-    if (encoding != SkPaint::kGlyphID_TextEncoding) {
-        auto tfEncoding = convert_encoding(encoding);
-        int utfSize = SkUTFN_CountUnichars(tfEncoding, bytes, byteLength);
-        if (utfSize > 0) {
-            runSize = SkTo<size_t>(utfSize);
-            fScratchGlyphIDs.resize(runSize);
-            typeface->charsToGlyphs(bytes, tfEncoding, fScratchGlyphIDs.data(), runSize);
-            glyphIDs = fScratchGlyphIDs.data();
-        }
-    } else {
-        runSize = byteLength / 2;
-        glyphIDs = (const SkGlyphID*)bytes;
-    }
-
-    SkASSERT(glyphIDs != nullptr);
-
-    if (runSize == 0) { return; }
-    fTemporaryShuntGlyphIDs = glyphIDs;
-
-    fGlyphSet.reuse(typeface->countGlyphs(), &fUniqueGlyphs);
-    for (size_t i = 0; i < runSize; i++) {
-        fDenseIndex.push_back(fGlyphSet.add(glyphIDs[i]));
-    }
+    this->addGlyphRunToList(temporaryShuntGlyphIDs, text, clusters);
 }
 
 
diff --git a/src/core/SkGlyphRun.h b/src/core/SkGlyphRun.h
index 243d5d1..1b41da5 100644
--- a/src/core/SkGlyphRun.h
+++ b/src/core/SkGlyphRun.h
@@ -24,36 +24,32 @@
 class SkSpan {
 public:
     SkSpan() : fPtr{nullptr}, fSize{0} {}
-    SkSpan(const T* ptr, size_t size) : fPtr{ptr}, fSize{size} {}
-    explicit SkSpan(const std::vector<T>& v) : fPtr{v.data()}, fSize{v.size()} {}
-    const T& operator [] (ptrdiff_t i) const { return fPtr[i]; }
+    SkSpan(T* ptr, size_t size) : fPtr{ptr}, fSize{size} {}
+    explicit SkSpan(std::vector<T>& v) : fPtr{v.data()}, fSize{v.size()} {}
+    SkSpan& operator=( const SkSpan& other ) = default;
+    T& operator [] (ptrdiff_t i) const { return fPtr[i]; }
     T* begin() const { return fPtr; }
     T* end() const { return fPtr + fSize; }
     const T* cbegin() const { return fPtr; }
     const T* cend() const { return fPtr + fSize; }
-    const T* data() const { return fPtr; }
+    T* data() const { return fPtr; }
     ptrdiff_t size() const { return fSize; }
     bool empty() const { return fSize == 0; }
 
 private:
-    const T* fPtr;
+    T* fPtr;
     size_t fSize;
 };
 
 class SkGlyphRun {
 public:
     SkGlyphRun() = default;
-    SkGlyphRun(SkSpan<uint16_t>  denseIndex,
-               SkSpan<SkPoint>   positions,
-               SkSpan<SkGlyphID> scratchGlyphs,
-               SkSpan<SkGlyphID> uniqueGlyphIDs)
-            : fDenseIndex{denseIndex}
-            , fPositions{positions}
-            , fTemporaryShuntGlyphIDs{scratchGlyphs}
-            , fUniqueGlyphIDs{uniqueGlyphIDs} {
-        SkASSERT(denseIndex.size() == positions.size());
-        SkASSERT(denseIndex.size() == scratchGlyphs.size());
-    }
+    SkGlyphRun(SkSpan<uint16_t>   denseIndex,
+               SkSpan<SkPoint>    positions,
+               SkSpan<SkGlyphID>  scratchGlyphs,
+               SkSpan<SkGlyphID>  uniqueGlyphIDs,
+               SkSpan<const char> text,
+               SkSpan<uint32_t>   clusters);
 
     // The temporaryShunt calls are to allow inter-operating with existing code while glyph runs
     // are developed.
@@ -67,19 +63,43 @@
 
 private:
     // Indices into the unique glyph IDs. On for each original glyph.
-    const SkSpan<uint16_t>  fDenseIndex;
+    const SkSpan<uint16_t>   fDenseIndex;
     // The base line position of all the glyphs in source space.
-    const SkSpan<SkPoint>   fPositions;
+    const SkSpan<SkPoint>    fPositions;
     // This is temporary while converting from the old per glyph code to the bulk code.
-    const SkSpan<SkGlyphID> fTemporaryShuntGlyphIDs;
+    const SkSpan<SkGlyphID>  fTemporaryShuntGlyphIDs;
     // The set of unique glyphs in the run.
-    const SkSpan<SkGlyphID> fUniqueGlyphIDs;
+    const SkSpan<SkGlyphID>  fUniqueGlyphIDs;
+    // Original text from SkTextBlob if present. Will be empty of not present.
+    const SkSpan<const char> fText;
+    // Original clusters from SkTextBlob if present. Will be empty if not present.
+    const SkSpan<uint32_t>   fClusters;
+};
+
+class SkGlyphRunList {
+    const uint64_t     fUniqueID{0};
+    SkSpan<SkGlyphRun> fGlyphRuns;
+
+public:
+    SkGlyphRunList() = default;
+    SkGlyphRunList(SkSpan<SkGlyphRun> glyphRuns, uint64_t uniqueID);
+
+    uint64_t uniqueID() const { return fUniqueID; }
+
+    auto begin() -> decltype(fGlyphRuns.begin())               { return fGlyphRuns.begin(); }
+    auto end()   -> decltype(fGlyphRuns.end())                 { return fGlyphRuns.end();   }
+    auto size()  -> decltype(fGlyphRuns.size())                { return fGlyphRuns.size();  }
+    auto operator [] (ptrdiff_t i) -> decltype(fGlyphRuns[i])  { return fGlyphRuns[i];      }
 };
 
 // A faster set implementation that does not need any initialization, and reading the set items
 // is order the number of items, and not the size of the universe.
 // This implementation is based on the paper by Briggs and Torczon, "An Efficient Representation
 // for Sparse Sets"
+//
+// This implementation assumes that the unique glyphs added are appended to a vector that may
+// already have unique glyph from a previous computation. This allows the packing of multiple
+// UniqueID sequences in a single vector.
 class SkGlyphSet {
 public:
     SkGlyphSet() = default;
@@ -87,15 +107,13 @@
     void reuse(uint32_t glyphUniverseSize, std::vector<SkGlyphID>* uniqueGlyphIDs);
 
 private:
+    uint32_t uniqueSize();
     uint32_t                    fUniverseSize{0};
+    size_t                      fStartOfUniqueIDs{0};
     std::vector<uint16_t>       fIndices;
     std::vector<SkGlyphID>*     fUniqueGlyphIDs{nullptr};
 };
 
-// Currently the old code is passing around SkGlyphRunBuilder because it facilitates working in the
-// old single glyph lookup style with the cache. When the lower level code is transitioned over to
-// the bulk glyph cache style, then the builder will only be used in the canvas, and only runs will
-// be passed around.
 class SkGlyphRunBuilder {
 public:
     SkGlyphRunBuilder() = default;
@@ -106,32 +124,50 @@
             const SkScalar xpos[], SkScalar constY);
     void prepareDrawPosText(
             const SkPaint& paint, const void* bytes, size_t byteLength, const SkPoint pos[]);
+    void prepareTextBlob(const SkPaint& paint, const SkTextBlob& blob, SkPoint origin);
 
-    size_t runSize() const {return fDenseIndex.size();}
-    size_t uniqueSize() const {return fUniqueGlyphs.size();}
-
+    SkGlyphRunList* useGlyphRunList();
     SkGlyphRun* useGlyphRun();
 
 private:
-    void initializeDenseAndUnique(const SkPaint& paint, const void* bytes, size_t byteLength);
+    size_t runSize() const;
+    size_t uniqueSize() const;
+    void initialize();
+    SkGlyphID* addDenseAndUnique(const SkPaint& paint, const void* bytes, size_t byteLength);
+    void addGlyphRunToList(
+            SkGlyphID* temporaryShuntGlyphIDs, SkSpan<const char> text, SkSpan<uint32_t> clusters);
+
+    void drawText(
+            const SkPaint& paint, const void* bytes, size_t byteLength, SkPoint origin,
+            SkSpan<const char> text, SkSpan<uint32_t> clusters);
+    void drawPosTextH(
+            const SkPaint& paint, const void* bytes, size_t byteLength,
+            const SkScalar* xpos, SkScalar constY,
+            SkSpan<const char> text, SkSpan<uint32_t> clusters);
+    void drawPosText(
+            const SkPaint& paint, const void* bytes, size_t byteLength, const SkPoint* pos,
+            SkSpan<const char> text, SkSpan<uint32_t> clusters);
+
+    uint64_t               fUniqueID{0};
 
     std::vector<uint16_t>  fDenseIndex;
     std::vector<SkPoint>   fPositions;
     std::vector<SkGlyphID> fUniqueGlyphs;
 
+    size_t                 fLastDenseIndex{0};
+    size_t                 fLastUniqueIndex{0};
+
     // Used as a temporary for preparing using utfN text.
     std::vector<SkGlyphID> fScratchGlyphIDs;
 
     // Used as temporary storage for calculating positions for drawText.
     std::vector<SkPoint>   fScratchAdvances;
 
-    // Used to temporarily use of a glyph run for bulk cache API calls (just an experiment at
-    // this point).
-    SkGlyphRun             fScratchGlyphRun;
+    // Vector for accumulating runs. This is later deposited in fScratchGlyphRunList;
+    std::vector<SkGlyphRun> fGlyphRuns;
 
-    // Used as an aid to shunt from glyph runs to drawPosText. It will either be fScratchIDs or
-    // the bytes passed in.
-    const SkGlyphID*       fTemporaryShuntGlyphIDs{nullptr};
+    // Used as temporary glyph run for the rest of the Text stack.
+    SkGlyphRunList         fScratchGlyphRunList;
 
     // Used for collecting the set of unique glyphs.
     SkGlyphSet             fGlyphSet;
diff --git a/tests/GlyphRunTest.cpp b/tests/GlyphRunTest.cpp
index 3b25625..ca97404 100644
--- a/tests/GlyphRunTest.cpp
+++ b/tests/GlyphRunTest.cpp
@@ -7,9 +7,32 @@
 
 #include "SkGlyphRun.h"
 
+#include "SkTextBlob.h"
+
 #include "Test.h"
 
-DEF_TEST(GlyphRunInfo, reporter) {
+DEF_TEST(GlyphSetBasic, reporter) {
+    SkGlyphSet set;
+
+    std::vector<SkGlyphID> unique;
+
+    set.reuse(10, &unique);
+    REPORTER_ASSERT(reporter, set.add(7) == 0);
+    REPORTER_ASSERT(reporter, set.add(3) == 1);
+    set.reuse(10, &unique);
+    REPORTER_ASSERT(reporter, set.add(5) == 0);
+    REPORTER_ASSERT(reporter, set.add(8) == 1);
+    REPORTER_ASSERT(reporter, set.add(3) == 2);
+
+    REPORTER_ASSERT(reporter, unique.size() == 5);
+    REPORTER_ASSERT(reporter, unique[0] == 7);
+    REPORTER_ASSERT(reporter, unique[1] == 3);
+    REPORTER_ASSERT(reporter, unique[2] == 5);
+    REPORTER_ASSERT(reporter, unique[3] == 8);
+    REPORTER_ASSERT(reporter, unique[4] == 3);
+}
+
+DEF_TEST(GlyphRunBasic, reporter) {
     SkGlyphID glyphs[] = {100, 3, 240, 3, 234, 111, 3, 4, 10, 11};
     uint16_t count = SK_ARRAY_COUNT(glyphs);
 
@@ -18,5 +41,46 @@
 
     SkGlyphRunBuilder builder;
     builder.prepareDrawText(paint, glyphs, count, SkPoint::Make(0, 0));
+}
 
+DEF_TEST(GlyphRunBlob, reporter) {
+    constexpr uint16_t count = 10;
+
+    auto tf = SkTypeface::MakeFromName("monospace", SkFontStyle());
+
+    SkPaint font;
+    font.setTypeface(tf);
+    font.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+    font.setTextAlign(SkPaint::kLeft_Align);
+    font.setStyle(SkPaint::kFill_Style);
+    font.setHinting(SkPaint::kNormal_Hinting);
+    font.setTextSize(1u);
+
+    SkTextBlobBuilder blobBuilder;
+    for (int runNum = 0; runNum < 2; runNum++) {
+        const auto& runBuffer = blobBuilder.allocRunPosH(font, count, runNum);
+        SkASSERT(runBuffer.utf8text == nullptr);
+        SkASSERT(runBuffer.clusters == nullptr);
+
+        for (int i = 0; i < count; i++) {
+            runBuffer.glyphs[i] = static_cast<SkGlyphID>(i + runNum * 10);
+            runBuffer.pos[i] = SkIntToScalar(i + runNum * 10);
+        }
+    }
+
+    auto blob = blobBuilder.make();
+
+    SkPaint paint;
+    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+    SkGlyphRunBuilder runBuilder;
+    runBuilder.prepareTextBlob(font, *blob, SkPoint::Make(0, 0));
+
+    auto runList = runBuilder.useGlyphRunList();
+
+    REPORTER_ASSERT(reporter, runList->size() == 2);
+    for (auto& run : *runList) {
+        REPORTER_ASSERT(reporter, run.runSize() == 10);
+        REPORTER_ASSERT(reporter, run.uniqueSize() == 10);
+    }
 }
\ No newline at end of file