SkShaper per-line callback

Tweak SkShaper to call out for each line, instead of bundling everything
as a text blob.

Change-Id: Ic522f88afcf31cefd873dc8b5cde1ac2e107c64f
Reviewed-on: https://skia-review.googlesource.com/c/176592
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skshaper/BUILD.gn b/modules/skshaper/BUILD.gn
index eb67f90..cb221e3 100644
--- a/modules/skshaper/BUILD.gn
+++ b/modules/skshaper/BUILD.gn
@@ -16,25 +16,24 @@
 source_set("skshaper") {
   if (skia_enable_skshaper) {
     public_configs = [ ":public_config" ]
-    public = [ "include/SkShaper.h" ]
+    public = [
+      "include/SkShaper.h",
+    ]
     deps = [
       "../..:skia",
     ]
+    sources = [
+      "src/SkShaper.cpp",
+    ]
     if (target_cpu == "wasm") {
-      sources = [
-        "src/SkShaper_primitive.cpp",
-      ]
+      sources += [ "src/SkShaper_primitive.cpp" ]
     } else {
-      sources = [
-        "src/SkShaper_harfbuzz.cpp",
-      ]
+      sources += [ "src/SkShaper_harfbuzz.cpp" ]
       deps += [
         "//third_party/harfbuzz",
         "//third_party/icu",
       ]
     }
     configs += [ "../../:skia_private" ]
-
   }
 }
-
diff --git a/modules/skshaper/include/SkShaper.h b/modules/skshaper/include/SkShaper.h
index 92c236c..391e583 100644
--- a/modules/skshaper/include/SkShaper.h
+++ b/modules/skshaper/include/SkShaper.h
@@ -11,14 +11,14 @@
 #include <memory>
 
 #include "SkPoint.h"
+#include "SkTextBlob.h"
 #include "SkTypeface.h"
 
 class SkFont;
-class SkTextBlobBuilder;
 
 /**
    Shapes text using HarfBuzz and places the shaped text into a
-   TextBlob.
+   client-managed buffer.
 
    If compiled without HarfBuzz, fall back on SkPaint::textToGlyphs.
  */
@@ -27,8 +27,22 @@
     SkShaper(sk_sp<SkTypeface> face);
     ~SkShaper();
 
+    class LineHandler {
+    public:
+        virtual ~LineHandler() = default;
+
+        struct Buffer {
+            SkGlyphID* glyphs;    // required
+            SkPoint*   positions; // required
+            char*      utf8text;  // optional
+            uint32_t*  clusters;  // optional
+        };
+
+        virtual Buffer newLineBuffer(const SkFont&, int glyphCount, int utf8textCount) = 0;
+    };
+
     bool good() const;
-    SkPoint shape(SkTextBlobBuilder* dest,
+    SkPoint shape(LineHandler* handler,
                   const SkFont& srcPaint,
                   const char* utf8text,
                   size_t textBytes,
@@ -44,4 +58,17 @@
     std::unique_ptr<Impl> fImpl;
 };
 
+/**
+ * Helper for shaping text directly into a SkTextBlob.
+ */
+class SkTextBlobBuilderLineHandler final : public SkShaper::LineHandler {
+public:
+    sk_sp<SkTextBlob> makeBlob();
+
+    SkShaper::LineHandler::Buffer newLineBuffer(const SkFont&, int, int) override;
+
+private:
+    SkTextBlobBuilder fBuilder;
+};
+
 #endif  // SkShaper_DEFINED
diff --git a/modules/skshaper/src/SkShaper.cpp b/modules/skshaper/src/SkShaper.cpp
new file mode 100644
index 0000000..35dafed
--- /dev/null
+++ b/modules/skshaper/src/SkShaper.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkShaper.h"
+
+#include "SkTextBlobPriv.h"
+
+SkShaper::LineHandler::Buffer SkTextBlobBuilderLineHandler::newLineBuffer(const SkFont& font,
+                                                                          int glyphCount,
+                                                                          int textCount) {
+    const auto& runBuffer = SkTextBlobBuilderPriv::AllocRunTextPos(&fBuilder, font, glyphCount,
+                                                                   textCount, SkString());
+    return { runBuffer.glyphs,
+             reinterpret_cast<SkPoint*>(runBuffer.pos),
+             runBuffer.utf8text,
+             runBuffer.clusters };
+}
+
+sk_sp<SkTextBlob> SkTextBlobBuilderLineHandler::makeBlob() {
+    return fBuilder.make();
+}
diff --git a/modules/skshaper/src/SkShaper_harfbuzz.cpp b/modules/skshaper/src/SkShaper_harfbuzz.cpp
index e5664cc..beb5923 100644
--- a/modules/skshaper/src/SkShaper_harfbuzz.cpp
+++ b/modules/skshaper/src/SkShaper_harfbuzz.cpp
@@ -22,7 +22,6 @@
 #include "SkTFitsIn.h"
 #include "SkTLazy.h"
 #include "SkTemplates.h"
-#include "SkTextBlobPriv.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
 #include "SkTypes.h"
@@ -414,19 +413,26 @@
     return (level & 1) == 0;
 }
 
-static void append(SkTextBlobBuilder* b, const ShapedRun& run, int start, int end, SkPoint* p) {
+static void append(SkShaper::LineHandler* handler, const ShapedRun& run, int start, int end,
+                   SkPoint* p) {
     unsigned len = end - start;
-    auto runBuffer = SkTextBlobBuilderPriv::AllocRunTextPos(b, run.fFont, len,
-            run.fUtf8End - run.fUtf8Start, SkString());
-    memcpy(runBuffer.utf8text, run.fUtf8Start, run.fUtf8End - run.fUtf8Start);
+
+    const auto buffer = handler->newLineBuffer(run.fFont, len, run.fUtf8End - run.fUtf8Start);
+    SkASSERT(buffer.glyphs);
+    SkASSERT(buffer.positions);
+
+    if (buffer.utf8text) {
+        memcpy(buffer.utf8text, run.fUtf8Start, run.fUtf8End - run.fUtf8Start);
+    }
 
     for (unsigned i = 0; i < len; i++) {
         // Glyphs are in logical order, but output ltr since PDF readers seem to expect that.
         const ShapedGlyph& glyph = run.fGlyphs[is_LTR(run.fLevel) ? start + i : end - 1 - i];
-        runBuffer.glyphs[i] = glyph.fID;
-        runBuffer.clusters[i] = glyph.fCluster;
-        reinterpret_cast<SkPoint*>(runBuffer.pos)[i] =
-                SkPoint::Make(p->fX + glyph.fOffset.fX, p->fY - glyph.fOffset.fY);
+        buffer.glyphs[i] = glyph.fID;
+        buffer.positions[i] = SkPoint::Make(p->fX + glyph.fOffset.fX, p->fY - glyph.fOffset.fY);
+        if (buffer.clusters) {
+            buffer.clusters[i] = glyph.fCluster;
+        }
         p->fX += glyph.fAdvance.fX;
         p->fY += glyph.fAdvance.fY;
     }
@@ -516,7 +522,7 @@
            fImpl->fBreakIterator;
 }
 
-SkPoint SkShaper::shape(SkTextBlobBuilder* builder,
+SkPoint SkShaper::shape(LineHandler* handler,
                         const SkFont& srcPaint,
                         const char* utf8,
                         size_t utf8Bytes,
@@ -524,7 +530,7 @@
                         SkPoint point,
                         SkScalar width) const {
     sk_sp<SkFontMgr> fontMgr = SkFontMgr::RefDefault();
-    SkASSERT(builder);
+    SkASSERT(handler);
     UBiDiLevel defaultLevel = leftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
     //hb_script_t script = ...
 
@@ -761,7 +767,7 @@
             int endGlyphIndex = (logicalIndex == runIndex)
                               ? glyphIndex + 1
                               : runs[logicalIndex].fNumGlyphs;
-            append(builder, runs[logicalIndex], startGlyphIndex, endGlyphIndex, &currentPoint);
+            append(handler, runs[logicalIndex], startGlyphIndex, endGlyphIndex, &currentPoint);
         }
 
         currentPoint.fY += maxDescent + maxLeading;
diff --git a/modules/skshaper/src/SkShaper_primitive.cpp b/modules/skshaper/src/SkShaper_primitive.cpp
index 7b798e7..3d54262 100644
--- a/modules/skshaper/src/SkShaper_primitive.cpp
+++ b/modules/skshaper/src/SkShaper_primitive.cpp
@@ -8,7 +8,6 @@
 #include "SkShaper.h"
 
 #include "SkStream.h"
-#include "SkTextBlob.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
 
@@ -32,7 +31,7 @@
     return (((0xE5 << 24) >> ((unsigned)c >> 4 << 1)) & 3) + 1;
 }
 
-SkPoint SkShaper::shape(SkTextBlobBuilder* builder,
+SkPoint SkShaper::shape(LineHandler* handler,
                         const SkFont& srcFont,
                         const char* utf8text,
                         size_t textBytes,
@@ -40,6 +39,7 @@
                         SkPoint point,
                         SkScalar width) const {
     sk_ignore_unused_variable(leftToRight);
+    sk_ignore_unused_variable(width);
 
     SkFont font(srcFont);
     font.setTypeface(fImpl->fTypeface);
@@ -47,32 +47,29 @@
     if (glyphCount <= 0) {
         return point;
     }
-    SkRect bounds;
+
     SkFontMetrics metrics;
     font.getMetrics(&metrics);
     point.fY -= metrics.fAscent;
-    (void)font.measureText(utf8text, textBytes, SkTextEncoding::kUTF8, &bounds);
-    const SkTextBlobBuilder::RunBuffer& runBuffer =
-        builder->allocRunTextPosH(font, glyphCount, point.y(), textBytes, SkString(), &bounds);
-    memcpy(runBuffer.utf8text, utf8text, textBytes);
-    const char* txtPtr = utf8text;
-    for (int i = 0; i < glyphCount; ++i) {
-        // Each charater maps to exactly one glyph via SkGlyphCache::unicharToGlyph().
-        runBuffer.clusters[i] = SkToU32(txtPtr - utf8text);
-        txtPtr += utf8_lead_byte_to_count(txtPtr);
-        SkASSERT(txtPtr <= utf8text + textBytes);
-    }
-    (void)font.textToGlyphs(utf8text, textBytes, SkTextEncoding::kUTF8,
-                            runBuffer.glyphs, glyphCount);
-    // replace with getPos()?
-    (void)font.getWidths(runBuffer.glyphs, glyphCount, runBuffer.pos);
-    SkScalar x = point.x();
-    for (int i = 0; i < glyphCount; ++i) {
-        SkScalar w = runBuffer.pos[i];
-        runBuffer.pos[i] = x;
-        x += w;
-    }
-    point.fY += metrics.fDescent + metrics.fLeading;
 
-    return point;
+    const auto buffer = handler->newLineBuffer(font, glyphCount, textBytes);
+    SkAssertResult(font.textToGlyphs(utf8text, textBytes, SkTextEncoding::kUTF8, buffer.glyphs,
+                                     glyphCount) == glyphCount);
+    font.getPos(buffer.glyphs, glyphCount, buffer.positions, point);
+
+    if (buffer.utf8text) {
+        memcpy(buffer.utf8text, utf8text, textBytes);
+    }
+
+    if (buffer.clusters) {
+        const char* txtPtr = utf8text;
+        for (int i = 0; i < glyphCount; ++i) {
+            // Each charater maps to exactly one glyph via SkGlyphCache::unicharToGlyph().
+            buffer.clusters[i] = SkToU32(txtPtr - utf8text);
+            txtPtr += utf8_lead_byte_to_count(txtPtr);
+            SkASSERT(txtPtr <= utf8text + textBytes);
+        }
+    }
+
+    return point + SkVector::Make(0, metrics.fDescent + metrics.fLeading);
 }
diff --git a/samplecode/SampleTextBox.cpp b/samplecode/SampleTextBox.cpp
index 8762391..7931dfc 100644
--- a/samplecode/SampleTextBox.cpp
+++ b/samplecode/SampleTextBox.cpp
@@ -81,12 +81,12 @@
         paint.setColor(fg);
 
         for (int i = 9; i < 24; i += 2) {
-            SkTextBlobBuilder builder;
+            SkTextBlobBuilderLineHandler builder;
             paint.setTextSize(SkIntToScalar(i));
             SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
             SkPoint end = shaper.shape(&builder, font, gText, strlen(gText), true,
                                        { margin, margin }, w - margin);
-            canvas->drawTextBlob(builder.make(), 0, 0, paint);
+            canvas->drawTextBlob(builder.makeBlob(), 0, 0, paint);
 
             canvas->translate(0, end.y());
         }
diff --git a/src/utils/SkLua.cpp b/src/utils/SkLua.cpp
index 4ff6c6e..12b5279 100644
--- a/src/utils/SkLua.cpp
+++ b/src/utils/SkLua.cpp
@@ -1923,11 +1923,11 @@
     SkShaper shaper(nullptr);
 
     SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
-    SkTextBlobBuilder builder;
+    SkTextBlobBuilderLineHandler builder;
     SkPoint end = shaper.shape(&builder, font, text, strlen(text), true,
                                { bounds.left(), bounds.top() }, bounds.width());
 
-    push_ref<SkTextBlob>(L, builder.make());
+    push_ref<SkTextBlob>(L, builder.makeBlob());
     SkLua(L).pushScalar(end.fY);
     return 2;
 }
diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/using_skia_and_harfbuzz.cpp
index 2e3f715..6f378e2 100644
--- a/tools/using_skia_and_harfbuzz.cpp
+++ b/tools/using_skia_and_harfbuzz.cpp
@@ -139,11 +139,11 @@
     }
 
     void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) {
-        SkTextBlobBuilder textBlobBuilder;
+        SkTextBlobBuilderLineHandler textBlobBuilder;
         SkPoint endPoint = shaper.shape(&textBlobBuilder, font, text, textBytes, true,
                                         SkPoint{0, 0},
                                         config->page_width.value - 2*config->left_margin.value);
-        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+        sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
         // If we don't have a page, or if we're not at the start of the page and the blob won't fit
         if (!pageCanvas ||
               (current_y > config->line_spacing_ratio.value * config->font_size.value &&