Reland "Introduce SkGlyphSourceBuffer"

This is a reland of 77c53087c1ea92bc6c7e436f60746da7605cad98

Original change's description:
> Introduce SkGlyphSourceBuffer
>
> SkGlyphSourceBuffer provides a system for taking rejected glyphs from
> one glyph drawing stage, and turns them into the source for the next stage.
> It is rarely used, so it tries to conserve memory.
>
> Change-Id: I5275ffa3e804fc494eb2f5803e0cf2d148a755f7
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/248260
> Reviewed-by: Ben Wagner <bungeman@google.com>
> Commit-Queue: Herb Derby <herb@google.com>

Change-Id: Ib90508a21993b12c71ee86cbdeb51c4d4c2ec913
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/249128
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Herb Derby <herb@google.com>
diff --git a/src/core/SkGlyphBuffer.cpp b/src/core/SkGlyphBuffer.cpp
index a3ef7fb..e7ccf57 100644
--- a/src/core/SkGlyphBuffer.cpp
+++ b/src/core/SkGlyphBuffer.cpp
@@ -9,6 +9,11 @@
 #include "src/core/SkGlyphRunPainter.h"
 #include "src/core/SkStrikeForGPU.h"
 
+void SkSourceGlyphBuffer::reset() {
+    fRejectedGlyphIDs.reset();
+    fRejectedPositions.reset();
+}
+
 void SkDrawableGlyphBuffer::ensureSize(size_t size) {
     if (size > fMaxSize) {
         fMultiBuffer.reset(size);
diff --git a/src/core/SkGlyphBuffer.h b/src/core/SkGlyphBuffer.h
index ca7541a..abfd617 100644
--- a/src/core/SkGlyphBuffer.h
+++ b/src/core/SkGlyphBuffer.h
@@ -14,6 +14,71 @@
 class SkStrikeForGPU;
 struct SkGlyphPositionRoundingSpec;
 
+// SkSourceGlyphBuffer is the source of glyphs between the different stages of character drawing.
+// It starts with the glyphs and positions from the SkGlyphRun as the first source. When glyphs
+// are reject by a stage they become the source for the next stage.
+class SkSourceGlyphBuffer {
+public:
+    SkSourceGlyphBuffer() = default;
+
+    void setSource(SkZip<const SkGlyphID, const SkPoint> source) {
+        this->~SkSourceGlyphBuffer();
+        new (this) SkSourceGlyphBuffer{source};
+    }
+
+    void reset();
+
+    void reject(size_t index) {
+        SkASSERT(index < fSource.size());
+        if (!this->sourceIsRejectBuffers()) {
+            // Need to expand the buffers for first use. All other reject sets will be fewer than
+            // this one.
+            SkGlyphID glyphID; SkPoint pos;
+            std::tie(glyphID, pos) = fSource[index];
+            fRejectedGlyphIDs.push_back(glyphID);
+            fRejectedPositions.push_back(pos);
+            fRejectSize++;
+        } else {
+            SkASSERT(fRejectSize < fRejects.size());
+            fRejects[fRejectSize++] = fSource[index];
+        }
+    }
+
+    void reject(size_t index, int rejectedMaxDimension) {
+        fRejectedMaxDimension = SkTMax(fRejectedMaxDimension, rejectedMaxDimension);
+        this->reject(index);
+    }
+
+    SkZip<const SkGlyphID, const SkPoint> flipRejectsToSource() {
+        fRejects = SkMakeZip(fRejectedGlyphIDs, fRejectedPositions).first(fRejectSize);
+        fSource = fRejects;
+        fRejectSize = 0;
+        fSourceMaxDimension = fRejectedMaxDimension;
+        fRejectedMaxDimension = 0;
+        return fSource;
+    }
+
+    SkZip<const SkGlyphID, const SkPoint> source() const { return fSource; }
+
+    int rejectedMaxDimension() const { return fSourceMaxDimension; }
+
+private:
+    SkSourceGlyphBuffer(const SkZip<const SkGlyphID, const SkPoint>& source) {
+        fSource = source;
+    }
+    bool sourceIsRejectBuffers() const {
+        return fSource.get<0>().data() == fRejectedGlyphIDs.data();
+    }
+
+    SkZip<const SkGlyphID, const SkPoint> fSource;
+    size_t fRejectSize{0};
+    int fSourceMaxDimension{0};
+    int fRejectedMaxDimension{0};
+    SkZip<SkGlyphID, SkPoint> fRejects;
+    SkSTArray<4, SkGlyphID> fRejectedGlyphIDs;
+    SkSTArray<4, SkPoint> fRejectedPositions;
+};
+
 // A memory format that allows an SkPackedGlyphID, SkGlyph*, and SkPath* to occupy the same
 // memory. This allows SkPackedGlyphIDs as input, and SkGlyph*/SkPath* as output using the same
 // memory.
diff --git a/src/core/SkZip.h b/src/core/SkZip.h
index 1b38b41..1249fbb 100644
--- a/src/core/SkZip.h
+++ b/src/core/SkZip.h
@@ -8,7 +8,6 @@
 #ifndef SkZip_DEFINED
 #define SkZip_DEFINED
 
-#include <cstddef>
 #include <iterator>
 #include <tuple>
 #include <type_traits>
@@ -48,10 +47,10 @@
     };
 
     template<typename T>
-    using make_nullptr = std::integral_constant<std::nullptr_t, nullptr>;
+    static constexpr T* nullify = nullptr;
 
 public:
-    constexpr SkZip() : fPointers{make_nullptr<Ts*>::value...}, fSize{0} {}
+    constexpr SkZip() : fPointers{nullify<Ts>...}, fSize{0} {}
     constexpr SkZip(size_t) = delete;
     constexpr SkZip(size_t size, Ts*... ts)
             : fPointers{ts...}
@@ -181,6 +180,10 @@
 };
 
 template<typename... Ts>
+template<typename T>
+constexpr T* SkZip<Ts...>::nullify;
+
+template<typename... Ts>
 inline constexpr auto SkMakeZip(Ts&& ... ts) {
     return SkMakeZipDetail::MakeZip(std::forward<Ts>(ts)...);
 }
diff --git a/tests/SkGlyphBufferTest.cpp b/tests/SkGlyphBufferTest.cpp
index ea6c5d5..4ce5041 100644
--- a/tests/SkGlyphBufferTest.cpp
+++ b/tests/SkGlyphBufferTest.cpp
@@ -11,6 +11,72 @@
 #include "src/core/SkScalerContext.h"
 #include "tests/Test.h"
 
+
+DEF_TEST(SkSourceGlyphBufferBasic, reporter) {
+    SkSourceGlyphBuffer rejects;
+    // Positions are picked to avoid precision problems.
+    const SkPoint positions[] = {{10.25,10.25}, {20.5,10.25}, {30.75,10.25}, {40,10.25}};
+    const SkGlyphID glyphIDs[] = {1, 2, 3, 4};
+    auto source = SkMakeZip(glyphIDs, positions);
+
+    rejects.setSource(source);
+    for (auto t : SkMakeEnumerate(rejects.source())) {
+        size_t i; SkGlyphID glyphID; SkPoint pos;
+        std::forward_as_tuple(i, std::tie(glyphID, pos)) = t;
+        REPORTER_ASSERT(reporter, glyphID == std::get<0>(source[i]));
+        REPORTER_ASSERT(reporter, pos == std::get<1>(source[i]));
+    }
+    // Reject a couple of glyphs.
+    rejects.reject(1);
+    rejects.reject(2, 100);
+    rejects.flipRejectsToSource();
+    REPORTER_ASSERT(reporter, rejects.rejectedMaxDimension() == 100);
+    for (auto t : SkMakeEnumerate(rejects.source())) {
+        size_t i; SkGlyphID glyphID; SkPoint pos;
+        std::forward_as_tuple(i, std::tie(glyphID, pos)) = t;
+        // This will index 1 and 2 from the original source.
+        size_t j = i + 1;
+        REPORTER_ASSERT(reporter, glyphID == std::get<0>(source[j]));
+        REPORTER_ASSERT(reporter, pos == std::get<1>(source[j]));
+    }
+
+    // Reject an additional glyph
+    rejects.reject(0, 10);
+    rejects.flipRejectsToSource();
+    REPORTER_ASSERT(reporter, rejects.rejectedMaxDimension() == 10);
+    for (auto t : SkMakeEnumerate(rejects.source())) {
+        size_t i; SkGlyphID glyphID; SkPoint pos;
+        std::forward_as_tuple(i, std::tie(glyphID, pos)) = t;
+        // This will index 1 from the original source.
+        size_t j = i + 1;
+        REPORTER_ASSERT(reporter, glyphID == std::get<0>(source[j]));
+        REPORTER_ASSERT(reporter, pos == std::get<1>(source[j]));
+    }
+
+    // Start all over
+    rejects.setSource(source);
+    for (auto t : SkMakeEnumerate(rejects.source())) {
+        size_t i; SkGlyphID glyphID; SkPoint pos;
+        std::forward_as_tuple(i, std::tie(glyphID, pos)) = t;
+        REPORTER_ASSERT(reporter, glyphID == std::get<0>(source[i]));
+        REPORTER_ASSERT(reporter, pos == std::get<1>(source[i]));
+    }
+
+    // Check that everything is working after calling setSource.
+    rejects.reject(1);
+    rejects.reject(2, 100);
+    rejects.flipRejectsToSource();
+    REPORTER_ASSERT(reporter, rejects.rejectedMaxDimension() == 100);
+    for (auto t : SkMakeEnumerate(rejects.source())) {
+        size_t i; SkGlyphID glyphID; SkPoint pos;
+        std::forward_as_tuple(i, std::tie(glyphID, pos)) = t;
+        // This will index 1 and 2 from the original source.
+        size_t j = i + 1;
+        REPORTER_ASSERT(reporter, glyphID == std::get<0>(source[j]));
+        REPORTER_ASSERT(reporter, pos == std::get<1>(source[j]));
+    }
+}
+
 DEF_TEST(SkDrawableGlyphBufferBasic, reporter) {
     // Positions are picked to avoid precision problems.
     const SkPoint positions[] = {{10.25,10.25}, {20.5,10.25}, {30.75,10.25}, {40,10.25}};