Reland "Add setters for providing client info to SkParagraphBuilder"

This reverts commit b1398c9c64a878e4d3e5d8268b0a01bbda060857.

Reason for revert: Fixing the build

Original change's description:
> Revert "Add setters for providing client info to SkParagraphBuilder"
>
> This reverts commit d840c9bc3ef36ee79f991c35488d14013c8b06bb.
>
> Reason for revert: breaking g3 and Android rolls, e.g.
>
> ld.lld: error: undefined symbol: SkUnicode::MakeClientBasedUnicode(SkSpan<char>, std::__1::vector<SkUnicode::BidiRegion, std::__1::allocator<SkUnicode::BidiRegion>>, std::__1::vector<unsigned int, std::__1::allocator<unsigned int>>, std::__1::vector<unsigned int, std::__1::allocator<unsigned int>>, std::__1::vector<SkUnicode::LineBreakBefore, std::__1::allocator<SkUnicode::LineBreakBefore>>)
> >>> referenced by ParagraphBuilderImpl.cpp:161 (external/skia/modules/skparagraph/src/ParagraphBuilderImpl.cpp:161)
> >>>               out/soong/.intermediates/external/skia/skia_nanobench/android_arm_armv8-a/obj/external/skia/modules/skparagraph/src/ParagraphBuilderImpl.o:(skia::textlayout::ParagraphBuilderImpl::Build())
> clang-16: error: linker command failed with exit code 1 (use -v to see invocation)
>
> Original change's description:
> > Add setters for providing client info to SkParagraphBuilder
> >
> > We need to have UTF8 and UTF16 setters for client info. The reason is that ICU4X produces UTF8 indices, but browser APIs produce UTF16 indices. So SkParagraphBuilder needs to accept both.
> >
> > Change-Id: Ie26e1d728a35c2b2e0d971918c6c079ddbed25ed
> > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/611560
> > Reviewed-by: Kevin Lubick <kjlubick@google.com>
> > Reviewed-by: Julia Lavrova <jlavrova@google.com>
> > Commit-Queue: Julia Lavrova <jlavrova@google.com>
>
> Change-Id: I4f3cb667f992b86f0ed3a121a156fa7716e6a99d
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/631756
> Auto-Submit: Florin Malita <fmalita@google.com>
> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> Commit-Queue: Julia Lavrova <jlavrova@google.com>
> Reviewed-by: Kevin Lubick <kjlubick@google.com>

Change-Id: Ie6bfc5cb99d4ca60773e734450c79b06ae49693d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/631837
Reviewed-by: Julia Lavrova <jlavrova@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js
index e10fd33..89c177a 100644
--- a/modules/canvaskit/externs.js
+++ b/modules/canvaskit/externs.js
@@ -170,7 +170,16 @@
     ShapeText: function() {},
     addText: function() {},
     build: function() {},
-    buildWithClientInfo: function() {},
+
+    setBidiRegionsUtf8: function() {},
+    setBidiRegionsUtf16: function() {},
+    setWordsUtf8: function() {},
+    setWordsUtf16: function() {},
+    setGraphemeBreaksUtf8: function() {},
+    setGraphemeBreaksUtf16: function() {},
+    setLineBreaksUtf8: function() {},
+    setLineBreaksUtf16: function() {},
+
     getText: function() {},
     pop: function() {},
     reset: function() {},
@@ -188,7 +197,15 @@
     _pushStyle: function() {},
     _pushPaintStyle: function() {},
     _addPlaceholder: function() {},
-    _buildWithClientInfo: function() {},
+
+    _setBidiRegionsUtf8: function() {},
+    _setBidiRegionsUtf16: function() {},
+    _setWordsUtf8: function() {},
+    _setWordsUtf16: function() {},
+    _setGraphemeBreaksUtf8: function() {},
+    _setGraphemeBreaksUtf16: function() {},
+    _setLineBreaksUtf8: function() {},
+    _setLineBreaksUtf16: function() {},
   },
 
   RuntimeEffect: {
diff --git a/modules/canvaskit/npm_build/extra.html b/modules/canvaskit/npm_build/extra.html
index b8d210e..e01f2b6 100644
--- a/modules/canvaskit/npm_build/extra.html
+++ b/modules/canvaskit/npm_build/extra.html
@@ -450,12 +450,11 @@
         const mallocedLineBreaks = CanvasKit.Malloc(Uint32Array, lineBreaks.length);
         mallocedLineBreaks.toTypedArray().set(lineBreaks);
 
-        const paragraph = builder.buildWithClientInfo(
-            mallocedBidis,
-            mallocedWords,
-            graphemesArr,
-            mallocedLineBreaks
-        );
+        builder.setBidiRegionsUtf16(mallocedBidis);
+        builder.setWordsUtf16(mallocedWords);
+        builder.setGraphemeBreaksUtf16(mallocedGraphemes);
+        builder.setLineBreaksUtf16(mallocedLineBreaks);
+        const paragraph = builder.build();
 
         paragraph.layout(600);
 
diff --git a/modules/canvaskit/npm_build/paragraphs.html b/modules/canvaskit/npm_build/paragraphs.html
index 8c14eaa..9fe043a 100644
--- a/modules/canvaskit/npm_build/paragraphs.html
+++ b/modules/canvaskit/npm_build/paragraphs.html
@@ -150,12 +150,11 @@
     const mallocedLineBreaks = CanvasKit.Malloc(Uint32Array, lineBreaks.length);
     mallocedLineBreaks.toTypedArray().set(lineBreaks);
 
-    const paragraph = builder.buildWithClientInfo(
-        mallocedBidis,
-        mallocedWords,
-        mallocedGraphemes,
-        mallocedLineBreaks
-    );
+    builder.setBidiRegionsUtf16(mallocedBidis);
+    builder.setWordsUtf16(mallocedWords);
+    builder.setGraphemeBreaksUtf16(mallocedGraphemes);
+    builder.setLineBreaksUtf16(mallocedLineBreaks);
+    const paragraph = builder.build();
 
     fontMgr.delete();
 
@@ -207,7 +206,6 @@
       breaks.push(iterator.current(), getBreakType(iterator.breakType()));
     }
 
-    console.log(breaks);
     return breaks;
   }
 
diff --git a/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts b/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts
index 374a52b..c770fa8 100644
--- a/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts
+++ b/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts
@@ -614,16 +614,11 @@
     builder2.reset();
 
     const text = builder.getText(); // $ExpectType string
-    const mallocedBidis = CK.Malloc(Uint32Array, 3);
-    const mallocedWords = new Uint32Array(10);
-    const mallocedGraphemes =  new Uint32Array(10);
-    const mallocedLineBreaks =  new Uint32Array(10);
-    const paragraph3 = builder.buildWithClientInfo(// $ExpectType Paragraph
-        mallocedBidis,
-        mallocedWords,
-        mallocedGraphemes,
-        mallocedLineBreaks
-    );
+    builder.setBidiRegionsUtf16(CK.Malloc(Uint32Array, 3));
+    builder.setWordsUtf16(new Uint32Array(10));
+    builder.setGraphemeBreaksUtf16(new Uint32Array(10));
+    builder.setLineBreaksUtf16(new Uint32Array(10));
+    const paragraph3 = builder.build(); // $ExpectType Paragraph
 }
 
 function particlesTests(CK: CanvasKit, canvas?: Canvas) {
diff --git a/modules/canvaskit/npm_build/types/index.d.ts b/modules/canvaskit/npm_build/types/index.d.ts
index 29149c7..7bfb841 100644
--- a/modules/canvaskit/npm_build/types/index.d.ts
+++ b/modules/canvaskit/npm_build/types/index.d.ts
@@ -987,25 +987,52 @@
     build(): Paragraph;
 
     /**
-     * Returns a Paragraph object that can be used to be layout and
-     * paint the text to an Canvas.
      * @param bidiRegions is an array of unsigned integers that should be
-     * treated as triples (starting index, ending index, direction).
-     * Direction == 1 means left-to-right, direction == 0 is right-to-left. It's
-     * recommended to use `CanvasKit.TextDirection.RTL.value` or
-     * `CanvasKit.TextDirection.LTR.value` instead of hardcoding 0 or 1.
+     * treated as triples (starting index, ending index, bidi level).
+     *
+     * The indices are expected to be relative to the UTF-8 representation of
+     * the text.
+     */
+    setBidiRegionsUtf8(bidiRegions: InputBidiRegions): void;
+    /**
+     * @param bidiRegions is an array of unsigned integers that should be
+     * treated as triples (starting index, ending index, bidi level).
      *
      * The indices are expected to be relative to the UTF-16 representation of
      * the text.
+     */
+    setBidiRegionsUtf16(bidiRegions: InputBidiRegions): void;
+
+    /**
      * @param words is an array of word edges (starting or ending). You can
      * pass 2 elements (0 as a start of the entire text and text.size as the
-     * end). This information only needed for a specific API method getWords.
+     * end). This information is only needed for a specific API method getWords.
+     *
+     * The indices are expected to be relative to the UTF-8 representation of
+     * the text.
+     */
+    setWordsUtf8(words: InputWords): void;
+    /**
+     * @param words is an array of word edges (starting or ending). You can
+     * pass 2 elements (0 as a start of the entire text and text.size as the
+     * end). This information is only needed for a specific API method getWords.
      *
      * The indices are expected to be relative to the UTF-16 representation of
      * the text.
      *
      * The `Intl.Segmenter` API can be used as a source for this data.
+     */
+    setWordsUtf16(words: InputWords): void;
+
+    /**
+     * @param graphemes is an array of indexes in the input text that point
+     * to the start of each grapheme.
      *
+     * The indices are expected to be relative to the UTF-8 representation of
+     * the text.
+     */
+    setGraphemeBreaksUtf8(graphemes: InputGraphemes): void;
+    /**
      * @param graphemes is an array of indexes in the input text that point
      * to the start of each grapheme.
      *
@@ -1013,7 +1040,20 @@
      * the text.
      *
      * The `Intl.Segmenter` API can be used as a source for this data.
+     */
+    setGraphemeBreaksUtf16(graphemes: InputGraphemes): void;
+
+    /**
+     * @param lineBreaks is an array of unsigned integers that should be
+     * treated as pairs (index, break type) that point to the places of possible
+     * line breaking if needed. It should include 0 as the first element.
+     * Break type == 0 means soft break, break type == 1 is a hard break.
      *
+     * The indices are expected to be relative to the UTF-8 representation of
+     * the text.
+     */
+    setLineBreaksUtf8(lineBreaks: InputLineBreaks): void;
+    /**
      * @param lineBreaks is an array of unsigned integers that should be
      * treated as pairs (index, break type) that point to the places of possible
      * line breaking if needed. It should include 0 as the first element.
@@ -1024,10 +1064,7 @@
      *
      * Chrome's `v8BreakIterator` API can be used as a source for this data.
      */
-    buildWithClientInfo(bidiRegions?: InputBidiRegions | null,
-                        words?: InputWords | null,
-                        graphemes?: InputGraphemes | null,
-                        lineBreaks?: InputLineBreaks | null): Paragraph;
+    setLineBreaksUtf16(lineBreaks: InputLineBreaks): void;
 
     /**
      * Returns the entire Paragraph text (which is useful in case that text
diff --git a/modules/canvaskit/paragraph.js b/modules/canvaskit/paragraph.js
index c16c8eb..cc43ab6 100644
--- a/modules/canvaskit/paragraph.js
+++ b/modules/canvaskit/paragraph.js
@@ -329,23 +329,48 @@
       this._addPlaceholder(width, height, alignment, baseline, offset);
     };
 
-    CanvasKit.ParagraphBuilder.prototype.buildWithClientInfo =
-          function(bidiRegions, words, graphemeBreaks, lineBreaks) {
-
+    CanvasKit.ParagraphBuilder.prototype.setBidiRegionsUtf8 = function(bidiRegions) {
       var bPtr = copy1dArray(bidiRegions, 'HEAPU32');
-      var wPtr = copy1dArray(words, 'HEAPU32');
-      var gPtr = copy1dArray(graphemeBreaks, 'HEAPU32');
-      var lPtr = copy1dArray(lineBreaks, 'HEAPU32');
-      var para = this._buildWithClientInfo(
-                          bPtr, bidiRegions && bidiRegions.length || 0,
-                          wPtr, words && words.length || 0,
-                          gPtr, graphemeBreaks && graphemeBreaks.length || 0,
-                          lPtr, lineBreaks && lineBreaks.length || 0);
+      this._setBidiRegionsUtf8(bPtr, bidiRegions && bidiRegions.length || 0);
       freeArraysThatAreNotMallocedByUsers(bPtr,     bidiRegions);
-      freeArraysThatAreNotMallocedByUsers(wPtr,     words);
-      freeArraysThatAreNotMallocedByUsers(gPtr,     graphemeBreaks);
-      freeArraysThatAreNotMallocedByUsers(lPtr,     lineBreaks);
-      return para;
+    };
+    CanvasKit.ParagraphBuilder.prototype.setBidiRegionsUtf16 = function(bidiRegions) {
+      var bPtr = copy1dArray(bidiRegions, 'HEAPU32');
+      this._setBidiRegionsUtf16(bPtr, bidiRegions && bidiRegions.length || 0);
+      freeArraysThatAreNotMallocedByUsers(bPtr, bidiRegions);
+    };
+
+    CanvasKit.ParagraphBuilder.prototype.setWordsUtf8 = function(words) {
+      var bPtr = copy1dArray(words, 'HEAPU32');
+      this._setWordsUtf8(bPtr, words && words.length || 0);
+      freeArraysThatAreNotMallocedByUsers(bPtr,     words);
+    };
+    CanvasKit.ParagraphBuilder.prototype.setWordsUtf16 = function(words) {
+      var bPtr = copy1dArray(words, 'HEAPU32');
+      this._setWordsUtf16(bPtr, words && words.length || 0);
+      freeArraysThatAreNotMallocedByUsers(bPtr, words);
+    };
+
+    CanvasKit.ParagraphBuilder.prototype.setGraphemeBreaksUtf8 = function(graphemeBreaks) {
+      var bPtr = copy1dArray(graphemeBreaks, 'HEAPU32');
+      this._setGraphemeBreaksUtf8(bPtr, graphemeBreaks && graphemeBreaks.length || 0);
+      freeArraysThatAreNotMallocedByUsers(bPtr,     graphemeBreaks);
+    };
+    CanvasKit.ParagraphBuilder.prototype.setGraphemeBreaksUtf16 = function(graphemeBreaks) {
+      var bPtr = copy1dArray(graphemeBreaks, 'HEAPU32');
+      this._setGraphemeBreaksUtf16(bPtr, graphemeBreaks && graphemeBreaks.length || 0);
+      freeArraysThatAreNotMallocedByUsers(bPtr, graphemeBreaks);
+    };
+
+    CanvasKit.ParagraphBuilder.prototype.setLineBreaksUtf8 = function(lineBreaks) {
+      var bPtr = copy1dArray(lineBreaks, 'HEAPU32');
+      this._setLineBreaksUtf8(bPtr, lineBreaks && lineBreaks.length || 0);
+      freeArraysThatAreNotMallocedByUsers(bPtr,     lineBreaks);
+    };
+    CanvasKit.ParagraphBuilder.prototype.setLineBreaksUtf16 = function(lineBreaks) {
+      var bPtr = copy1dArray(lineBreaks, 'HEAPU32');
+      this._setLineBreaksUtf16(bPtr, lineBreaks && lineBreaks.length || 0);
+      freeArraysThatAreNotMallocedByUsers(bPtr, lineBreaks);
     };
 });
 }(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/modules/canvaskit/paragraph_bindings.cpp b/modules/canvaskit/paragraph_bindings.cpp
index 637b0f7..6c1e95b 100644
--- a/modules/canvaskit/paragraph_bindings.cpp
+++ b/modules/canvaskit/paragraph_bindings.cpp
@@ -592,24 +592,57 @@
                           auto text = self.getText();
                           return emscripten::val(std::string(text.data(), text.size()).c_str());
                       }))
-            .function("_buildWithClientInfo",
+            .function("_setBidiRegionsUtf8",
                       optional_override([](para::ParagraphBuilderImpl& self,
-                                           WASMPointerU32 clientBidis, size_t bidisNum,
-                                           WASMPointerU32 clientWords, size_t wordsNum,
-                                           WASMPointerU32 clientGraphemes, size_t graphemesNum,
-                                           WASMPointerU32 clientLineBreaks, size_t lineBreaksNum) {
+                                           WASMPointerU32 clientBidis, size_t bidisNum) {
                       SkUnicode::Position* bidiData = reinterpret_cast<SkUnicode::Position*>(clientBidis);
                       std::vector<SkUnicode::BidiRegion> bidiRegions;
                       for (size_t i = 0; i < bidisNum; i += 3) {
                           auto start = bidiData[i];
                           auto end = bidiData[i+1];
-                          auto direction = SkToU8(bidiData[i+2]);
+                          auto level = SkToU8(bidiData[i+2]);
 
-                          SkUnicode::BidiLevel level = direction == static_cast<int>(para::TextDirection::kLtr)
-                                                   ? static_cast<SkUnicode::BidiLevel>(SkUnicode::TextDirection::kLTR)
-                                                   : static_cast<SkUnicode::BidiLevel>(SkUnicode::TextDirection::kRTL);
                           bidiRegions.emplace_back(start, end, level);
                       }
+                      self.setBidiRegionsUtf8(std::move(bidiRegions));
+                  }))
+            .function("_setBidiRegionsUtf16",
+                      optional_override([](para::ParagraphBuilderImpl& self,
+                                           WASMPointerU32 clientBidis, size_t bidisNum) {
+                      SkUnicode::Position* bidiData = reinterpret_cast<SkUnicode::Position*>(clientBidis);
+                      std::vector<SkUnicode::BidiRegion> bidiRegions;
+                      for (size_t i = 0; i < bidisNum; i += 3) {
+                          auto start = bidiData[i];
+                          auto end = bidiData[i+1];
+                          auto level = SkToU8(bidiData[i+2]);
+
+                          bidiRegions.emplace_back(start, end, level);
+                      }
+                      self.setBidiRegionsUtf16(std::move(bidiRegions));
+                  }))
+            .function("_setWordsUtf8",
+                      optional_override([](para::ParagraphBuilderImpl& self,
+                                           WASMPointerU32 clientWords, size_t wordsNum) {
+                      self.setWordsUtf8(convertArrayU32(clientWords, wordsNum));
+                  }))
+            .function("_setWordsUtf16",
+                      optional_override([](para::ParagraphBuilderImpl& self,
+                                           WASMPointerU32 clientWords, size_t wordsNum) {
+                      self.setWordsUtf16(convertArrayU32(clientWords, wordsNum));
+                  }))
+            .function("_setGraphemeBreaksUtf8",
+                      optional_override([](para::ParagraphBuilderImpl& self,
+                                           WASMPointerU32 clientGraphemes, size_t graphemesNum) {
+                      self.setGraphemeBreaksUtf8(convertArrayU32(clientGraphemes, graphemesNum));
+                  }))
+            .function("_setGraphemeBreaksUtf16",
+                      optional_override([](para::ParagraphBuilderImpl& self,
+                                           WASMPointerU32 clientGraphemes, size_t graphemesNum) {
+                      self.setGraphemeBreaksUtf16(convertArrayU32(clientGraphemes, graphemesNum));
+                  }))
+            .function("_setLineBreaksUtf8",
+                      optional_override([](para::ParagraphBuilderImpl& self,
+                                           WASMPointerU32 clientLineBreaks, size_t lineBreaksNum) {
                       SkUnicode::Position* lineBreakData = reinterpret_cast<SkUnicode::Position*>(clientLineBreaks);
                       std::vector<SkUnicode::LineBreakBefore> lineBreaks;
                       for (size_t i = 0; i < lineBreaksNum; i += 2) {
@@ -621,11 +654,23 @@
                               lineBreaks.emplace_back(pos, SkUnicode::LineBreakType::kHardLineBreak);
                           }
                       }
-                      return self.BuildWithClientInfo(
-                                        std::move(bidiRegions),
-                                        convertArrayU32(clientWords, wordsNum),
-                                        convertArrayU32(clientGraphemes, graphemesNum),
-                                        std::move(lineBreaks));
+                      self.setLineBreaksUtf8(std::move(lineBreaks));
+                  }))
+            .function("_setLineBreaksUtf16",
+                      optional_override([](para::ParagraphBuilderImpl& self,
+                                           WASMPointerU32 clientLineBreaks, size_t lineBreaksNum) {
+                      SkUnicode::Position* lineBreakData = reinterpret_cast<SkUnicode::Position*>(clientLineBreaks);
+                      std::vector<SkUnicode::LineBreakBefore> lineBreaks;
+                      for (size_t i = 0; i < lineBreaksNum; i += 2) {
+                          auto pos = lineBreakData[i];
+                          auto breakType = lineBreakData[i+1];
+                          if (breakType == 0) {
+                              lineBreaks.emplace_back(pos, SkUnicode::LineBreakType::kSoftLineBreak);
+                          } else {
+                              lineBreaks.emplace_back(pos, SkUnicode::LineBreakType::kHardLineBreak);
+                          }
+                      }
+                      self.setLineBreaksUtf16(std::move(lineBreaks));
                   }));
 
     class_<para::TypefaceFontProvider, base<SkFontMgr>>("TypefaceFontProvider")
diff --git a/modules/skparagraph/src/ParagraphBuilderImpl.cpp b/modules/skparagraph/src/ParagraphBuilderImpl.cpp
index 96c56ba..8a5d268 100644
--- a/modules/skparagraph/src/ParagraphBuilderImpl.cpp
+++ b/modules/skparagraph/src/ParagraphBuilderImpl.cpp
@@ -40,7 +40,11 @@
         , fUtf8()
         , fFontCollection(std::move(fontCollection))
         , fParagraphStyle(style)
-        , fUnicode(std::move(unicode)) {
+        , fUnicode(std::move(unicode))
+#if !defined(SK_UNICODE_ICU_IMPLEMENTATION) && defined(SK_UNICODE_CLIENT_IMPLEMENTATION)
+        , fUsingClientInfo(false)
+#endif
+{
     startStyledBlock();
 }
 
@@ -148,6 +152,19 @@
 
     // Add one fake placeholder with the rest of the text
     addPlaceholder(PlaceholderStyle(), true);
+
+#if !defined(SK_UNICODE_ICU_IMPLEMENTATION) && defined(SK_UNICODE_CLIENT_IMPLEMENTATION)
+    SkASSERT(fUsingClientInfo);
+    fUTF8IndexForUTF16Index.clear();
+
+    // This is the place where SkUnicode is paired with SkParagraph
+    fUnicode = SkUnicode::MakeClientBasedUnicode(this->getText(),
+                                                 std::move(fBidiRegionsUtf8),
+                                                 std::move(fWordsUtf8),
+                                                 std::move(fGraphemeBreaksUtf8),
+                                                 std::move(fLineBreaksUtf8));
+#endif
+    SkASSERT(fUnicode);
     return std::make_unique<ParagraphImpl>(
             fUtf8, fParagraphStyle, fStyledBlocks, fPlaceholders, fFontCollection, fUnicode);
 }
@@ -161,65 +178,123 @@
     return fParagraphStyle;
 }
 
-std::unique_ptr<Paragraph> ParagraphBuilderImpl::BuildWithClientInfo(
-                std::vector<SkUnicode::BidiRegion> bidiRegionsUtf16,
-                std::vector<SkUnicode::Position> wordsUtf16,
-                std::vector<SkUnicode::Position> graphemeBreaksUtf16,
-                std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf16) {
-#ifndef SK_UNICODE_CLIENT_IMPLEMENTATION
-    return nullptr;
-#else
-    SkSpan text = SkSpan<char>(fUtf8.isEmpty() ? nullptr : &fUtf8[0], fUtf8.size());
-
-    // TODO: This mapping is created twice. Here and in ParagraphImpl.cpp.
-    SkTArray<TextIndex, true> utf8IndexForUtf16Index;
-    SkUnicode::extractUtfConversionMapping(
-                text,
-                [&](size_t index) { utf8IndexForUtf16Index.emplace_back(index); },
+void ParagraphBuilderImpl::ensureUTF16Mapping() {
+    fillUTF16MappingOnce([&] {
+        SkUnicode::extractUtfConversionMapping(
+                this->getText(),
+                [&](size_t index) { fUTF8IndexForUTF16Index.emplace_back(index); },
                 [&](size_t index) {});
+    });
+}
 
+#if !defined(SK_UNICODE_ICU_IMPLEMENTATION) && defined(SK_UNICODE_CLIENT_IMPLEMENTATION)
+void ParagraphBuilderImpl::setBidiRegionsUtf8(std::vector<SkUnicode::BidiRegion> bidiRegionsUtf8) {
+    fUsingClientInfo = true;
+    fBidiRegionsUtf8 = std::move(bidiRegionsUtf8);
+}
+
+void ParagraphBuilderImpl::setBidiRegionsUtf16(std::vector<SkUnicode::BidiRegion> bidiRegionsUtf16) {
+    ensureUTF16Mapping();
     std::vector<SkUnicode::BidiRegion> bidiRegionsUtf8;
     for (SkUnicode::BidiRegion bidiRegionUtf16: bidiRegionsUtf16) {
         bidiRegionsUtf8.emplace_back(
-                SkUnicode::BidiRegion(utf8IndexForUtf16Index[bidiRegionUtf16.start],
-                                      utf8IndexForUtf16Index[bidiRegionUtf16.end],
+                SkUnicode::BidiRegion(fUTF8IndexForUTF16Index[bidiRegionUtf16.start],
+                                      fUTF8IndexForUTF16Index[bidiRegionUtf16.end],
                                       bidiRegionUtf16.level));
     }
+    setBidiRegionsUtf8(bidiRegionsUtf8);
+}
 
+void ParagraphBuilderImpl::setWordsUtf8(std::vector<SkUnicode::Position> wordsUtf8) {
+    fUsingClientInfo = true;
+    fWordsUtf8 = std::move(wordsUtf8);
+}
+
+void ParagraphBuilderImpl::setWordsUtf16(std::vector<SkUnicode::Position> wordsUtf16) {
+    ensureUTF16Mapping();
     std::vector<SkUnicode::Position> wordsUtf8;
     for (SkUnicode::Position indexUtf16: wordsUtf16) {
-        wordsUtf8.emplace_back(utf8IndexForUtf16Index[indexUtf16]);
+        wordsUtf8.emplace_back(fUTF8IndexForUTF16Index[indexUtf16]);
     }
+    setWordsUtf8(wordsUtf8);
+}
 
+void ParagraphBuilderImpl::setGraphemeBreaksUtf8(std::vector<SkUnicode::Position> graphemeBreaksUtf8) {
+    fUsingClientInfo = true;
+    fGraphemeBreaksUtf8 = std::move(graphemeBreaksUtf8);
+}
+
+void ParagraphBuilderImpl::setGraphemeBreaksUtf16(std::vector<SkUnicode::Position> graphemeBreaksUtf16) {
+    ensureUTF16Mapping();
     std::vector<SkUnicode::Position> graphemeBreaksUtf8;
     for (SkUnicode::Position indexUtf16: graphemeBreaksUtf16) {
-        graphemeBreaksUtf8.emplace_back(utf8IndexForUtf16Index[indexUtf16]);
+        graphemeBreaksUtf8.emplace_back(fUTF8IndexForUTF16Index[indexUtf16]);
     }
+    setGraphemeBreaksUtf8(graphemeBreaksUtf8);
+}
 
+void ParagraphBuilderImpl::setLineBreaksUtf8(std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf8) {
+    fUsingClientInfo = true;
+    fLineBreaksUtf8 = std::move(lineBreaksUtf8);
+}
+
+void ParagraphBuilderImpl::setLineBreaksUtf16(std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf16) {
+    ensureUTF16Mapping();
     std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf8;
     for (SkUnicode::LineBreakBefore lineBreakUtf16: lineBreaksUtf16) {
         lineBreaksUtf8.emplace_back(SkUnicode::LineBreakBefore(
-                utf8IndexForUtf16Index[lineBreakUtf16.pos], lineBreakUtf16.breakType));
+                fUTF8IndexForUTF16Index[lineBreakUtf16.pos], lineBreakUtf16.breakType));
     }
-
-    utf8IndexForUtf16Index.clear();
-
-    // This is the place where SkUnicode is paired with SkParagraph
-    fUnicode = SkUnicode::MakeClientBasedUnicode(text,
-                                std::move(bidiRegionsUtf8),
-                                std::move(wordsUtf8),
-                                std::move(graphemeBreaksUtf8),
-                                std::move(lineBreaksUtf8));
-    return this->Build();
-#endif
+    setLineBreaksUtf8(lineBreaksUtf8);
+}
+#else
+void ParagraphBuilderImpl::setBidiRegionsUtf8(std::vector<SkUnicode::BidiRegion> bidiRegionsUtf8) {
+    SkASSERT(false);
 }
 
+void ParagraphBuilderImpl::setBidiRegionsUtf16(std::vector<SkUnicode::BidiRegion> bidiRegionsUtf16) {
+    SkASSERT(false);
+}
+
+void ParagraphBuilderImpl::setWordsUtf8(std::vector<SkUnicode::Position> wordsUtf8) {
+    SkASSERT(false);
+}
+
+void ParagraphBuilderImpl::setWordsUtf16(std::vector<SkUnicode::Position> wordsUtf16) {
+    SkASSERT(false);
+}
+
+void ParagraphBuilderImpl::setGraphemeBreaksUtf8(std::vector<SkUnicode::Position> graphemesUtf8) {
+    SkASSERT(false);
+}
+
+void ParagraphBuilderImpl::setGraphemeBreaksUtf16(std::vector<SkUnicode::Position> graphemesUtf16) {
+    SkASSERT(false);
+}
+
+void ParagraphBuilderImpl::setLineBreaksUtf8(std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf8) {
+    SkASSERT(false);
+}
+
+void ParagraphBuilderImpl::setLineBreaksUtf16(std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf16) {
+    SkASSERT(false);
+}
+#endif
+
 void ParagraphBuilderImpl::Reset() {
+
     fTextStyles.clear();
     fUtf8.reset();
     fStyledBlocks.clear();
     fPlaceholders.clear();
 
+#if !defined(SK_UNICODE_ICU_IMPLEMENTATION) && defined(SK_UNICODE_CLIENT_IMPLEMENTATION)
+    fUTF8IndexForUTF16Index.clear();
+    fBidiRegionsUtf8.clear();
+    fWordsUtf8.clear();
+    fGraphemeBreaksUtf8.clear();
+    fLineBreaksUtf8.clear();
+#endif
     startStyledBlock();
 }
 
diff --git a/modules/skparagraph/src/ParagraphBuilderImpl.h b/modules/skparagraph/src/ParagraphBuilderImpl.h
index 1b71b63..10273c2 100644
--- a/modules/skparagraph/src/ParagraphBuilderImpl.h
+++ b/modules/skparagraph/src/ParagraphBuilderImpl.h
@@ -6,6 +6,8 @@
 #include <stack>
 #include <string>
 #include <tuple>
+#include "include/private/base/SkOnce.h"
+#include "include/private/base/SkTArray.h"
 #include "modules/skparagraph/include/FontCollection.h"
 #include "modules/skparagraph/include/Paragraph.h"
 #include "modules/skparagraph/include/ParagraphBuilder.h"
@@ -62,11 +64,19 @@
     // Support for "Client" unicode
     SkSpan<char> getText();
     const ParagraphStyle& getParagraphStyle() const;
-    std::unique_ptr<Paragraph> BuildWithClientInfo(
-                    std::vector<SkUnicode::BidiRegion> bidiRegions,
-                    std::vector<SkUnicode::Position> words,
-                    std::vector<SkUnicode::Position> graphemeBreaks,
-                    std::vector<SkUnicode::LineBreakBefore> lineBreaks);
+
+    void setBidiRegionsUtf8(std::vector<SkUnicode::BidiRegion> bidiRegionsUtf8);
+    void setBidiRegionsUtf16(std::vector<SkUnicode::BidiRegion> bidiRegionsUtf16);
+
+    void setWordsUtf8(std::vector<SkUnicode::Position> wordsUtf8);
+    void setWordsUtf16(std::vector<SkUnicode::Position> wordsUtf16);
+
+    void setGraphemeBreaksUtf8(std::vector<SkUnicode::Position> graphemesUtf8);
+    void setGraphemeBreaksUtf16(std::vector<SkUnicode::Position> graphemesUtf16);
+
+    void setLineBreaksUtf8(std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf8);
+    void setLineBreaksUtf16(std::vector<SkUnicode::LineBreakBefore> lineBreaksUtf16);
+
     void SetUnicode(std::unique_ptr<SkUnicode> unicode) {
         fUnicode = std::move(unicode);
     }
@@ -94,6 +104,18 @@
     ParagraphStyle fParagraphStyle;
 
     std::shared_ptr<SkUnicode> fUnicode;
+
+private:
+    SkOnce fillUTF16MappingOnce;
+    void ensureUTF16Mapping();
+    SkTArray<TextIndex, true> fUTF8IndexForUTF16Index;
+#if !defined(SK_UNICODE_ICU_IMPLEMENTATION) && defined(SK_UNICODE_CLIENT_IMPLEMENTATION)
+    bool fUsingClientInfo;
+    std::vector<SkUnicode::BidiRegion> fBidiRegionsUtf8;
+    std::vector<SkUnicode::Position> fWordsUtf8;
+    std::vector<SkUnicode::Position> fGraphemeBreaksUtf8;
+    std::vector<SkUnicode::LineBreakBefore> fLineBreaksUtf8;
+#endif
 };
 }  // namespace textlayout
 }  // namespace skia