Include MinikinPaint into cache key

MeasruedText::buildLayout can not be used for acquiring bounding box since
the input range can across the style boundary. To make things correctly,
include MinikinPaint as the key of the LayoutPiece. This is totally same as the
LayoutCacheKey in LayoutCache, so reuses LayoutCacheKey instead of self defined
key class in LayoutPiece.
This increases the memory usage and ideally run index can be used for layout cache
key instead of full MinikinPaint, but let me make thing correct first.

Here is a raw performance result.

android.text.PrecomputedTextMemoryUsageTest:
  MemoryUsage
    Hyphenation                      :     50,873 ->     59,721: (+17.4%)
    Hyphenation WidthOnly            :      8,856 ->      8,856: (+0.0%)
    NoHyphenation                    :     26,386 ->     29,242: (+10.8%)
    NoHyphenation WidthOnly          :      8,000 ->      8,000: (+0.0%)

android.text.PrecomputedTextPerfTest:
  create
    NoStyle Hyphenation              : 18,378,988 -> 18,332,327: (-0.3%)
    NoStyle Hyphenation WidthOnly    : 18,332,392 -> 18,397,337: (+0.4%)
    NoStyle NoHyphenation            :  7,385,258 ->  7,390,699: (+0.1%)
    NoStyle NoHyphenation WidthOnly  :  7,403,445 ->  7,388,476: (-0.2%)
    Style Hyphenation                : 12,637,464 -> 12,624,799: (-0.1%)
    Style Hyphenation WidthOnly      : 12,667,559 -> 12,642,056: (-0.2%)
    Style NoHyphenation              : 12,348,519 -> 12,241,291: (-0.9%)
    Style NoHyphenation WidthOnly    : 12,325,515 -> 12,317,746: (-0.1%)

android.text.StaticLayoutPerfTest:
  create
    PrecomputedText
      Balanced Hyphenation           :    691,388 ->    680,137: (-1.6%)
      Balanced NoHyphenation         :    502,038 ->    495,980: (-1.2%)
      Greedy Hyphenation             :    451,619 ->    446,380: (-1.2%)
      Greedy NoHyphenation           :    449,011 ->    444,621: (-1.0%)
    RandomText
      Balanced Hyphenation           : 17,639,029 -> 17,609,190: (-0.2%)
      Balanced NoHyphenation         :  7,295,497 ->  7,251,221: (-0.6%)
      Greedy Hyphenation             :  7,268,452 ->  7,201,506: (-0.9%)
      Greedy NoHyphenation           :  7,215,397 ->  7,225,217: (+0.1%)
  draw
    PrecomputedText
      NoStyle                        :    588,349 ->    620,041: (+5.4%)
      NoStyle WithoutCache           :    613,312 ->    645,161: (+5.2%)
      Style                          :    911,309 ->    938,200: (+3.0%)
      Style WithoutCache             :    920,240 ->    955,410: (+3.8%)
    RandomText
      NoStyle                        :    542,517 ->    555,951: (+2.5%)
      NoStyle WithoutCache           :  6,747,436 ->  6,723,770: (-0.4%)
      Style                          :  1,022,591 ->  1,034,170: (+1.1%)
      Style WithoutCache             :  2,862,071 ->  2,835,226: (-0.9%)

android.widget.TextViewPrecomputedTextPerfTest:
  newLayout
    PrecomputedText                  :    791,018 ->    785,320: (-0.7%)
    PrecomputedText Selectable       :  1,569,428 ->  1,190,267: (-24.2%)
    RandomText                       : 17,146,396 -> 17,064,908: (-0.5%)
    RandomText Selectable            : 18,239,348 -> 18,225,575: (-0.1%)
  onDraw
    PrecomputedText                  :  1,263,842 ->  1,286,294: (+1.8%)
    PrecomputedText Selectable       :  1,380,186 ->  1,303,995: (-5.5%)
    RandomText                       : 17,734,725 -> 17,823,735: (+0.5%)
    RandomText Selectable            : 18,549,828 -> 18,610,101: (+0.3%)
  onMeasure
    PrecomputedText                  :    799,962 ->    809,164: (+1.2%)
    PrecomputedText Selectable       :  1,747,993 ->  1,358,504: (-22.3%)
    RandomText                       : 17,155,624 -> 17,087,524: (-0.4%)
    RandomText Selectable            : 18,435,408 -> 18,515,944: (+0.4%)
  setText
    PrecomputedText                  :    136,250 ->    133,602: (-1.9%)
    PrecomputedText Selectable       :    213,980 ->    205,610: (-3.9%)
    RandomText                       :     16,512 ->     16,599: (+0.5%)
    RandomText Selectable            :     56,143 ->     56,239: (+0.2%)

Bug: 77495049
Test: atest CtsWidgetTestCases:EditTextTest
    CtsWidgetTestCases:TextViewFadingEdgeTest
    FrameworksCoreTests:TextViewFallbackLineSpacingTest
    FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
    CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
    CtsTextTestCases FrameworksCoreTests:android.text
    CtsWidgetTestCases:TextViewPrecomputedTextTest

Change-Id: I20d7cdd2f11960e334a1f2cd816679bb8f84e6cb
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h
index 0676c8c..ba32d98 100644
--- a/include/minikin/Layout.h
+++ b/include/minikin/Layout.h
@@ -92,6 +92,11 @@
                                        Bidi bidiFlags, const MinikinPaint& paint,
                                        StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
                                        const LayoutPieces& pieces);
+    static std::pair<float, MinikinRect> getBoundsWithPrecomputedPieces(const U16StringPiece& str,
+                                                                        const Range& range,
+                                                                        Bidi bidiFlags,
+                                                                        const MinikinPaint& paint,
+                                                                        const LayoutPieces& pieces);
 
     static float measureText(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
                              const MinikinPaint& paint, StartHyphenEdit startHyphen,
@@ -123,6 +128,7 @@
     float getCharAdvance(size_t i) const { return mAdvances[i]; }
 
     void getBounds(MinikinRect* rect) const;
+    const MinikinRect& getBounds() const { return mBounds; }
 
     // Purge all caches, useful in low memory conditions
     static void purgeCaches();
@@ -158,14 +164,15 @@
                                    const MinikinPaint& paint, size_t dstStart,
                                    StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
                                    const LayoutPieces* lpIn, Layout* layout, float* advances,
-                                   MinikinExtent* extents, LayoutPieces* lpOut);
+                                   MinikinExtent* extents, MinikinRect* bounds,
+                                   LayoutPieces* lpOut);
 
     // Lay out a single word
     static float doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
                               bool isRtl, const MinikinPaint& paint, size_t bufStart,
                               StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
                               const LayoutPieces* lpIn, Layout* layout, float* advances,
-                              MinikinExtent* extents, LayoutPieces* lpOut);
+                              MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut);
 
     // Lay out a single bidi run
     void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize, bool isRtl,
@@ -184,73 +191,6 @@
     MinikinRect mBounds;
 };
 
-struct LayoutPieces {
-    struct Key {
-        Key(const U16StringPiece& textBuf, const Range& range, HyphenEdit edit)
-                : text(textBuf.data()), length(textBuf.length()), range(range), hyphenEdit(edit) {}
-
-        void makePersistent() {
-            uint16_t* copied = new uint16_t[length];
-            std::copy(text, text + length, copied);
-            text = copied;
-        }
-
-        inline bool operator==(const Key& o) const {
-            return length == o.length && hyphenEdit == o.hyphenEdit && range == o.range &&
-                   (text == o.text || memcmp(text, o.text, sizeof(uint16_t) * length) == 0);
-        }
-
-        const uint16_t* text;
-        const uint32_t length;
-        Range range;
-        HyphenEdit hyphenEdit;
-    };
-
-    struct KeyHasher {
-        std::size_t operator()(const Key& key) const {
-            uint32_t hash = android::JenkinsHashMix(0, static_cast<uint8_t>(key.hyphenEdit));
-            hash = android::JenkinsHashMix(hash, key.range.getStart());
-            hash = android::JenkinsHashMix(hash, key.range.getEnd());
-            hash = android::JenkinsHashMixShorts(hash, key.text, key.length);
-            return android::JenkinsHashWhiten(hash);
-        }
-    };
-
-    LayoutPieces() {}
-
-    ~LayoutPieces() {
-        for (const auto it : offsetMap) {
-            delete[] it.first.text;
-        }
-    }
-
-    std::unordered_map<Key, Layout, KeyHasher> offsetMap;
-
-    void insert(const U16StringPiece& textBuf, const Range& range, HyphenEdit edit,
-                const Layout& layout) {
-        Key key(textBuf, range, edit);
-        key.makePersistent();
-        offsetMap.insert(std::make_pair(key, layout));
-    }
-
-    const Layout* get(const U16StringPiece& textBuf, const Range& range, HyphenEdit edit) const {
-        auto it = offsetMap.find(Key(textBuf, range, edit));
-        if (it == offsetMap.end()) {
-            return nullptr;
-        }
-        return &it->second;
-    }
-
-    uint32_t getMemoryUsage() const {
-        uint32_t result = 0;
-        for (const auto& i : offsetMap) {
-            result += sizeof(Key) + sizeof(uint16_t) * i.first.length;
-            result += i.second.getMemoryUsage();
-        }
-        return result;
-    }
-};
-
 }  // namespace minikin
 
 #endif  // MINIKIN_LAYOUT_H
diff --git a/libs/minikin/LayoutCache.h b/include/minikin/LayoutCache.h
similarity index 98%
rename from libs/minikin/LayoutCache.h
rename to include/minikin/LayoutCache.h
index 5b87fcc..e99fbe4 100644
--- a/libs/minikin/LayoutCache.h
+++ b/include/minikin/LayoutCache.h
@@ -24,8 +24,6 @@
 #include <utils/JenkinsHash.h>
 #include <utils/LruCache.h>
 
-#include "LocaleListCache.h"
-
 namespace minikin {
 
 // Layout cache datatypes
@@ -81,6 +79,8 @@
                             mEndHyphen);
     }
 
+    uint32_t getMemoryUsage() const { return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mNchars; }
+
 private:
     const uint16_t* mChars;
     size_t mNchars;
diff --git a/include/minikin/LayoutPieces.h b/include/minikin/LayoutPieces.h
new file mode 100644
index 0000000..f581372
--- /dev/null
+++ b/include/minikin/LayoutPieces.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LAYOUT_PIECES_H
+#define MINIKIN_LAYOUT_PIECES_H
+
+#include <unordered_map>
+
+#include "minikin/Layout.h"
+#include "minikin/LayoutCache.h"
+
+namespace minikin {
+
+struct LayoutPieces {
+    struct KeyHasher {
+        std::size_t operator()(const LayoutCacheKey& key) const { return key.hash(); }
+    };
+
+    LayoutPieces() {}
+
+    ~LayoutPieces() {
+        for (const auto it : offsetMap) {
+            const_cast<LayoutCacheKey*>(&it.first)->freeText();
+        }
+    }
+
+    std::unordered_map<LayoutCacheKey, Layout, KeyHasher> offsetMap;
+
+    void insert(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
+                bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, const Layout& layout) {
+        auto result = offsetMap.emplace(
+                std::piecewise_construct,
+                std::forward_as_tuple(textBuf, range, paint, dir, startEdit, endEdit),
+                std::forward_as_tuple(layout));
+        if (result.second) {
+            const_cast<LayoutCacheKey*>(&result.first->first)->copyText();
+        }
+    }
+
+    template <typename F>
+    void getOrCreate(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
+                     bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, F& f) const {
+        auto it = offsetMap.find(LayoutCacheKey(textBuf, range, paint, dir, startEdit, endEdit));
+        if (it == offsetMap.end()) {
+            LayoutCache::getInstance().getOrCreate(textBuf, range, paint, dir, startEdit, endEdit,
+                                                   f);
+        } else {
+            f(it->second);
+        }
+    }
+
+    uint32_t getMemoryUsage() const {
+        uint32_t result = 0;
+        for (const auto& i : offsetMap) {
+            result += i.first.getMemoryUsage() + i.second.getMemoryUsage();
+        }
+        return result;
+    }
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_LAYOUT_PIECES_H
diff --git a/include/minikin/MeasuredText.h b/include/minikin/MeasuredText.h
index 474cdf3..d33a1ca 100644
--- a/include/minikin/MeasuredText.h
+++ b/include/minikin/MeasuredText.h
@@ -22,6 +22,7 @@
 
 #include "minikin/FontCollection.h"
 #include "minikin/Layout.h"
+#include "minikin/LayoutPieces.h"
 #include "minikin/Macros.h"
 #include "minikin/MinikinFont.h"
 #include "minikin/Range.h"
@@ -47,6 +48,9 @@
     virtual void getMetrics(const U16StringPiece& text, float* advances, MinikinExtent* extents,
                             LayoutPieces* piece) const = 0;
 
+    virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
+                                                    const LayoutPieces& pieces) const = 0;
+
     // Following two methods are only called when the implementation returns true for
     // canHyphenate method.
 
@@ -85,6 +89,12 @@
                             EndHyphenEdit::NO_EDIT, advances, extents, pieces);
     }
 
+    std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
+                                            const LayoutPieces& pieces) const override {
+        Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+        return Layout::getBoundsWithPrecomputedPieces(text, range, bidiFlag, mPaint, pieces);
+    }
+
     const MinikinPaint* getPaint() const override { return &mPaint; }
 
     float measureHyphenPiece(const U16StringPiece& text, const Range& range,
@@ -115,6 +125,13 @@
         // TODO: Get the extents information from the caller.
     }
 
+    std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
+                                            const Range& /* range */,
+                                            const LayoutPieces& /* pieces */) const override {
+        // Bounding Box is not used in replacement run.
+        return std::make_pair(mWidth, MinikinRect());
+    }
+
 private:
     const float mWidth;
     const uint32_t mLocaleListId;
@@ -165,6 +182,7 @@
     void buildLayout(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
                      Bidi bidiFlag, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
                      Layout* layout);
+    MinikinRect getBounds(const U16StringPiece& textBuf, const Range& range);
 
     MeasuredText(MeasuredText&&) = default;
     MeasuredText& operator=(MeasuredText&&) = default;
diff --git a/include/minikin/Range.h b/include/minikin/Range.h
index 2efde7e..4c7fbfa 100644
--- a/include/minikin/Range.h
+++ b/include/minikin/Range.h
@@ -73,6 +73,9 @@
         return left.isValid() && right.isValid() && left.mStart < right.mEnd &&
                right.mStart < left.mEnd;
     }
+    inline static Range intersection(const Range& left, const Range& right) {
+        return Range(std::max(left.mStart, right.mStart), std::min(left.mEnd, right.mEnd));
+    }
 
     // Returns merged range. This method assumes left and right are not invalid ranges and they have
     // an intersection.
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index 4383d60..1b05274 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -34,10 +34,11 @@
 
 #include "minikin/Emoji.h"
 #include "minikin/HbUtils.h"
+#include "minikin/LayoutCache.h"
+#include "minikin/LayoutPieces.h"
 #include "minikin/Macros.h"
 
 #include "BidiUtils.h"
-#include "LayoutCache.h"
 #include "LayoutUtils.h"
 #include "LocaleListCache.h"
 #include "MinikinInternal.h"
@@ -234,7 +235,8 @@
 
     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
         doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
-                          startHyphen, endHyphen, nullptr, this, nullptr, nullptr, nullptr);
+                          startHyphen, endHyphen, nullptr, this, nullptr, nullptr, nullptr,
+                          nullptr);
     }
 }
 
@@ -249,10 +251,25 @@
 
     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
         doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
-                          startHyphen, endHyphen, &lpIn, this, nullptr, nullptr, nullptr);
+                          startHyphen, endHyphen, &lpIn, this, nullptr, nullptr, nullptr, nullptr);
     }
 }
 
+std::pair<float, MinikinRect> Layout::getBoundsWithPrecomputedPieces(const U16StringPiece& textBuf,
+                                                                     const Range& range,
+                                                                     Bidi bidiFlags,
+                                                                     const MinikinPaint& paint,
+                                                                     const LayoutPieces& pieces) {
+    MinikinRect rect;
+    float advance = 0;
+    for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
+        advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0,
+                                     StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, &pieces,
+                                     nullptr, nullptr, nullptr, &rect, nullptr);
+    }
+    return std::make_pair(advance, rect);
+}
+
 float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
                           const MinikinPaint& paint, StartHyphenEdit startHyphen,
                           EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
@@ -264,7 +281,7 @@
         MinikinExtent* extentsForRun = extents ? extents + offset : nullptr;
         advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0, startHyphen,
                                      endHyphen, nullptr, nullptr, advancesForRun, extentsForRun,
-                                     pieces);
+                                     nullptr, pieces);
     }
     return advance;
 }
@@ -273,7 +290,7 @@
                                 const MinikinPaint& paint, size_t dstStart,
                                 StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
                                 const LayoutPieces* lpIn, Layout* layout, float* advances,
-                                MinikinExtent* extents, LayoutPieces* lpOut) {
+                                MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
     if (!range.isValid()) {
         return 0.0f;  // ICU failed to retrieve the bidi run?
     }
@@ -297,7 +314,7 @@
                                     iter == start ? startHyphen : StartHyphenEdit::NO_EDIT,
                                     wordend >= end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn,
                                     layout, advances ? advances + offset : nullptr,
-                                    extents ? extents + offset : nullptr, lpOut);
+                                    extents ? extents + offset : nullptr, bounds, lpOut);
             wordstart = wordend;
         }
     } else {
@@ -315,7 +332,7 @@
                                     wordstart <= start ? startHyphen : StartHyphenEdit::NO_EDIT,
                                     iter == end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn, layout,
                                     advances ? advances + offset : nullptr,
-                                    extents ? extents + offset : nullptr, lpOut);
+                                    extents ? extents + offset : nullptr, bounds, lpOut);
             wordend = wordstart;
         }
     }
@@ -324,18 +341,23 @@
 
 class LayoutAppendFunctor {
 public:
-    LayoutAppendFunctor(const U16StringPiece& textBuf, const Range& range, HyphenEdit hyphenEdit,
-                        Layout* layout, float* advances, MinikinExtent* extents,
-                        LayoutPieces* pieces, float* totalAdvance, uint32_t outOffset,
-                        float wordSpacing)
+    LayoutAppendFunctor(const U16StringPiece& textBuf, const Range& range,
+                        const MinikinPaint& paint, bool dir, StartHyphenEdit startEdit,
+                        EndHyphenEdit endEdit, Layout* layout, float* advances,
+                        MinikinExtent* extents, LayoutPieces* pieces, float* totalAdvance,
+                        MinikinRect* bounds, uint32_t outOffset, float wordSpacing)
             : mTextBuf(textBuf),
               mRange(range),
-              mHyphenEdit(hyphenEdit),
+              mPaint(paint),
+              mDir(dir),
+              mStartEdit(startEdit),
+              mEndEdit(endEdit),
               mLayout(layout),
               mAdvances(advances),
               mExtents(extents),
               mPieces(pieces),
               mTotalAdvance(totalAdvance),
+              mBounds(bounds),
               mOutOffset(outOffset),
               mWordSpacing(wordSpacing) {}
 
@@ -352,20 +374,27 @@
         if (mExtents) {
             layout.getExtents(mExtents);
         }
+        if (mBounds) {
+            mBounds->join(layout.getBounds());
+        }
         if (mPieces) {
-            mPieces->insert(mTextBuf, mRange, mHyphenEdit, layout);
+            mPieces->insert(mTextBuf, mRange, mPaint, mDir, mStartEdit, mEndEdit, layout);
         }
     }
 
 private:
     const U16StringPiece& mTextBuf;
     const Range& mRange;
-    HyphenEdit mHyphenEdit;
+    const MinikinPaint& mPaint;
+    bool mDir;
+    StartHyphenEdit mStartEdit;
+    EndHyphenEdit mEndEdit;
     Layout* mLayout;
     float* mAdvances;
     MinikinExtent* mExtents;
     LayoutPieces* mPieces;
     float* mTotalAdvance;
+    MinikinRect* mBounds;
     const uint32_t mOutOffset;
     const float mWordSpacing;
 };
@@ -374,22 +403,16 @@
                            bool isRtl, const MinikinPaint& paint, size_t bufStart,
                            StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
                            const LayoutPieces* lpIn, Layout* layout, float* advances,
-                           MinikinExtent* extents, LayoutPieces* lpOut) {
+                           MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
     float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
     float totalAdvance;
 
     const U16StringPiece textBuf(buf, bufSize);
     const Range range(start, start + count);
-    HyphenEdit hyphenEdit = packHyphenEdit(startHyphen, endHyphen);
-
-    LayoutAppendFunctor f(textBuf, range, hyphenEdit, layout, advances, extents, lpOut,
-                          &totalAdvance, bufStart, wordSpacing);
-
-    const Layout* layoutInPieces =
-            lpIn == nullptr ? nullptr : lpIn->get(textBuf, range, hyphenEdit);
-
-    if (layoutInPieces != nullptr) {
-        f(*layoutInPieces);
+    LayoutAppendFunctor f(textBuf, range, paint, isRtl, startHyphen, endHyphen, layout, advances,
+                          extents, lpOut, &totalAdvance, bounds, bufStart, wordSpacing);
+    if (lpIn != nullptr) {
+        lpIn->getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen, f);
     } else {
         LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
                                                f);
diff --git a/libs/minikin/MeasuredText.cpp b/libs/minikin/MeasuredText.cpp
index 7be949b..bbc6091 100644
--- a/libs/minikin/MeasuredText.cpp
+++ b/libs/minikin/MeasuredText.cpp
@@ -63,4 +63,22 @@
                                           layoutPieces);
 }
 
+MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) {
+    MinikinRect rect;
+    float advance = 0.0f;
+    for (const auto& run : runs) {
+        const Range& runRange = run->getRange();
+        if (!Range::intersects(range, runRange)) {
+            continue;
+        }
+        std::pair<float, MinikinRect> next =
+                run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
+        MinikinRect nextRect = next.second;
+        nextRect.offset(advance, 0);
+        rect.join(nextRect);
+        advance += next.first;
+    }
+    return rect;
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/LayoutCacheTest.cpp b/tests/unittest/LayoutCacheTest.cpp
index 8235556..b7ad0ba 100644
--- a/tests/unittest/LayoutCacheTest.cpp
+++ b/tests/unittest/LayoutCacheTest.cpp
@@ -18,8 +18,10 @@
 
 #include <gtest/gtest.h>
 
+#include "minikin/LayoutCache.h"
+
 #include "FontTestUtils.h"
-#include "LayoutCache.h"
+#include "LocaleListCache.h"
 #include "UnicodeUtils.h"
 
 namespace minikin {
diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp
index 830dc92..21109cb 100644
--- a/tests/unittest/LayoutTest.cpp
+++ b/tests/unittest/LayoutTest.cpp
@@ -19,6 +19,7 @@
 #include <gtest/gtest.h>
 
 #include "minikin/FontCollection.h"
+#include "minikin/LayoutPieces.h"
 
 #include "FontTestUtils.h"
 #include "UnicodeUtils.h"
@@ -554,12 +555,14 @@
     float MARKER1 = 1e+16;
     float MARKER2 = 1e+17;
     auto fc = buildFontCollection("LayoutTestFont.ttf");
+    MinikinPaint paint(fc);
     {
         LayoutPieces pieces;
 
         Layout inLayout = doLayout("I", MinikinPaint(fc));
         inLayout.mAdvances[0] = MARKER1;  // Modify the advance to make sure this layout is used.
-        pieces.insert(utf8ToUtf16("I"), Range(0, 1), 0 /* hyphen edit */, inLayout);
+        pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
 
         Layout outLayout = doLayoutWithPrecomputedPieces("I", MinikinPaint(fc), pieces);
         EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
@@ -569,7 +572,8 @@
 
         Layout inLayout = doLayout("I", MinikinPaint(fc));
         inLayout.mAdvances[0] = MARKER1;
-        pieces.insert(utf8ToUtf16("I"), Range(0, 1), 0 /* hyphen edit */, inLayout);
+        pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
 
         Layout outLayout = doLayoutWithPrecomputedPieces("II", MinikinPaint(fc), pieces);
         // The layout pieces are used in word units. Should not be used "I" for "II".
@@ -581,7 +585,8 @@
 
         Layout inLayout = doLayout("I", MinikinPaint(fc));
         inLayout.mAdvances[0] = MARKER1;
-        pieces.insert(utf8ToUtf16("I"), Range(0, 1), 0 /* hyphen edit */, inLayout);
+        pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
 
         Layout outLayout = doLayoutWithPrecomputedPieces("I I", MinikinPaint(fc), pieces);
         EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
@@ -592,11 +597,13 @@
 
         Layout inLayout = doLayout("I", MinikinPaint(fc));
         inLayout.mAdvances[0] = MARKER1;  // Modify the advance to make sure this layout is used.
-        pieces.insert(utf8ToUtf16("I"), Range(0, 1), 0 /* hyphen edit */, inLayout);
+        pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
 
         inLayout = doLayout("V", MinikinPaint(fc));
         inLayout.mAdvances[0] = MARKER2;  // Modify the advance to make sure this layout is used.
-        pieces.insert(utf8ToUtf16("V"), Range(0, 1), 0 /* hyphen edit */, inLayout);
+        pieces.insert(utf8ToUtf16("V"), Range(0, 1), paint, false /* dir */,
+                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
 
         Layout outLayout = doLayoutWithPrecomputedPieces("I V", MinikinPaint(fc), pieces);
         EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
diff --git a/tests/unittest/LineBreakerTestHelper.h b/tests/unittest/LineBreakerTestHelper.h
index d6ab740..70d6521 100644
--- a/tests/unittest/LineBreakerTestHelper.h
+++ b/tests/unittest/LineBreakerTestHelper.h
@@ -57,6 +57,12 @@
         std::fill(advances, advances + mRange.getLength(), mWidth);
     }
 
+    virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
+                                                    const Range& /* range */,
+                                                    const LayoutPieces& /* pieces */) const {
+        return std::make_pair(mWidth, MinikinRect());
+    }
+
     virtual const MinikinPaint* getPaint() const { return &mPaint; }
 
     virtual float measureHyphenPiece(const U16StringPiece&, const Range& range,