DO NOT MERGE - Merge Android 10 into master

Bug: 139893257
Change-Id: Ia5e3d32375c7e7228d46477e9269958d8f5fbdfc
diff --git a/Android.bp b/Android.bp
index e875a17..adbd4e6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2,6 +2,11 @@
     name: "libminikin_headers",
     host_supported: true,
     export_include_dirs: ["include"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
 
 subdirs = [
diff --git a/include/minikin/AndroidLineBreakerHelper.h b/include/minikin/AndroidLineBreakerHelper.h
index 462644d..302d2b1 100644
--- a/include/minikin/AndroidLineBreakerHelper.h
+++ b/include/minikin/AndroidLineBreakerHelper.h
@@ -27,15 +27,12 @@
 class AndroidLineWidth : public LineWidth {
 public:
     AndroidLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
-                     const std::vector<float>& indents, const std::vector<float>& leftPaddings,
-                     const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
+                     const std::vector<float>& indents, int32_t indentsOffset)
             : mFirstWidth(firstWidth),
               mFirstLineCount(firstLineCount),
               mRestWidth(restWidth),
               mIndents(indents),
-              mLeftPaddings(leftPaddings),
-              mRightPaddings(rightPaddings),
-              mOffset(indentsAndPaddingsOffset) {}
+              mOffset(indentsOffset) {}
 
     float getAt(size_t lineNo) const override {
         const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount) ? mFirstWidth : mRestWidth;
@@ -54,10 +51,6 @@
         return minWidth;
     }
 
-    float getLeftPaddingAt(size_t lineNo) const override { return get(mLeftPaddings, lineNo); }
-
-    float getRightPaddingAt(size_t lineNo) const override { return get(mRightPaddings, lineNo); }
-
 private:
     float get(const std::vector<float>& vec, size_t lineNo) const {
         if (vec.empty()) {
@@ -75,32 +68,27 @@
     const int32_t mFirstLineCount;
     const float mRestWidth;
     const std::vector<float>& mIndents;
-    const std::vector<float>& mLeftPaddings;
-    const std::vector<float>& mRightPaddings;
     const int32_t mOffset;
 };
 
 class StaticLayoutNative {
 public:
     StaticLayoutNative(BreakStrategy strategy, HyphenationFrequency frequency, bool isJustified,
-                       std::vector<float>&& indents, std::vector<float>&& leftPaddings,
-                       std::vector<float>&& rightPaddings)
+                       std::vector<float>&& indents)
             : mStrategy(strategy),
               mFrequency(frequency),
               mIsJustified(isJustified),
-              mIndents(std::move(indents)),
-              mLeftPaddings(std::move(leftPaddings)),
-              mRightPaddings(std::move(rightPaddings)) {}
+              mIndents(std::move(indents)) {}
 
     LineBreakResult computeBreaks(const U16StringPiece& textBuf, const MeasuredText& measuredText,
                                   // Line width arguments
                                   float firstWidth, int32_t firstWidthLineCount, float restWidth,
                                   int32_t indentsOffset,
                                   // Tab stop arguments
-                                  const int32_t* tabStops, int32_t tabStopSize,
-                                  int32_t defaultTabStopWidth) const {
+                                  const float* tabStops, int32_t tabStopSize,
+                                  float defaultTabStopWidth) const {
         AndroidLineWidth lineWidth(firstWidth, firstWidthLineCount, restWidth, mIndents,
-                                   mLeftPaddings, mRightPaddings, indentsOffset);
+                                   indentsOffset);
         return breakIntoLines(textBuf, mStrategy, mFrequency, mIsJustified, measuredText, lineWidth,
                               TabStops(tabStops, tabStopSize, defaultTabStopWidth));
     }
diff --git a/include/minikin/FamilyVariant.h b/include/minikin/FamilyVariant.h
new file mode 100644
index 0000000..1734a1f
--- /dev/null
+++ b/include/minikin/FamilyVariant.h
@@ -0,0 +1,33 @@
+/*
+ * 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_FAMILY_VARIANT_H
+#define MINIKIN_FAMILY_VARIANT_H
+
+#include <cstdint>
+
+namespace minikin {
+
+// Must be the same value as FontConfig.java
+enum class FamilyVariant : uint8_t {
+    DEFAULT = 0,  // Must be the same as FontConfig.VARIANT_DEFAULT
+    COMPACT = 1,  // Must be the same as FontConfig.VARIANT_COMPACT
+    ELEGANT = 2,  // Must be the same as FontConfig.VARIANT_ELEGANT
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_FAMILY_VARIANT_H
diff --git a/include/minikin/Font.h b/include/minikin/Font.h
new file mode 100644
index 0000000..eeb074e
--- /dev/null
+++ b/include/minikin/Font.h
@@ -0,0 +1,133 @@
+/*
+ * 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_FONT_H
+#define MINIKIN_FONT_H
+
+#include <memory>
+#include <unordered_set>
+
+#include "minikin/FontStyle.h"
+#include "minikin/FontVariation.h"
+#include "minikin/HbUtils.h"
+#include "minikin/Macros.h"
+#include "minikin/MinikinFont.h"
+
+namespace minikin {
+
+class Font;
+
+// attributes representing transforms (fake bold, fake italic) to match styles
+class FontFakery {
+public:
+    FontFakery() : mFakeBold(false), mFakeItalic(false) {}
+    FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) {}
+    // TODO: want to support graded fake bolding
+    bool isFakeBold() { return mFakeBold; }
+    bool isFakeItalic() { return mFakeItalic; }
+    inline bool operator==(const FontFakery& o) const {
+        return mFakeBold == o.mFakeBold && mFakeItalic == o.mFakeItalic;
+    }
+    inline bool operator!=(const FontFakery& o) const { return !(*this == o); }
+
+private:
+    bool mFakeBold;
+    bool mFakeItalic;
+};
+
+struct FakedFont {
+    inline bool operator==(const FakedFont& o) const {
+        return font == o.font && fakery == o.fakery;
+    }
+    inline bool operator!=(const FakedFont& o) const { return !(*this == o); }
+
+    // ownership is the enclosing FontCollection
+    const Font* font;
+    FontFakery fakery;
+};
+
+// Represents a single font file.
+class Font {
+public:
+    class Builder {
+    public:
+        Builder(const std::shared_ptr<MinikinFont>& typeface) : mTypeface(typeface) {}
+
+        // Override the font style. If not called, info from OS/2 table is used.
+        Builder& setStyle(FontStyle style) {
+            mWeight = style.weight();
+            mSlant = style.slant();
+            mIsWeightSet = mIsSlantSet = true;
+            return *this;
+        }
+
+        // Override the font weight. If not called, info from OS/2 table is used.
+        Builder& setWeight(uint16_t weight) {
+            mWeight = weight;
+            mIsWeightSet = true;
+            return *this;
+        }
+
+        // Override the font slant. If not called, info from OS/2 table is used.
+        Builder& setSlant(FontStyle::Slant slant) {
+            mSlant = slant;
+            mIsSlantSet = true;
+            return *this;
+        }
+
+        Font build();
+
+    private:
+        std::shared_ptr<MinikinFont> mTypeface;
+        uint16_t mWeight = static_cast<uint16_t>(FontStyle::Weight::NORMAL);
+        FontStyle::Slant mSlant = FontStyle::Slant::UPRIGHT;
+        bool mIsWeightSet = false;
+        bool mIsSlantSet = false;
+    };
+
+    Font(Font&& o) = default;
+    Font& operator=(Font&& o) = default;
+
+    Font& operator=(const Font& o) {
+        mTypeface = o.mTypeface;
+        mStyle = o.mStyle;
+        mBaseFont = HbFontUniquePtr(hb_font_reference(o.mBaseFont.get()));
+        return *this;
+    }
+    Font(const Font& o) { *this = o; }
+
+    inline const std::shared_ptr<MinikinFont>& typeface() const { return mTypeface; }
+    inline FontStyle style() const { return mStyle; }
+    inline const HbFontUniquePtr& baseFont() const { return mBaseFont; }
+
+    std::unordered_set<AxisTag> getSupportedAxes() const;
+
+private:
+    // Use Builder instead.
+    Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont)
+            : mTypeface(std::move(typeface)), mStyle(style), mBaseFont(std::move(baseFont)) {}
+
+    static HbFontUniquePtr prepareFont(const std::shared_ptr<MinikinFont>& typeface);
+    static FontStyle analyzeStyle(const HbFontUniquePtr& font);
+
+    std::shared_ptr<MinikinFont> mTypeface;
+    FontStyle mStyle;
+    HbFontUniquePtr mBaseFont;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_FONT_H
diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h
index 642bb6f..f136384 100644
--- a/include/minikin/FontCollection.h
+++ b/include/minikin/FontCollection.h
@@ -23,6 +23,7 @@
 
 #include "minikin/FontFamily.h"
 #include "minikin/MinikinFont.h"
+#include "minikin/U16StringPiece.h"
 
 namespace minikin {
 
@@ -40,8 +41,15 @@
         int end;
     };
 
-    void itemize(const uint16_t* string, size_t string_length, const MinikinPaint& paint,
-                 std::vector<Run>* result) const;
+    // Perform the itemization until given max runs.
+    std::vector<Run> itemize(U16StringPiece text, FontStyle style, uint32_t localeListId,
+                             FamilyVariant familyVariant, uint32_t runMax) const;
+
+    // Perform the itemization until end of the text.
+    std::vector<Run> itemize(U16StringPiece text, FontStyle style, uint32_t localeListId,
+                             FamilyVariant familyVariant) const {
+        return itemize(text, style, localeListId, familyVariant, text.size());
+    }
 
     // Returns true if there is a glyph for the code point and variation selector pair.
     // Returns false if no fonts have a glyph for the code point and variation
@@ -79,10 +87,9 @@
 
     const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch, uint32_t vs,
                                                         uint32_t localeListId,
-                                                        FontFamily::Variant variant) const;
+                                                        FamilyVariant variant) const;
 
-    uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
-                             uint32_t localeListId,
+    uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, FamilyVariant variant, uint32_t localeListId,
                              const std::shared_ptr<FontFamily>& fontFamily) const;
 
     uint32_t calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t localeListId,
@@ -91,8 +98,7 @@
     static uint32_t calcLocaleMatchingScore(uint32_t userLocaleListId,
                                             const FontFamily& fontFamily);
 
-    static uint32_t calcVariantMatchingScore(FontFamily::Variant variant,
-                                             const FontFamily& fontFamily);
+    static uint32_t calcVariantMatchingScore(FamilyVariant variant, const FontFamily& fontFamily);
 
     // unique id for this font collection (suitable for cache key)
     uint32_t mId;
diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h
index 66fe3a8..4aafaa0 100644
--- a/include/minikin/FontFamily.h
+++ b/include/minikin/FontFamily.h
@@ -22,6 +22,8 @@
 #include <unordered_set>
 #include <vector>
 
+#include "minikin/FamilyVariant.h"
+#include "minikin/Font.h"
 #include "minikin/FontStyle.h"
 #include "minikin/HbUtils.h"
 #include "minikin/Macros.h"
@@ -29,117 +31,17 @@
 
 namespace minikin {
 
-class Font;
-class MinikinFont;
-
-// attributes representing transforms (fake bold, fake italic) to match styles
-class FontFakery {
-public:
-    FontFakery() : mFakeBold(false), mFakeItalic(false) {}
-    FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) {}
-    // TODO: want to support graded fake bolding
-    bool isFakeBold() { return mFakeBold; }
-    bool isFakeItalic() { return mFakeItalic; }
-
-private:
-    bool mFakeBold;
-    bool mFakeItalic;
-};
-
-struct FakedFont {
-    // ownership is the enclosing FontCollection
-    const Font* font;
-    FontFakery fakery;
-};
-
-typedef uint32_t AxisTag;
-
-// Represents a single font file.
-class Font {
-public:
-    class Builder {
-    public:
-        Builder(const std::shared_ptr<MinikinFont>& typeface) : mTypeface(typeface) {}
-
-        // Override the font style. If not called, info from OS/2 table is used.
-        Builder& setStyle(FontStyle style) {
-            mWeight = style.weight();
-            mSlant = style.slant();
-            mIsWeightSet = mIsSlantSet = true;
-            return *this;
-        }
-
-        // Override the font weight. If not called, info from OS/2 table is used.
-        Builder& setWeight(uint16_t weight) {
-            mWeight = weight;
-            mIsWeightSet = true;
-            return *this;
-        }
-
-        // Override the font slant. If not called, info from OS/2 table is used.
-        Builder& setSlant(FontStyle::Slant slant) {
-            mSlant = slant;
-            mIsSlantSet = true;
-            return *this;
-        }
-
-        Font build();
-
-    private:
-        std::shared_ptr<MinikinFont> mTypeface;
-        uint16_t mWeight = static_cast<uint16_t>(FontStyle::Weight::NORMAL);
-        FontStyle::Slant mSlant = FontStyle::Slant::UPRIGHT;
-        bool mIsWeightSet = false;
-        bool mIsSlantSet = false;
-    };
-
-    Font(Font&& o) = default;
-    Font& operator=(Font&& o) = default;
-
-    inline const std::shared_ptr<MinikinFont>& typeface() const { return mTypeface; }
-    inline FontStyle style() const { return mStyle; }
-    inline const HbFontUniquePtr& baseFont() const { return mBaseFont; }
-
-    std::unordered_set<AxisTag> getSupportedAxes() const;
-
-private:
-    // Use Builder instead.
-    Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont)
-            : mTypeface(std::move(typeface)), mStyle(style), mBaseFont(std::move(baseFont)) {}
-
-    static HbFontUniquePtr prepareFont(const std::shared_ptr<MinikinFont>& typeface);
-    static FontStyle analyzeStyle(const HbFontUniquePtr& font);
-
-    std::shared_ptr<MinikinFont> mTypeface;
-    FontStyle mStyle;
-    HbFontUniquePtr mBaseFont;
-
-    MINIKIN_PREVENT_COPY_AND_ASSIGN(Font);
-};
-
-struct FontVariation {
-    FontVariation(AxisTag axisTag, float value) : axisTag(axisTag), value(value) {}
-    AxisTag axisTag;
-    float value;
-};
-
 class FontFamily {
 public:
-    // Must be the same value as FontConfig.java
-    enum class Variant : uint8_t {
-        DEFAULT = 0,  // Must be the same as FontConfig.VARIANT_DEFAULT
-        COMPACT = 1,  // Must be the same as FontConfig.VARIANT_COMPACT
-        ELEGANT = 2,  // Must be the same as FontConfig.VARIANT_ELEGANT
-    };
-
     explicit FontFamily(std::vector<Font>&& fonts);
-    FontFamily(Variant variant, std::vector<Font>&& fonts);
-    FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts);
+    FontFamily(FamilyVariant variant, std::vector<Font>&& fonts);
+    FontFamily(uint32_t localeListId, FamilyVariant variant, std::vector<Font>&& fonts,
+               bool isCustomFallback);
 
     FakedFont getClosestMatch(FontStyle style) const;
 
     uint32_t localeListId() const { return mLocaleListId; }
-    Variant variant() const { return mVariant; }
+    FamilyVariant variant() const { return mVariant; }
 
     // API's for enumerating the fonts in a family. These don't guarantee any particular order
     size_t getNumFonts() const { return mFonts.size(); }
@@ -147,6 +49,7 @@
     FontStyle getStyle(size_t index) const { return mFonts[index].style(); }
     bool isColorEmojiFamily() const { return mIsColorEmoji; }
     const std::unordered_set<AxisTag>& supportedAxes() const { return mSupportedAxes; }
+    bool isCustomFallback() const { return mIsCustomFallback; }
 
     // Get Unicode coverage.
     const SparseBitSet& getCoverage() const { return mCoverage; }
@@ -167,10 +70,11 @@
     void computeCoverage();
 
     uint32_t mLocaleListId;
-    Variant mVariant;
+    FamilyVariant mVariant;
     std::vector<Font> mFonts;
     std::unordered_set<AxisTag> mSupportedAxes;
     bool mIsColorEmoji;
+    bool mIsCustomFallback;
 
     SparseBitSet mCoverage;
     std::vector<std::unique_ptr<SparseBitSet>> mCmapFmt14Coverage;
diff --git a/include/minikin/FontVariation.h b/include/minikin/FontVariation.h
new file mode 100644
index 0000000..0c38d6a
--- /dev/null
+++ b/include/minikin/FontVariation.h
@@ -0,0 +1,34 @@
+/*
+ * 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_FONT_VARIATION_H
+#define MINIKIN_FONT_VARIATION_H
+
+#include <cstdint>
+
+namespace minikin {
+
+typedef uint32_t AxisTag;
+
+struct FontVariation {
+    FontVariation(AxisTag axisTag, float value) : axisTag(axisTag), value(value) {}
+    AxisTag axisTag;
+    float value;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_FONT_VARIATION_H
diff --git a/include/minikin/GraphemeBreak.h b/include/minikin/GraphemeBreak.h
index 8d8e359..445fa63 100644
--- a/include/minikin/GraphemeBreak.h
+++ b/include/minikin/GraphemeBreak.h
@@ -17,6 +17,7 @@
 #ifndef MINIKIN_GRAPHEME_BREAK_H
 #define MINIKIN_GRAPHEME_BREAK_H
 
+#include <cstddef>
 #include <cstdint>
 
 namespace minikin {
diff --git a/include/minikin/Hasher.h b/include/minikin/Hasher.h
new file mode 100644
index 0000000..8a79b61
--- /dev/null
+++ b/include/minikin/Hasher.h
@@ -0,0 +1,83 @@
+/*
+ * 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_HASHER_H
+#define MINIKIN_HASHER_H
+
+#include <cstdint>
+
+#include <string>
+
+#include "minikin/Macros.h"
+
+namespace minikin {
+
+// Provides a Jenkins hash implementation.
+class Hasher {
+public:
+    Hasher() : mHash(0u) {}
+
+    IGNORE_INTEGER_OVERFLOW inline Hasher& update(uint32_t data) {
+        mHash += data;
+        mHash += (mHash << 10);
+        mHash ^= (mHash >> 6);
+        return *this;
+    }
+
+    inline Hasher& updateShorts(const uint16_t* data, uint32_t length) {
+        update(length);
+        uint32_t i;
+        for (i = 0; i < (length & -2); i += 2) {
+            update((uint32_t)data[i] | ((uint32_t)data[i + 1] << 16));
+        }
+        if (length & 1) {
+            update((uint32_t)data[i]);
+        }
+        return *this;
+    }
+
+    inline Hasher& updateString(const std::string& str) {
+        uint32_t size = str.size();
+        update(size);
+        uint32_t i;
+        for (i = 0; i < (size & -4); i += 4) {
+            update((uint32_t)str[i] | ((uint32_t)str[i + 1] << 8) | ((uint32_t)str[i + 2] << 16) |
+                   ((uint32_t)str[i + 3] << 24));
+        }
+        if (size & 3) {
+            uint32_t data = str[i];
+            data |= ((size & 3) > 1) ? ((uint32_t)str[i + 1] << 8) : 0;
+            data |= ((size & 3) > 2) ? ((uint32_t)str[i + 2] << 16) : 0;
+            update(data);
+        }
+        return *this;
+    }
+
+    IGNORE_INTEGER_OVERFLOW inline uint32_t hash() {
+        uint32_t hash = mHash;
+        hash += (hash << 3);
+        hash ^= (hash >> 11);
+        hash += (hash << 15);
+        return hash;
+    }
+
+private:
+    uint32_t mHash;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_HASHER_H
diff --git a/include/minikin/HbUtils.h b/include/minikin/HbUtils.h
index 68e4ab8..68fdbba 100644
--- a/include/minikin/HbUtils.h
+++ b/include/minikin/HbUtils.h
@@ -18,10 +18,19 @@
 #define MINIKIN_HB_UTILS_H
 
 #include <hb.h>
+#include <cmath>
 #include <memory>
 
 namespace minikin {
 
+inline float HBFixedToFloat(hb_position_t v) {
+    return scalbnf(v, -8);
+}
+
+inline hb_position_t HBFloatToFixed(float v) {
+    return scalbnf(v, +8);
+}
+
 struct HbBlobDeleter {
     void operator()(hb_blob_t* v) { hb_blob_destroy(v); }
 };
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h
index ba32d98..19f3109 100644
--- a/include/minikin/Layout.h
+++ b/include/minikin/Layout.h
@@ -22,9 +22,9 @@
 #include <vector>
 
 #include <gtest/gtest_prod.h>
-#include <utils/JenkinsHash.h>
 
 #include "minikin/FontCollection.h"
+#include "minikin/LayoutCore.h"
 #include "minikin/Range.h"
 #include "minikin/U16StringPiece.h"
 
@@ -34,14 +34,11 @@
 struct LayoutPieces;
 
 struct LayoutGlyph {
-    // index into mFaces and mHbFonts vectors. We could imagine
-    // moving this into a run length representation, because it's
-    // more efficient for long strings, and we'll probably need
-    // something like that for paint attributes (color, underline,
-    // fake b/i, etc), as having those per-glyph is bloated.
-    int font_ix;
+    LayoutGlyph(FakedFont font, uint32_t glyph_id, float x, float y)
+            : font(font), glyph_id(glyph_id), x(x), y(y) {}
+    FakedFont font;
 
-    unsigned int glyph_id;
+    uint32_t glyph_id;
     float x;
     float y;
 };
@@ -68,66 +65,34 @@
 // may not mutate it at the same time.
 class Layout {
 public:
-    Layout()
-            : mGlyphs(),
-              mAdvances(),
-              mExtents(),
-              mFaces(),
-              mAdvance(0),
-              mBounds() {
-        mBounds.setEmpty();
+    Layout(const U16StringPiece& str, const Range& range, Bidi bidiFlags, const MinikinPaint& paint,
+           StartHyphenEdit startHyphen, EndHyphenEdit endHyphen)
+            : mAdvance(0) {
+        doLayout(str, range, bidiFlags, paint, startHyphen, endHyphen);
     }
 
-    Layout(Layout&& layout) = default;
-
-    Layout(const Layout&) = default;
-    Layout& operator=(const Layout&) = default;
-
-    void dump() const;
-
-    void doLayout(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
-                  const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
-
-    void doLayoutWithPrecomputedPieces(const U16StringPiece& str, const Range& range,
-                                       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);
+    Layout(uint32_t count) : mAdvance(0) {
+        mAdvances.resize(count, 0);
+        mGlyphs.reserve(count);
+    }
 
     static float measureText(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
                              const MinikinPaint& paint, StartHyphenEdit startHyphen,
-                             EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
-                             LayoutPieces* pieces);
+                             EndHyphenEdit endHyphen, float* advances);
 
-    inline const std::vector<float>& advances() const { return mAdvances; }
+    const std::vector<float>& advances() const { return mAdvances; }
 
     // public accessors
-    size_t nGlyphs() const;
-    const MinikinFont* getFont(int i) const;
-    FontFakery getFakery(int i) const;
-    unsigned int getGlyphId(int i) const;
-    float getX(int i) const;
-    float getY(int i) const;
-
-    float getAdvance() const;
-
-    // Get advances, copying into caller-provided buffer. The size of this
-    // buffer must match the length of the string (count arg to doLayout).
-    void getAdvances(float* advances) const;
-
-    // Get extents, copying into caller-provided buffer. The size of this buffer must match the
-    // length of the string (count arg to doLayout).
-    void getExtents(MinikinExtent* extents) const;
-
-    // The i parameter is an offset within the buf relative to start, it is < count, where
-    // start and count are the parameters to doLayout
+    size_t nGlyphs() const { return mGlyphs.size(); }
+    const MinikinFont* getFont(int i) const { return mGlyphs[i].font.font->typeface().get(); }
+    FontFakery getFakery(int i) const { return mGlyphs[i].font.fakery; }
+    unsigned int getGlyphId(int i) const { return mGlyphs[i].glyph_id; }
+    float getX(int i) const { return mGlyphs[i].x; }
+    float getY(int i) const { return mGlyphs[i].y; }
+    float getAdvance() const { return mAdvance; }
     float getCharAdvance(size_t i) const { return mAdvances[i]; }
-
-    void getBounds(MinikinRect* rect) const;
+    const std::vector<float>& getAdvances() const { return mAdvances; }
+    void getBounds(MinikinRect* rect) const { rect->set(mBounds); }
     const MinikinRect& getBounds() const { return mBounds; }
 
     // Purge all caches, useful in low memory conditions
@@ -136,26 +101,14 @@
     // Dump minikin internal statistics, cache usage, cache hit ratio, etc.
     static void dumpMinikinStats(int fd);
 
-    uint32_t getMemoryUsage() const {
-        return sizeof(LayoutGlyph) * nGlyphs() + sizeof(float) * mAdvances.size() +
-               sizeof(MinikinExtent) * mExtents.size() + sizeof(FakedFont) * mFaces.size() +
-               sizeof(float /* mAdvance */) + sizeof(MinikinRect /* mBounds */);
-    }
-
     // Append another layout (for example, cached value) into this one
-    void appendLayout(const Layout& src, size_t start, float extraAdvance);
+    void appendLayout(const LayoutPiece& src, size_t start, float extraAdvance);
 
 private:
-    friend class LayoutCacheKey;
-    friend class LayoutCache;
-
     FRIEND_TEST(LayoutTest, doLayoutWithPrecomputedPiecesTest);
 
-    // Find a face in the mFaces vector. If not found, push back the entry to mFaces.
-    uint8_t findOrPushBackFace(const FakedFont& face);
-
-    // Clears layout, ready to be used again
-    void reset();
+    void doLayout(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
+                  const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
 
     // Lay out a single bidi run
     // When layout is not null, layout info will be stored in the object.
@@ -163,16 +116,13 @@
     static float doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
                                    const MinikinPaint& paint, size_t dstStart,
                                    StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
-                                   const LayoutPieces* lpIn, Layout* layout, float* advances,
-                                   MinikinExtent* extents, MinikinRect* bounds,
-                                   LayoutPieces* lpOut);
+                                   Layout* layout, float* advances);
 
     // 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, MinikinRect* bounds, LayoutPieces* lpOut);
+                              StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* layout,
+                              float* advances);
 
     // Lay out a single bidi run
     void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize, bool isRtl,
@@ -181,12 +131,9 @@
 
     std::vector<LayoutGlyph> mGlyphs;
 
-    // The following three vectors are defined per code unit, so their length is identical to the
-    // input text.
+    // This vector defined per code unit, so their length is identical to the input text.
     std::vector<float> mAdvances;
-    std::vector<MinikinExtent> mExtents;
 
-    std::vector<FakedFont> mFaces;
     float mAdvance;
     MinikinRect mBounds;
 };
diff --git a/include/minikin/LayoutCache.h b/include/minikin/LayoutCache.h
index e99fbe4..058891b 100644
--- a/include/minikin/LayoutCache.h
+++ b/include/minikin/LayoutCache.h
@@ -17,15 +17,22 @@
 #ifndef MINIKIN_LAYOUT_CACHE_H
 #define MINIKIN_LAYOUT_CACHE_H
 
-#include "minikin/Layout.h"
+#include "minikin/LayoutCore.h"
 
 #include <mutex>
 
-#include <utils/JenkinsHash.h>
 #include <utils/LruCache.h>
 
-namespace minikin {
+#include "minikin/FontCollection.h"
+#include "minikin/Hasher.h"
+#include "minikin/MinikinPaint.h"
 
+#ifdef _WIN32
+#include <io.h>
+#endif
+
+namespace minikin {
+const uint32_t LENGTH_LIMIT_CACHE = 128;
 // Layout cache datatypes
 class LayoutCacheKey {
 public:
@@ -42,7 +49,7 @@
               mSkewX(paint.skewX),
               mLetterSpacing(paint.letterSpacing),
               mWordSpacing(paint.wordSpacing),
-              mPaintFlags(paint.paintFlags),
+              mFontFlags(paint.fontFlags),
               mLocaleListId(paint.localeListId),
               mFamilyVariant(paint.familyVariant),
               mStartHyphen(startHyphen),
@@ -54,7 +61,7 @@
         return mId == o.mId && mStart == o.mStart && mCount == o.mCount && mStyle == o.mStyle &&
                mSize == o.mSize && mScaleX == o.mScaleX && mSkewX == o.mSkewX &&
                mLetterSpacing == o.mLetterSpacing && mWordSpacing == o.mWordSpacing &&
-               mPaintFlags == o.mPaintFlags && mLocaleListId == o.mLocaleListId &&
+               mFontFlags == o.mFontFlags && mLocaleListId == o.mLocaleListId &&
                mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen &&
                mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars &&
                !memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t));
@@ -72,13 +79,6 @@
         mChars = NULL;
     }
 
-    void doLayout(Layout* layout, const MinikinPaint& paint) const {
-        layout->mAdvances.resize(mCount, 0);
-        layout->mExtents.resize(mCount);
-        layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, paint, mStartHyphen,
-                            mEndHyphen);
-    }
-
     uint32_t getMemoryUsage() const { return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mNchars; }
 
 private:
@@ -93,9 +93,9 @@
     float mSkewX;
     float mLetterSpacing;
     float mWordSpacing;
-    int32_t mPaintFlags;
+    int32_t mFontFlags;
     uint32_t mLocaleListId;
-    FontFamily::Variant mFamilyVariant;
+    FamilyVariant mFamilyVariant;
     StartHyphenEdit mStartHyphen;
     EndHyphenEdit mEndHyphen;
     bool mIsRtl;
@@ -104,29 +104,27 @@
     android::hash_t mHash;
 
     android::hash_t computeHash() const {
-        uint32_t hash = android::JenkinsHashMix(0, mId);
-        hash = android::JenkinsHashMix(hash, mStart);
-        hash = android::JenkinsHashMix(hash, mCount);
-        hash = android::JenkinsHashMix(hash, android::hash_type(mStyle.identifier()));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mSize));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mScaleX));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mSkewX));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mLetterSpacing));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mWordSpacing));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mPaintFlags));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mLocaleListId));
-        hash = android::JenkinsHashMix(hash,
-                                       android::hash_type(static_cast<uint8_t>(mFamilyVariant)));
-        hash = android::JenkinsHashMix(
-                hash,
-                android::hash_type(static_cast<uint8_t>(packHyphenEdit(mStartHyphen, mEndHyphen))));
-        hash = android::JenkinsHashMix(hash, android::hash_type(mIsRtl));
-        hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);
-        return android::JenkinsHashWhiten(hash);
+        return Hasher()
+                .update(mId)
+                .update(mStart)
+                .update(mCount)
+                .update(mStyle.identifier())
+                .update(mSize)
+                .update(mScaleX)
+                .update(mSkewX)
+                .update(mLetterSpacing)
+                .update(mWordSpacing)
+                .update(mFontFlags)
+                .update(mLocaleListId)
+                .update(static_cast<uint8_t>(mFamilyVariant))
+                .update(packHyphenEdit(mStartHyphen, mEndHyphen))
+                .update(mIsRtl)
+                .updateShorts(mChars, mNchars)
+                .hash();
     }
 };
 
-class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
+class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, LayoutPiece*> {
 public:
     void clear() {
         std::lock_guard<std::mutex> lock(mMutex);
@@ -138,29 +136,26 @@
     void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
                      bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, F& f) {
         LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen);
-        if (paint.skipCache()) {
-            Layout layoutForWord;
-            key.doLayout(&layoutForWord, paint);
-            f(layoutForWord);
+        if (paint.skipCache() || range.getLength() >= LENGTH_LIMIT_CACHE) {
+            f(LayoutPiece(text, range, dir, paint, startHyphen, endHyphen), paint);
             return;
         }
-
         mRequestCount++;
         {
             std::lock_guard<std::mutex> lock(mMutex);
-            Layout* layout = mCache.get(key);
+            LayoutPiece* layout = mCache.get(key);
             if (layout != nullptr) {
                 mCacheHitCount++;
-                f(*layout);
+                f(*layout, paint);
                 return;
             }
         }
         // Doing text layout takes long time, so releases the mutex during doing layout.
         // Don't care even if we do the same layout in other thred.
         key.copyText();
-        std::unique_ptr<Layout> layout = std::make_unique<Layout>();
-        key.doLayout(layout.get(), paint);
-        f(*layout);
+        std::unique_ptr<LayoutPiece> layout =
+                std::make_unique<LayoutPiece>(text, range, dir, paint, startHyphen, endHyphen);
+        f(*layout, paint);
         {
             std::lock_guard<std::mutex> lock(mMutex);
             mCache.put(key, layout.release());
@@ -169,10 +164,23 @@
 
     void dumpStats(int fd) {
         std::lock_guard<std::mutex> lock(mMutex);
+#ifdef _WIN32
+        float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount;
+        int count = _scprintf(
+                "\nLayout Cache Info:\n  Usage: %zd/%zd entries\n  Hit ratio: %d/%d (%f)\n",
+                mCache.size(), kMaxEntries, mCacheHitCount, mRequestCount, ratio);
+        int size = count + 1;
+        char* buffer = new char[size];
+        sprintf_s(buffer, size,
+                  "\nLayout Cache Info:\n  Usage: %zd/%zd entries\n  Hit ratio: %d/%d (%f)\n",
+                  mCache.size(), kMaxEntries, mCacheHitCount, mRequestCount, ratio);
+        _write(fd, buffer, sizeof(buffer));
+#else
         dprintf(fd, "\nLayout Cache Info:\n");
         dprintf(fd, "  Usage: %zd/%zd entries\n", mCache.size(), kMaxEntries);
         float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount;
         dprintf(fd, "  Hit ratio: %d/%d (%f)\n", mCacheHitCount, mRequestCount, ratio);
+#endif
     }
 
     static LayoutCache& getInstance() {
@@ -185,14 +193,19 @@
         mCache.setOnEntryRemovedListener(this);
     }
 
+    uint32_t getCacheSize() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mCache.size();
+    }
+
 private:
     // callback for OnEntryRemoved
-    void operator()(LayoutCacheKey& key, Layout*& value) {
+    void operator()(LayoutCacheKey& key, LayoutPiece*& value) {
         key.freeText();
         delete value;
     }
 
-    android::LruCache<LayoutCacheKey, Layout*> mCache GUARDED_BY(mMutex);
+    android::LruCache<LayoutCacheKey, LayoutPiece*> mCache GUARDED_BY(mMutex);
 
     int32_t mRequestCount;
     int32_t mCacheHitCount;
diff --git a/include/minikin/LayoutCore.h b/include/minikin/LayoutCore.h
new file mode 100644
index 0000000..852b985
--- /dev/null
+++ b/include/minikin/LayoutCore.h
@@ -0,0 +1,94 @@
+/*
+ * 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_CORE_H
+#define MINIKIN_LAYOUT_CORE_H
+
+#include <vector>
+
+#include <gtest/gtest_prod.h>
+
+#include "minikin/FontFamily.h"
+#include "minikin/Hyphenator.h"
+#include "minikin/MinikinExtent.h"
+#include "minikin/MinikinFont.h"
+#include "minikin/MinikinRect.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+struct MinikinPaint;
+
+struct Point {
+    Point() : x(0), y(0) {}
+    Point(float x, float y) : x(x), y(y) {}
+    bool operator==(const Point& o) const { return x == o.x && y == o.y; }
+    float x;
+    float y;
+};
+
+// Immutable, recycle-able layout result.
+class LayoutPiece {
+public:
+    LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool isRtl,
+                const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
+
+    // Low level accessors.
+    const std::vector<uint8_t>& fontIndices() const { return mFontIndices; }
+    const std::vector<uint32_t> glyphIds() const { return mGlyphIds; }
+    const std::vector<Point> points() const { return mPoints; }
+    const std::vector<float> advances() const { return mAdvances; }
+    float advance() const { return mAdvance; }
+    const MinikinRect& bounds() const { return mBounds; }
+    const MinikinExtent& extent() const { return mExtent; }
+    const std::vector<FakedFont>& fonts() const { return mFonts; }
+
+    // Helper accessors
+    uint32_t glyphCount() const { return mGlyphIds.size(); }
+    const FakedFont& fontAt(int glyphPos) const { return mFonts[mFontIndices[glyphPos]]; }
+    uint32_t glyphIdAt(int glyphPos) const { return mGlyphIds[glyphPos]; }
+    const Point& pointAt(int glyphPos) const { return mPoints[glyphPos]; }
+
+    uint32_t getMemoryUsage() const {
+        return sizeof(uint8_t) * mFontIndices.size() + sizeof(uint32_t) * mGlyphIds.size() +
+               sizeof(Point) * mPoints.size() + sizeof(float) * mAdvances.size() + sizeof(float) +
+               sizeof(MinikinRect) + sizeof(MinikinExtent);
+    }
+
+private:
+    FRIEND_TEST(LayoutTest, doLayoutWithPrecomputedPiecesTest);
+
+    std::vector<uint8_t> mFontIndices;  // per glyph
+    std::vector<uint32_t> mGlyphIds;    // per glyph
+    std::vector<Point> mPoints;         // per glyph
+
+    std::vector<float> mAdvances;  // per code units
+
+    float mAdvance;
+    MinikinRect mBounds;
+    MinikinExtent mExtent;
+
+    std::vector<FakedFont> mFonts;
+};
+
+// For gtest output
+inline std::ostream& operator<<(std::ostream& os, const Point& p) {
+    return os << "(" << p.x << ", " << p.y << ")";
+}
+}  // namespace minikin
+
+#endif  // MINIKIN_LAYOUT_CORE_H
diff --git a/include/minikin/LayoutPieces.h b/include/minikin/LayoutPieces.h
index f581372..7985ecc 100644
--- a/include/minikin/LayoutPieces.h
+++ b/include/minikin/LayoutPieces.h
@@ -19,54 +19,97 @@
 
 #include <unordered_map>
 
-#include "minikin/Layout.h"
 #include "minikin/LayoutCache.h"
+#include "minikin/LayoutCore.h"
+#include "minikin/MinikinPaint.h"
 
 namespace minikin {
 
 struct LayoutPieces {
-    struct KeyHasher {
-        std::size_t operator()(const LayoutCacheKey& key) const { return key.hash(); }
+    const static uint32_t kNoPaintId = static_cast<uint32_t>(-1);
+
+    struct Key {
+        Key(const Range& range, HyphenEdit hyphenEdit, bool dir, uint32_t paintId)
+                : range(range), hyphenEdit(hyphenEdit), dir(dir), paintId(paintId) {}
+
+        Range range;
+        HyphenEdit hyphenEdit;
+        bool dir;
+        uint32_t paintId;
+
+        uint32_t hash() const {
+            return Hasher()
+                    .update(range.getStart())
+                    .update(range.getEnd())
+                    .update(hyphenEdit)
+                    .update(dir)
+                    .update(paintId)
+                    .hash();
+        }
+
+        bool operator==(const Key& o) const {
+            return range == o.range && hyphenEdit == o.hyphenEdit && dir == o.dir &&
+                   paintId == o.paintId;
+        }
+
+        uint32_t getMemoryUsage() const {
+            return sizeof(Range) + sizeof(HyphenEdit) + sizeof(bool) + sizeof(uint32_t);
+        }
     };
 
-    LayoutPieces() {}
+    struct KeyHasher {
+        std::size_t operator()(const Key& key) const { return key.hash(); }
+    };
 
-    ~LayoutPieces() {
-        for (const auto it : offsetMap) {
-            const_cast<LayoutCacheKey*>(&it.first)->freeText();
+    struct PaintHasher {
+        std::size_t operator()(const MinikinPaint& paint) const { return paint.hash(); }
+    };
+
+    LayoutPieces() : nextPaintId(0) {}
+    ~LayoutPieces() {}
+
+    uint32_t nextPaintId;
+    std::unordered_map<MinikinPaint, uint32_t, PaintHasher> paintMap;
+    std::unordered_map<Key, LayoutPiece, KeyHasher> offsetMap;
+
+    void insert(const Range& range, HyphenEdit edit, const LayoutPiece& layout, bool dir,
+                const MinikinPaint& paint) {
+        uint32_t paintId = findPaintId(paint);
+        if (paintId == kNoPaintId) {
+            paintId = nextPaintId++;
+            paintMap.insert(std::make_pair(paint, paintId));
         }
-    }
-
-    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();
-        }
+        offsetMap.emplace(std::piecewise_construct,
+                          std::forward_as_tuple(range, edit, dir, paintId),
+                          std::forward_as_tuple(layout));
     }
 
     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));
+    void getOrCreate(const U16StringPiece& textBuf, const Range& range, const Range& context,
+                     const MinikinPaint& paint, bool dir, StartHyphenEdit startEdit,
+                     EndHyphenEdit endEdit, uint32_t paintId, F& f) const {
+        const HyphenEdit edit = packHyphenEdit(startEdit, endEdit);
+        auto it = offsetMap.find(Key(range, edit, dir, paintId));
         if (it == offsetMap.end()) {
-            LayoutCache::getInstance().getOrCreate(textBuf, range, paint, dir, startEdit, endEdit,
-                                                   f);
+            LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
+                                                   range - context.getStart(), paint, dir,
+                                                   startEdit, endEdit, f);
         } else {
-            f(it->second);
+            f(it->second, paint);
         }
     }
 
+    uint32_t findPaintId(const MinikinPaint& paint) const {
+        auto paintIt = paintMap.find(paint);
+        return paintIt == paintMap.end() ? kNoPaintId : paintIt->second;
+    }
+
     uint32_t getMemoryUsage() const {
         uint32_t result = 0;
         for (const auto& i : offsetMap) {
             result += i.first.getMemoryUsage() + i.second.getMemoryUsage();
         }
+        result += (sizeof(MinikinPaint) + sizeof(uint32_t)) * paintMap.size();
         return result;
     }
 };
diff --git a/include/minikin/LineBreaker.h b/include/minikin/LineBreaker.h
index 2974c20..3410339 100644
--- a/include/minikin/LineBreaker.h
+++ b/include/minikin/LineBreaker.h
@@ -53,7 +53,7 @@
 class TabStops {
 public:
     // Caller must free stops. stops can be nullprt.
-    TabStops(const int32_t* stops, size_t nStops, int32_t tabWidth)
+    TabStops(const float* stops, size_t nStops, float tabWidth)
             : mStops(stops), mStopsSize(nStops), mTabWidth(tabWidth) {}
 
     float nextTab(float widthSoFar) const {
@@ -66,9 +66,9 @@
     }
 
 private:
-    const int32_t* mStops;
+    const float* mStops;
     size_t mStopsSize;
-    int32_t mTabWidth;
+    float mTabWidth;
 };
 
 // Implement this for the additional information during line breaking.
@@ -83,12 +83,6 @@
 
     // Called to find out the minimum line width. This mut not return negative values.
     virtual float getMin() const = 0;
-
-    // Called to find out the available left-side padding for the line.
-    virtual float getLeftPaddingAt(size_t lineNo) const = 0;
-
-    // Called to find out the available right-side padding for the line.
-    virtual float getRightPaddingAt(size_t lineNo) const = 0;
 };
 
 struct LineBreakResult {
diff --git a/include/minikin/Macros.h b/include/minikin/Macros.h
index be1b713..dd13bd8 100644
--- a/include/minikin/Macros.h
+++ b/include/minikin/Macros.h
@@ -16,6 +16,12 @@
 #ifndef MINIKIN_MACROS_H
 #define MINIKIN_MACROS_H
 
+#if defined(__clang__)
+#define IGNORE_INTEGER_OVERFLOW __attribute__((no_sanitize("integer")))
+#else
+#define IGNORE_INTEGER_OVERFLOW  // no-op
+#endif                           // __clang__
+
 #define MINIKIN_PREVENT_COPY_AND_ASSIGN(Type) \
     Type(const Type&) = delete;               \
     Type& operator=(const Type&) = delete
diff --git a/include/minikin/MeasuredText.h b/include/minikin/MeasuredText.h
index d33a1ca..2c4b3f8 100644
--- a/include/minikin/MeasuredText.h
+++ b/include/minikin/MeasuredText.h
@@ -38,31 +38,39 @@
     // Returns true if this run is RTL. Otherwise returns false.
     virtual bool isRtl() const = 0;
 
-    // Returns true if this run is a target of hyphenation. Otherwise return false.
-    virtual bool canHyphenate() const = 0;
+    // Returns true if this run can be broken into multiple pieces for line breaking.
+    virtual bool canBreak() const = 0;
 
     // Returns the locale list ID for this run.
     virtual uint32_t getLocaleListId() const = 0;
 
     // Fills the each character's advances, extents and overhangs.
-    virtual void getMetrics(const U16StringPiece& text, float* advances, MinikinExtent* extents,
-                            LayoutPieces* piece) const = 0;
+    virtual void getMetrics(const U16StringPiece& text, std::vector<float>* advances,
+                            LayoutPieces* precomputed, LayoutPieces* outPieces) const = 0;
 
     virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
                                                     const LayoutPieces& pieces) const = 0;
+    virtual MinikinExtent getExtent(const U16StringPiece& text, const Range& range,
+                                    const LayoutPieces& pieces) const = 0;
+
+    virtual void appendLayout(const U16StringPiece& text, const Range& range,
+                              const Range& contextRange, const LayoutPieces& pieces,
+                              const MinikinPaint& paint, uint32_t outOrigin,
+                              StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+                              Layout* outLayout) const = 0;
 
     // Following two methods are only called when the implementation returns true for
-    // canHyphenate method.
+    // canBreak method.
 
     // Returns the paint pointer used for this run.
-    // Returns null if canHyphenate has not returned true.
+    // Returns null if canBreak has not returned true.
     virtual const MinikinPaint* getPaint() const { return nullptr; }
 
     // Measures the hyphenation piece and fills each character's advances and overhangs.
     virtual float measureHyphenPiece(const U16StringPiece& /* text */,
                                      const Range& /* hyphenPieceRange */,
                                      StartHyphenEdit /* startHyphen */,
-                                     EndHyphenEdit /* endHyphen */, float* /* advances */,
+                                     EndHyphenEdit /* endHyphen */,
                                      LayoutPieces* /* pieces */) const {
         return 0.0;
     }
@@ -78,32 +86,29 @@
     StyleRun(const Range& range, MinikinPaint&& paint, bool isRtl)
             : Run(range), mPaint(std::move(paint)), mIsRtl(isRtl) {}
 
-    bool canHyphenate() const override { return true; }
+    bool canBreak() const override { return true; }
     uint32_t getLocaleListId() const override { return mPaint.localeListId; }
     bool isRtl() const override { return mIsRtl; }
 
-    void getMetrics(const U16StringPiece& text, float* advances, MinikinExtent* extents,
-                    LayoutPieces* pieces) const override {
-        Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
-        Layout::measureText(text, mRange, bidiFlag, mPaint, StartHyphenEdit::NO_EDIT,
-                            EndHyphenEdit::NO_EDIT, advances, extents, pieces);
-    }
+    void getMetrics(const U16StringPiece& text, std::vector<float>* advances,
+                    LayoutPieces* precomputed, LayoutPieces* outPieces) const override;
 
     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 LayoutPieces& pieces) const override;
+
+    MinikinExtent getExtent(const U16StringPiece& text, const Range& range,
+                            const LayoutPieces& pieces) const override;
+
+    void appendLayout(const U16StringPiece& text, const Range& range, const Range& contextRange,
+                      const LayoutPieces& pieces, const MinikinPaint& paint, uint32_t outOrigin,
+                      StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+                      Layout* outLayout) const override;
 
     const MinikinPaint* getPaint() const override { return &mPaint; }
 
     float measureHyphenPiece(const U16StringPiece& text, const Range& range,
-                             StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, float* advances,
-                             LayoutPieces* pieces) const override {
-        Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
-        return Layout::measureText(text, range, bidiFlag, mPaint, startHyphen, endHyphen, advances,
-                                   nullptr /* extent */, pieces);
-    }
+                             StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+                             LayoutPieces* pieces) const override;
 
 private:
     MinikinPaint mPaint;
@@ -116,12 +121,12 @@
             : Run(range), mWidth(width), mLocaleListId(localeListId) {}
 
     bool isRtl() const { return false; }
-    bool canHyphenate() const { return false; }
+    bool canBreak() const { return false; }
     uint32_t getLocaleListId() const { return mLocaleListId; }
 
-    void getMetrics(const U16StringPiece& /* unused */, float* advances,
-                    MinikinExtent* /* unused */, LayoutPieces* /* pieces */) const override {
-        advances[0] = mWidth;
+    void getMetrics(const U16StringPiece& /* text */, std::vector<float>* advances,
+                    LayoutPieces* /* precomputed */, LayoutPieces* /* outPieces */) const override {
+        (*advances)[mRange.getStart()] = mWidth;
         // TODO: Get the extents information from the caller.
     }
 
@@ -132,6 +137,17 @@
         return std::make_pair(mWidth, MinikinRect());
     }
 
+    MinikinExtent getExtent(const U16StringPiece& /* text */, const Range& /* range */,
+                            const LayoutPieces& /* pieces */) const override {
+        return MinikinExtent();
+    }
+
+    void appendLayout(const U16StringPiece& /* text */, const Range& /* range */,
+                      const Range& /* contextRange */, const LayoutPieces& /* pieces */,
+                      const MinikinPaint& /* paint */, uint32_t /* outOrigin */,
+                      StartHyphenEdit /* startHyphen */, EndHyphenEdit /* endHyphen */,
+                      Layout* /* outLayout*/) const override {}
+
 private:
     const float mWidth;
     const uint32_t mLocaleListId;
@@ -160,10 +176,6 @@
     // Character widths.
     std::vector<float> widths;
 
-    // Font vertical extents for characters.
-    // TODO: Introduce compression for extents. Usually, this has the same values for all chars.
-    std::vector<MinikinExtent> extents;
-
     // Hyphenation points.
     std::vector<HyphenBreak> hyphenBreaks;
 
@@ -175,14 +187,15 @@
     LayoutPieces layoutPieces;
 
     uint32_t getMemoryUsage() const {
-        return sizeof(float) * widths.size() + sizeof(MinikinExtent) * extents.size() +
-               sizeof(HyphenBreak) * hyphenBreaks.size() + layoutPieces.getMemoryUsage();
+        return sizeof(float) * widths.size() + sizeof(HyphenBreak) * hyphenBreaks.size() +
+               layoutPieces.getMemoryUsage();
     }
 
-    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);
+    Layout buildLayout(const U16StringPiece& textBuf, const Range& range, const Range& contextRange,
+                       const MinikinPaint& paint, StartHyphenEdit startHyphen,
+                       EndHyphenEdit endHyphen);
+    MinikinRect getBounds(const U16StringPiece& textBuf, const Range& range) const;
+    MinikinExtent getExtent(const U16StringPiece& textBuf, const Range& range) const;
 
     MeasuredText(MeasuredText&&) = default;
     MeasuredText& operator=(MeasuredText&&) = default;
@@ -192,13 +205,14 @@
 private:
     friend class MeasuredTextBuilder;
 
-    void measure(const U16StringPiece& textBuf, bool computeHyphenation, bool computeLayout);
+    void measure(const U16StringPiece& textBuf, bool computeHyphenation, bool computeLayout,
+                 MeasuredText* hint);
 
     // Use MeasuredTextBuilder instead.
     MeasuredText(const U16StringPiece& textBuf, std::vector<std::unique_ptr<Run>>&& runs,
-                 bool computeHyphenation, bool computeLayout)
-            : widths(textBuf.size()), extents(textBuf.size()), runs(std::move(runs)) {
-        measure(textBuf, computeHyphenation, computeLayout);
+                 bool computeHyphenation, bool computeLayout, MeasuredText* hint)
+            : widths(textBuf.size()), runs(std::move(runs)) {
+        measure(textBuf, computeHyphenation, computeLayout, hint);
     }
 };
 
@@ -221,10 +235,10 @@
     }
 
     std::unique_ptr<MeasuredText> build(const U16StringPiece& textBuf, bool computeHyphenation,
-                                        bool computeLayout) {
+                                        bool computeLayout, MeasuredText* hint) {
         // Unable to use make_unique here since make_unique is not a friend of MeasuredText.
-        return std::unique_ptr<MeasuredText>(
-                new MeasuredText(textBuf, std::move(mRuns), computeHyphenation, computeLayout));
+        return std::unique_ptr<MeasuredText>(new MeasuredText(
+                textBuf, std::move(mRuns), computeHyphenation, computeLayout, hint));
     }
 
     MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(MeasuredTextBuilder);
diff --git a/include/minikin/Measurement.h b/include/minikin/Measurement.h
index 55c8474..c8b97d1 100644
--- a/include/minikin/Measurement.h
+++ b/include/minikin/Measurement.h
@@ -17,6 +17,7 @@
 #ifndef MINIKIN_MEASUREMENT_H
 #define MINIKIN_MEASUREMENT_H
 
+#include <cstddef>
 #include <cstdint>
 
 namespace minikin {
diff --git a/include/minikin/MinikinExtent.h b/include/minikin/MinikinExtent.h
new file mode 100644
index 0000000..ac9b49f
--- /dev/null
+++ b/include/minikin/MinikinExtent.h
@@ -0,0 +1,48 @@
+/*
+ * 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_MINIKIN_EXTENT_H
+#define MINIKIN_MINIKIN_EXTENT_H
+
+#include <ostream>
+
+namespace minikin {
+
+// For holding vertical extents.
+struct MinikinExtent {
+    MinikinExtent() : ascent(0), descent(0) {}
+    MinikinExtent(float ascent, float descent) : ascent(ascent), descent(descent) {}
+    bool operator==(const MinikinExtent& o) const {
+        return ascent == o.ascent && descent == o.descent;
+    }
+    float ascent;   // negative
+    float descent;  // positive
+
+    void reset() { ascent = descent = 0.0; }
+
+    void extendBy(const MinikinExtent& e) {
+        ascent = std::min(ascent, e.ascent);
+        descent = std::max(descent, e.descent);
+    }
+};
+
+// For gtest output
+inline std::ostream& operator<<(std::ostream& os, const MinikinExtent& e) {
+    return os << e.ascent << ", " << e.descent;
+}
+}  // namespace minikin
+
+#endif  // MINIKIN_MINIKIN_EXTENT_H
diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h
index f57e4f0..a5ba074 100644
--- a/include/minikin/MinikinFont.h
+++ b/include/minikin/MinikinFont.h
@@ -14,113 +14,24 @@
  * limitations under the License.
  */
 
-#ifndef MINIKIN_FONT_H
-#define MINIKIN_FONT_H
+#ifndef MINIKIN_MINIKIN_FONT_H
+#define MINIKIN_MINIKIN_FONT_H
 
-#include <string>
+#include <cstdint>
+#include <memory>
+#include <vector>
 
-#include "minikin/FontFamily.h"
-#include "minikin/Hyphenator.h"
-
-// An abstraction for platform fonts, allowing Minikin to be used with
-// multiple actual implementations of fonts.
+#include "minikin/FontVariation.h"
 
 namespace minikin {
 
-class FontCollection;
-class MinikinFont;
+class FontFakery;
+struct MinikinExtent;
+struct MinikinPaint;
+struct MinikinRect;
 
-// Possibly move into own .h file?
-// Note: if you add a field here, either add it to LayoutCacheKey or to skipCache()
-struct MinikinPaint {
-    MinikinPaint(const std::shared_ptr<FontCollection>& font)
-            : size(0),
-              scaleX(0),
-              skewX(0),
-              letterSpacing(0),
-              wordSpacing(0),
-              paintFlags(0),
-              localeListId(0),
-              familyVariant(FontFamily::Variant::DEFAULT),
-              fontFeatureSettings(),
-              font(font) {}
-
-    bool skipCache() const { return !fontFeatureSettings.empty(); }
-
-    float size;
-    float scaleX;
-    float skewX;
-    float letterSpacing;
-    float wordSpacing;
-    uint32_t paintFlags;
-    uint32_t localeListId;
-    FontStyle fontStyle;
-    FontFamily::Variant familyVariant;
-    std::string fontFeatureSettings;
-    std::shared_ptr<FontCollection> font;
-
-    void copyFrom(const MinikinPaint& paint) { *this = paint; }
-
-    MinikinPaint(MinikinPaint&&) = default;
-    MinikinPaint& operator=(MinikinPaint&&) = default;
-
-    inline bool operator==(const MinikinPaint& paint) {
-        return size == paint.size && scaleX == paint.scaleX && skewX == paint.skewX &&
-               letterSpacing == paint.letterSpacing && wordSpacing == paint.wordSpacing &&
-               paintFlags == paint.paintFlags && localeListId == paint.localeListId &&
-               fontStyle == paint.fontStyle && familyVariant == paint.familyVariant &&
-               fontFeatureSettings == paint.fontFeatureSettings && font.get() == paint.font.get();
-    }
-
-private:
-    // Forbid implicit copy and assign. Use copyFrom instead.
-    MinikinPaint(const MinikinPaint&) = default;
-    MinikinPaint& operator=(const MinikinPaint&) = default;
-};
-
-// Only a few flags affect layout, but those that do should have values
-// consistent with Android's paint flags.
-enum MinikinPaintFlags {
-    LinearTextFlag = 0x40,
-};
-
-struct MinikinRect {
-    float mLeft = 0.0;
-    float mTop = 0.0;
-    float mRight = 0.0;
-    float mBottom = 0.0;
-    bool isEmpty() const { return mLeft == mRight || mTop == mBottom; }
-    void set(const MinikinRect& r) {
-        mLeft = r.mLeft;
-        mTop = r.mTop;
-        mRight = r.mRight;
-        mBottom = r.mBottom;
-    }
-    void offset(float dx, float dy) {
-        mLeft += dx;
-        mTop += dy;
-        mRight += dx;
-        mBottom += dy;
-    }
-    void setEmpty() { mLeft = mTop = mRight = mBottom = 0.0; }
-    void join(const MinikinRect& r);
-};
-
-// For holding vertical extents.
-struct MinikinExtent {
-    float ascent = 0.0;    // negative
-    float descent = 0.0;   // positive
-    float line_gap = 0.0;  // positive
-
-    void reset() { ascent = descent = line_gap = 0.0; }
-
-    void extendBy(const MinikinExtent& e) {
-        ascent = std::min(ascent, e.ascent);
-        descent = std::max(descent, e.descent);
-        line_gap = std::max(line_gap, e.line_gap);
-    }
-};
-
+// An abstraction for platform fonts, allowing Minikin to be used with
+// multiple actual implementations of fonts.
 class MinikinFont {
 public:
     explicit MinikinFont(int32_t uniqueId) : mUniqueId(uniqueId) {}
@@ -129,6 +40,13 @@
 
     virtual float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint& paint,
                                        const FontFakery& fakery) const = 0;
+    virtual void GetHorizontalAdvances(uint16_t* glyph_ids, uint32_t count,
+                                       const MinikinPaint& paint, const FontFakery& fakery,
+                                       float* outAdvances) const {
+        for (uint32_t i = 0; i < count; ++i) {
+            outAdvances[i] = GetHorizontalAdvance(glyph_ids[i], paint, fakery);
+        }
+    }
 
     virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id, const MinikinPaint& paint,
                            const FontFakery& fakery) const = 0;
@@ -165,4 +83,4 @@
 
 }  // namespace minikin
 
-#endif  // MINIKIN_FONT_H
+#endif  // MINIKIN_MINIKIN_FONT_H
diff --git a/include/minikin/MinikinPaint.h b/include/minikin/MinikinPaint.h
new file mode 100644
index 0000000..54b476f
--- /dev/null
+++ b/include/minikin/MinikinPaint.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 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_MINIKIN_PAINT_H
+#define MINIKIN_MINIKIN_PAINT_H
+
+#include <memory>
+#include <string>
+
+#include "minikin/FamilyVariant.h"
+#include "minikin/FontCollection.h"
+#include "minikin/FontFamily.h"
+#include "minikin/Hasher.h"
+
+namespace minikin {
+
+class FontCollection;
+
+// These describe what is stored in MinikinPaint.fontFlags.
+enum MinikinFontFlags {
+    Embolden_Shift = 0,
+    LinearMetrics_Shift = 1,
+    Subpixel_Shift = 2,
+    EmbeddedBitmaps_Shift = 3,
+    ForceAutoHinting_Shift = 4,
+
+    Embolden_Flag = 1 << Embolden_Shift,
+    LinearMetrics_Flag = 1 << LinearMetrics_Shift,
+    Subpixel_Flag = 1 << Subpixel_Shift,
+    EmbeddedBitmaps_Flag = 1 << EmbeddedBitmaps_Shift,
+    ForceAutoHinting_Flag = 1 << ForceAutoHinting_Shift,
+};
+
+// Possibly move into own .h file?
+// Note: if you add a field here, either add it to LayoutCacheKey or to skipCache()
+struct MinikinPaint {
+    MinikinPaint(const std::shared_ptr<FontCollection>& font)
+            : size(0),
+              scaleX(0),
+              skewX(0),
+              letterSpacing(0),
+              wordSpacing(0),
+              fontFlags(0),
+              localeListId(0),
+              familyVariant(FamilyVariant::DEFAULT),
+              fontFeatureSettings(),
+              font(font) {}
+
+    bool skipCache() const { return !fontFeatureSettings.empty(); }
+
+    float size;
+    float scaleX;
+    float skewX;
+    float letterSpacing;
+    float wordSpacing;
+    uint32_t fontFlags;
+    uint32_t localeListId;
+    FontStyle fontStyle;
+    FamilyVariant familyVariant;
+    std::string fontFeatureSettings;
+    std::shared_ptr<FontCollection> font;
+
+    void copyFrom(const MinikinPaint& paint) { *this = paint; }
+
+    MinikinPaint(const MinikinPaint&) = default;
+    MinikinPaint& operator=(const MinikinPaint&) = default;
+
+    MinikinPaint(MinikinPaint&&) = default;
+    MinikinPaint& operator=(MinikinPaint&&) = default;
+
+    inline bool operator==(const MinikinPaint& paint) const {
+        return size == paint.size && scaleX == paint.scaleX && skewX == paint.skewX &&
+               letterSpacing == paint.letterSpacing && wordSpacing == paint.wordSpacing &&
+               fontFlags == paint.fontFlags && localeListId == paint.localeListId &&
+               fontStyle == paint.fontStyle && familyVariant == paint.familyVariant &&
+               fontFeatureSettings == paint.fontFeatureSettings && font.get() == paint.font.get();
+    }
+
+    uint32_t hash() const {
+        return Hasher()
+                .update(size)
+                .update(scaleX)
+                .update(skewX)
+                .update(letterSpacing)
+                .update(wordSpacing)
+                .update(fontFlags)
+                .update(localeListId)
+                .update(fontStyle.identifier())
+                .update(static_cast<uint8_t>(familyVariant))
+                .updateString(fontFeatureSettings)
+                .update(font->getId())
+                .hash();
+    }
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_MINIKIN_PAINT_H
diff --git a/include/minikin/MinikinRect.h b/include/minikin/MinikinRect.h
new file mode 100644
index 0000000..6a3d88b
--- /dev/null
+++ b/include/minikin/MinikinRect.h
@@ -0,0 +1,69 @@
+/*
+ * 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_MINIKIN_RECT_H
+#define MINIKIN_MINIKIN_RECT_H
+
+#include <ostream>
+
+namespace minikin {
+
+struct MinikinRect {
+    MinikinRect() : mLeft(0), mTop(0), mRight(0), mBottom(0) {}
+    MinikinRect(float left, float top, float right, float bottom)
+            : mLeft(left), mTop(top), mRight(right), mBottom(bottom) {}
+    bool operator==(const MinikinRect& o) const {
+        return mLeft == o.mLeft && mTop == o.mTop && mRight == o.mRight && mBottom == o.mBottom;
+    }
+    float mLeft;
+    float mTop;
+    float mRight;
+    float mBottom;
+
+    bool isEmpty() const { return mLeft == mRight || mTop == mBottom; }
+    void set(const MinikinRect& r) {
+        mLeft = r.mLeft;
+        mTop = r.mTop;
+        mRight = r.mRight;
+        mBottom = r.mBottom;
+    }
+    void offset(float dx, float dy) {
+        mLeft += dx;
+        mTop += dy;
+        mRight += dx;
+        mBottom += dy;
+    }
+    void setEmpty() { mLeft = mTop = mRight = mBottom = 0.0; }
+    void join(const MinikinRect& r) {
+        if (isEmpty()) {
+            set(r);
+        } else if (!r.isEmpty()) {
+            mLeft = std::min(mLeft, r.mLeft);
+            mTop = std::min(mTop, r.mTop);
+            mRight = std::max(mRight, r.mRight);
+            mBottom = std::max(mBottom, r.mBottom);
+        }
+    }
+};
+
+// For gtest output
+inline std::ostream& operator<<(std::ostream& os, const MinikinRect& r) {
+    return os << "(" << r.mLeft << ", " << r.mTop << ")-(" << r.mRight << ", " << r.mBottom << ")";
+}
+
+}  // namespace minikin
+
+#endif  // MINIKIN_MINIKIN_RECT_H
diff --git a/include/minikin/Range.h b/include/minikin/Range.h
index 4c7fbfa..991912d 100644
--- a/include/minikin/Range.h
+++ b/include/minikin/Range.h
@@ -87,6 +87,10 @@
 
     inline bool operator!=(const Range& o) const { return !(*this == o); }
 
+    inline Range operator+(int32_t shift) const { return Range(mStart + shift, mEnd + shift); }
+
+    inline Range operator-(int32_t shift) const { return Range(mStart - shift, mEnd - shift); }
+
 private:
     // Helper class for "for (uint32_t i : range)" style for-loop.
     class RangeIterator {
diff --git a/include/minikin/SystemFonts.h b/include/minikin/SystemFonts.h
new file mode 100644
index 0000000..4108215
--- /dev/null
+++ b/include/minikin/SystemFonts.h
@@ -0,0 +1,73 @@
+/*
+ * 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_SYSTEM_FONTS_H
+#define MINIKIN_SYSTEM_FONTS_H
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "minikin/FontCollection.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+// Provides a system font mapping.
+class SystemFonts {
+public:
+    static std::shared_ptr<FontCollection> findFontCollection(const std::string& familyName) {
+        return getInstance().findFontCollectionInternal(familyName);
+    }
+
+    // Do not call this function outside Zygote process.
+    static void registerFallback(const std::string& familyName,
+                                 const std::shared_ptr<FontCollection>& fc) {
+        return getInstance().registerFallbackInternal(familyName, fc);
+    }
+
+    // Do not call this function outside Zygote process.
+    static void registerDefault(const std::shared_ptr<FontCollection>& fc) {
+        return getInstance().registerDefaultInternal(fc);
+    }
+
+protected:
+    // Visible for testing purposes.
+    SystemFonts() {}
+    virtual ~SystemFonts() {}
+
+    std::shared_ptr<FontCollection> findFontCollectionInternal(const std::string& familyName) const;
+    void registerFallbackInternal(const std::string& familyName,
+                                  const std::shared_ptr<FontCollection>& fc) {
+        mSystemFallbacks.insert(std::make_pair(familyName, fc));
+    }
+
+    void registerDefaultInternal(const std::shared_ptr<FontCollection>& fc) {
+        mDefaultFallback = fc;
+    }
+
+private:
+    static SystemFonts& getInstance();
+
+    // There is no mutex guard here since registerFallback is designed to be
+    // called only in Zygote.
+    std::map<std::string, std::shared_ptr<FontCollection>> mSystemFallbacks;
+    std::shared_ptr<FontCollection> mDefaultFallback;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_SYSTEM_FONTS_H
diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp
index 7cd3858..99dd015 100644
--- a/libs/minikin/Android.bp
+++ b/libs/minikin/Android.bp
@@ -23,65 +23,37 @@
     name: "libminikin",
     host_supported: true,
     srcs: [
+        "BidiUtils.cpp",
+        "CmapCoverage.cpp",
+        "Emoji.cpp",
+        "FontCollection.cpp",
+        "FontFamily.cpp",
+        "FontUtils.cpp",
+        "GraphemeBreak.cpp",
+        "GreedyLineBreaker.cpp",
         "Hyphenator.cpp",
+        "HyphenatorMap.cpp",
+        "Layout.cpp",
+        "LayoutCore.cpp",
+        "LayoutUtils.cpp",
+        "LineBreaker.cpp",
+        "LineBreakerUtil.cpp",
+        "Locale.cpp",
+        "LocaleListCache.cpp",
+        "MeasuredText.cpp",
+        "Measurement.cpp",
+        "MinikinInternal.cpp",
+        "OptimalLineBreaker.cpp",
+        "SparseBitSet.cpp",
+        "SystemFonts.cpp",
+        "WordBreaker.cpp",
     ],
     cflags: ["-Wall", "-Werror"],
-    target: {
-        android: {
-            srcs: [
-                "BidiUtils.cpp",
-                "CmapCoverage.cpp",
-                "Emoji.cpp",
-                "FontCollection.cpp",
-                "FontFamily.cpp",
-                "FontUtils.cpp",
-                "GraphemeBreak.cpp",
-                "GreedyLineBreaker.cpp",
-                "HyphenatorMap.cpp",
-                "Layout.cpp",
-                "LayoutUtils.cpp",
-                "LineBreaker.cpp",
-                "LineBreakerUtil.cpp",
-                "Locale.cpp",
-                "LocaleListCache.cpp",
-                "MeasuredText.cpp",
-                "Measurement.cpp",
-                "MinikinInternal.cpp",
-                "OptimalLineBreaker.cpp",
-                "SparseBitSet.cpp",
-                "WordBreaker.cpp",
-            ],
-            shared_libs: [
-                "libandroidicu",
-                "libharfbuzz_ng",
-                "libft2",
-                "libz",
-                "libutils",
-            ],
-            export_shared_lib_headers: [
-                "libandroidicu",
-                // TODO: clean up Minikin so it doesn't need the freetype include
-                "libft2",
-            ],
-
-            sanitize: {
-                misc_undefined: [
-                    "signed-integer-overflow",
-                    // b/26432628.
-                    //"unsigned-integer-overflow",
-                ],
-            },
-        },
-        host: {
-            shared_libs: [
-                "libicui18n",
-                "libicuuc",
-            ],
-            export_shared_lib_headers: [
-                "libicui18n",
-                "libicuuc",
-            ],
-        },
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
     },
     cppflags: [
         "-Werror",
@@ -97,13 +69,43 @@
     },
     shared_libs: [
         "liblog",
+        "libharfbuzz_ng",
     ],
     header_libs: [
         "libbase_headers",
         "libminikin_headers",
+        "libutils_headers",
     ],
     export_header_lib_headers: ["libminikin_headers"],
     whole_static_libs: ["libgtest_prod"],
 
     clang: true,
+
+    target: {
+        android: {
+            shared_libs: [
+                "libandroidicu",
+            ],
+            export_shared_lib_headers: [
+                "libandroidicu",
+            ],
+        },
+        host: {
+            shared_libs: [
+                "libicui18n",
+                "libicuuc",
+            ],
+            export_shared_lib_headers: [
+                "libicui18n",
+                "libicuuc",
+            ],
+        },
+        windows: {
+            enabled: true,
+            cppflags: [
+                "-Wno-ignored-attributes",
+                "-Wno-thread-safety",
+            ],
+        },
+    },
 }
diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp
index f6143d3..8d04ce8 100644
--- a/libs/minikin/CmapCoverage.cpp
+++ b/libs/minikin/CmapCoverage.cpp
@@ -21,6 +21,7 @@
 #include <algorithm>
 #include <vector>
 
+#include "minikin/Range.h"
 #include "minikin/SparseBitSet.h"
 
 #include "MinikinInternal.h"
diff --git a/libs/minikin/Emoji.cpp b/libs/minikin/Emoji.cpp
index ad83189..cd52f52 100644
--- a/libs/minikin/Emoji.cpp
+++ b/libs/minikin/Emoji.cpp
@@ -19,18 +19,20 @@
 namespace minikin {
 
 bool isNewEmoji(uint32_t c) {
-    // Emoji characters new in Unicode emoji 11
-    // From https://www.unicode.org/Public/emoji/11.0/emoji-data.txt
-    // TODO: Remove once emoji-data.text 11 is in ICU or update to 11.
-    if (c < 0x1F6F9 || c > 0x1F9FF) {
+    // Emoji characters new in Unicode emoji 12
+    // From https://www.unicode.org/Public/emoji/12.0/emoji-data.txt
+    // TODO: Remove once emoji-data.text 12 is in ICU or update to 12.
+    if (c < 0x1F6D5 || c > 0x1FA95) {
         // Optimization for characters outside the new emoji range.
         return false;
     }
-    return c == 0x265F || c == 0x267E || c == 0x1F6F9 || (0x1F94D <= c && c <= 0x1F94F) ||
-           (0x1F96C <= c && c <= 0x1F970) || (0x1F973 <= c && c <= 0x1F976) || c == 0x1F97A ||
-           (0x1F97C <= c && c <= 0x1F97F) || (0x1F998 <= c && c <= 0x1F9A2) ||
-           (0x1F9B0 <= c && c <= 0x1F9B9) || (0x1F9C1 <= c && c <= 0x1F9C2) ||
-           (0x1F9E7 <= c && c <= 0x1F9FF);
+    return c == 0x1F6D5 || c == 0x1F6FA || c == 0x1F93F || c == 0x1F971 || c == 0x1F97B ||
+           (0x1F7E0 <= c && c <= 0x1F7EB) || (0x1F90D <= c && c <= 0x1F90F) ||
+           (0x1F9A5 <= c && c <= 0x1F9AA) || (0x1F9AE <= c && c <= 0x1F9AF) ||
+           (0x1F9BA <= c && c <= 0x1F9BF) || (0x1F9C3 <= c && c <= 0x1F9CA) ||
+           (0x1F9CD <= c && c <= 0x1F9CF) || (0x1FA70 <= c && c <= 0x1FA73) ||
+           (0x1FA78 <= c && c <= 0x1FA7A) || (0x1FA80 <= c && c <= 0x1FA82) ||
+           (0x1FA90 <= c && c <= 0x1FA95);
 }
 
 bool isEmoji(uint32_t c) {
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index 1bf2668..6cbabea 100644
--- a/libs/minikin/FontCollection.cpp
+++ b/libs/minikin/FontCollection.cpp
@@ -123,7 +123,7 @@
 //    base character.
 //  - kFirstFontScore: When the font is the first font family in the collection and it supports the
 //    given character or variation sequence.
-uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
+uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, FamilyVariant variant,
                                          uint32_t localeListId,
                                          const std::shared_ptr<FontFamily>& fontFamily) const {
     const uint32_t coverageScore = calcCoverageScore(ch, vs, localeListId, fontFamily);
@@ -159,7 +159,7 @@
         return kUnsupportedFontScore;
     }
 
-    if ((vs == 0 || hasVSGlyph) && mFamilies[0] == fontFamily) {
+    if ((vs == 0 || hasVSGlyph) && (mFamilies[0] == fontFamily || fontFamily->isCustomFallback())) {
         // If the first font family supports the given character or variation sequence, always use
         // it.
         return kFirstFontScore;
@@ -228,16 +228,16 @@
 // Calculates a font score based on variant ("compact" or "elegant") matching.
 //  - Returns 1 if the font doesn't have variant or the variant matches with the text style.
 //  - No score if the font has a variant but it doesn't match with the text style.
-uint32_t FontCollection::calcVariantMatchingScore(FontFamily::Variant variant,
+uint32_t FontCollection::calcVariantMatchingScore(FamilyVariant variant,
                                                   const FontFamily& fontFamily) {
-    const FontFamily::Variant familyVariant = fontFamily.variant();
-    if (familyVariant == FontFamily::Variant::DEFAULT) {
+    const FamilyVariant familyVariant = fontFamily.variant();
+    if (familyVariant == FamilyVariant::DEFAULT) {
         return 1;
     }
     if (familyVariant == variant) {
         return 1;
     }
-    if (variant == FontFamily::Variant::DEFAULT && familyVariant == FontFamily::Variant::COMPACT) {
+    if (variant == FamilyVariant::DEFAULT && familyVariant == FamilyVariant::COMPACT) {
         // If default is requested, prefer compat variation.
         return 1;
     }
@@ -249,8 +249,9 @@
 // 2. Calculate a score for the font family. See comments in calcFamilyScore for the detail.
 // 3. Highest score wins, with ties resolved to the first font.
 // This method never returns nullptr.
-const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(
-        uint32_t ch, uint32_t vs, uint32_t localeListId, FontFamily::Variant variant) const {
+const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs,
+                                                                    uint32_t localeListId,
+                                                                    FamilyVariant variant) const {
     if (ch >= mMaxChar) {
         return mFamilies[0];
     }
@@ -367,17 +368,19 @@
 
 constexpr uint32_t REPLACEMENT_CHARACTER = 0xFFFD;
 
-void FontCollection::itemize(const uint16_t* string, size_t string_size, const MinikinPaint& paint,
-                             vector<Run>* result) const {
-    const FontFamily::Variant familyVariant = paint.familyVariant;
-    const FontStyle style = paint.fontStyle;
-    const uint32_t localeListId = paint.localeListId;
+std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, FontStyle style,
+                                                         uint32_t localeListId,
+                                                         FamilyVariant familyVariant,
+                                                         uint32_t runMax) const {
+    const uint16_t* string = text.data();
+    const uint32_t string_size = text.size();
+    std::vector<Run> result;
 
     const FontFamily* lastFamily = nullptr;
     Run* run = nullptr;
 
     if (string_size == 0) {
-        return;
+        return result;
     }
 
     const uint32_t kEndOfString = 0xFFFFFFFF;
@@ -430,7 +433,7 @@
                     if (run != nullptr) {
                         run->end -= prevChLength;
                         if (run->start == run->end) {
-                            result->pop_back();
+                            result.pop_back();
                         }
                     }
                     start -= prevChLength;
@@ -442,8 +445,8 @@
                     // start to be 0 to include those characters).
                     start = 0;
                 }
-                result->push_back({family->getClosestMatch(style), static_cast<int>(start), 0});
-                run = &result->back();
+                result.push_back({family->getClosestMatch(style), static_cast<int>(start), 0});
+                run = &result.back();
                 lastFamily = family.get();
             }
         }
@@ -451,13 +454,28 @@
         if (run != nullptr) {
             run->end = nextUtf16Pos;  // exclusive
         }
+
+        // Stop searching the remaining characters if the result length gets runMax + 2.
+        // When result.size gets runMax + 2 here, the run between [0, runMax) was finalized.
+        // If the result.size() equals to runMax, the run may be still expanding.
+        // if the result.size() equals to runMax + 2, the last run may be removed and the last run
+        // may be exntended the previous run with above workaround.
+        if (result.size() >= 2 && runMax == result.size() - 2) {
+            break;
+        }
     } while (nextCh != kEndOfString);
 
     if (lastFamily == nullptr) {
         // No character needed any font support, so it doesn't really matter which font they end up
         // getting displayed in. We put the whole string in one run, using the first font.
-        result->push_back({mFamilies[0]->getClosestMatch(style), 0, static_cast<int>(string_size)});
+        result.push_back({mFamilies[0]->getClosestMatch(style), 0, static_cast<int>(string_size)});
     }
+
+    if (result.size() > runMax) {
+        // The itemization has terminated since it reaches the runMax. Remove last unfinalized runs.
+        result.resize(runMax);
+    }
+    return result;
 }
 
 FakedFont FontCollection::baseFontFaked(FontStyle style) {
diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp
index b95d858..85a5142 100644
--- a/libs/minikin/FontFamily.cpp
+++ b/libs/minikin/FontFamily.cpp
@@ -24,9 +24,9 @@
 #include <hb-ot.h>
 #include <hb.h>
 #include <log/log.h>
-#include <utils/JenkinsHash.h>
 
 #include "minikin/CmapCoverage.h"
+#include "minikin/FamilyVariant.h"
 #include "minikin/HbUtils.h"
 #include "minikin/MinikinFont.h"
 
@@ -105,17 +105,20 @@
 }
 
 FontFamily::FontFamily(std::vector<Font>&& fonts)
-        : FontFamily(Variant::DEFAULT, std::move(fonts)) {}
+        : FontFamily(FamilyVariant::DEFAULT, std::move(fonts)) {}
 
-FontFamily::FontFamily(Variant variant, std::vector<Font>&& fonts)
-        : FontFamily(LocaleListCache::kEmptyListId, variant, std::move(fonts)) {}
+FontFamily::FontFamily(FamilyVariant variant, std::vector<Font>&& fonts)
+        : FontFamily(LocaleListCache::kEmptyListId, variant, std::move(fonts),
+                     false /* isCustomFallback */) {}
 
-FontFamily::FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts)
+FontFamily::FontFamily(uint32_t localeListId, FamilyVariant variant, std::vector<Font>&& fonts,
+                       bool isCustomFallback)
         : mLocaleListId(localeListId),
           mVariant(variant),
           mFonts(std::move(fonts)),
           mIsColorEmoji(LocaleListCache::getById(localeListId).getEmojiStyle() ==
-                        EmojiStyle::EMOJI) {
+                        EmojiStyle::EMOJI),
+          mIsCustomFallback(isCustomFallback) {
     MINIKIN_ASSERT(!mFonts.empty(), "FontFamily must contain at least one font.");
     computeCoverage();
 }
@@ -235,7 +238,8 @@
         fonts.push_back(Font::Builder(minikinFont).setStyle(font.style()).build());
     }
 
-    return std::shared_ptr<FontFamily>(new FontFamily(mLocaleListId, mVariant, std::move(fonts)));
+    return std::shared_ptr<FontFamily>(
+            new FontFamily(mLocaleListId, mVariant, std::move(fonts), mIsCustomFallback));
 }
 
 }  // namespace minikin
diff --git a/libs/minikin/GraphemeBreak.cpp b/libs/minikin/GraphemeBreak.cpp
index 52b99c0..2bb5c17 100644
--- a/libs/minikin/GraphemeBreak.cpp
+++ b/libs/minikin/GraphemeBreak.cpp
@@ -106,10 +106,6 @@
     if ((p1 == U_GCB_LVT || p1 == U_GCB_T) && p2 == U_GCB_T) {
         return false;
     }
-    // Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x
-    if (p2 == U_GCB_EXTEND || p2 == U_GCB_ZWJ || p2 == U_GCB_SPACING_MARK || p1 == U_GCB_PREPEND) {
-        return false;
-    }
 
     // This is used to decide font-dependent grapheme clusters. If we don't have the advance
     // information, we become conservative in grapheme breaking and assume that it has no advance.
@@ -122,48 +118,32 @@
         return true;
     }
 
-    // Note: For Rule GB10 and GB11 below, we do not use the Unicode line breaking properties for
-    // determining emoji-ness and carry our own data, because our data could be more fresh than what
-    // ICU provides.
-    //
-    // Tailored version of Rule GB10, (E_Base | EBG) Extend* × E_Modifier.
-    // The rule itself says do not break between emoji base and emoji modifiers, skipping all Extend
-    // characters. Variation selectors are considered Extend, so they are handled fine.
-    //
-    // We tailor this by requiring that an actual ligature is formed. If the font doesn't form a
-    // ligature, we allow a break before the modifier.
-    if (isEmojiModifier(c2)) {
-        uint32_t c0 = c1;
+    // Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x
+    if (p2 == U_GCB_EXTEND || p2 == U_GCB_ZWJ || p2 == U_GCB_SPACING_MARK || p1 == U_GCB_PREPEND) {
+        return false;
+    }
+
+    // Tailored version of Rule GB11
+    // \p{Extended_Pictographic} Extend* ZWJ x \p{Extended_Pictographic}
+    if (offset_back > start && p1 == U_GCB_ZWJ &&
+        u_hasBinaryProperty(c2, UCHAR_EXTENDED_PICTOGRAPHIC)) {
+        uint32_t c0 = 0;
         size_t offset_backback = offset_back;
-        int32_t p0 = p1;
-        if (p0 == U_GCB_EXTEND && offset_backback > start) {
-            // skip over emoji variation selector
+        int32_t p0 = 0;
+
+        U16_PREV(buf, start, offset_backback, c0);
+        p0 = tailoredGraphemeClusterBreak(c0);
+
+        while (p0 == U_GCB_EXTEND && offset_backback > start) {
             U16_PREV(buf, start, offset_backback, c0);
             p0 = tailoredGraphemeClusterBreak(c0);
         }
-        if (isEmojiBase(c0)) {
+
+        if (u_hasBinaryProperty(c0, UCHAR_EXTENDED_PICTOGRAPHIC)) {
             return false;
         }
     }
-    // Tailored version of Rule GB11, ZWJ × (Glue_After_Zwj | EBG)
-    // We try to make emoji sequences with ZWJ a single grapheme cluster, but only if they actually
-    // merge to one cluster. So we are more relaxed than the UAX #29 rules in accepting any emoji
-    // character after the ZWJ, but are tighter in that we only treat it as one cluster if a
-    // ligature is actually formed and we also require the character before the ZWJ to also be an
-    // emoji.
-    if (p1 == U_GCB_ZWJ && isEmoji(c2) && offset_back > start) {
-        // look at character before ZWJ to see that both can participate in an emoji zwj sequence
-        uint32_t c0 = 0;
-        size_t offset_backback = offset_back;
-        U16_PREV(buf, start, offset_backback, c0);
-        if (c0 == 0xFE0F && offset_backback > start) {
-            // skip over emoji variation selector
-            U16_PREV(buf, start, offset_backback, c0);
-        }
-        if (isEmoji(c0)) {
-            return false;
-        }
-    }
+
     // Tailored version of Rule GB12 and Rule GB13 that look at even-odd cases.
     // sot   (RI RI)*  RI x RI
     // [^RI] (RI RI)*  RI x RI
diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp
index 661a42e..f6952a9 100644
--- a/libs/minikin/GreedyLineBreaker.cpp
+++ b/libs/minikin/GreedyLineBreaker.cpp
@@ -78,7 +78,7 @@
     void updateLineWidth(uint16_t c, float width);
 
     // Break line if current line exceeds the line limit.
-    void processLineBreak(uint32_t offset, WordBreaker* breaker);
+    void processLineBreak(uint32_t offset, WordBreaker* breaker, bool doHyphenation);
 
     // Try to break with previous word boundary.
     // Returns false if unable to break by word boundary.
@@ -96,7 +96,8 @@
     // This method only breaks at the first offset which has the longest width for the line width
     // limit. This method don't keep line breaking even if the rest of the word exceeds the line
     // width limit.
-    void doLineBreakWithGraphemeBounds(const Range& range);
+    // This method return true if there is no characters to be processed.
+    bool doLineBreakWithGraphemeBounds(const Range& range);
 
     // Info about the line currently processing.
     uint32_t mLineNum = 0;
@@ -193,9 +194,9 @@
             continue;  // Not a hyphenation point.
         }
 
-        const float width = targetRun->measureHyphenPiece(mTextBuf, contextRange.split(i).first,
-                                                          mStartHyphenEdit, editForThisLine(hyph),
-                                                          nullptr /* advances */, nullptr);
+        const float width =
+                targetRun->measureHyphenPiece(mTextBuf, contextRange.split(i).first,
+                                              mStartHyphenEdit, editForThisLine(hyph), nullptr);
 
         if (width <= mLineWidthLimit) {
             // There are still space, remember current offset and look up next hyphenation point.
@@ -214,7 +215,7 @@
             const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
             const float remainingCharWidths = targetRun->measureHyphenPiece(
                     mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
-                    EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+                    EndHyphenEdit::NO_EDIT, nullptr);
             breakLineAt(prevOffset, prevWidth,
                         remainingCharWidths - (mSumOfCharWidths - mLineWidth), remainingCharWidths,
                         editForThisLine(hyph), nextLineStartHyphenEdit);
@@ -243,7 +244,7 @@
         const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
         const float remainingCharWidths = targetRun->measureHyphenPiece(
                 mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
-                EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+                EndHyphenEdit::NO_EDIT, nullptr);
 
         breakLineAt(prevOffset, prevWidth, remainingCharWidths - (mSumOfCharWidths - mLineWidth),
                     remainingCharWidths, editForThisLine(hyph), nextLineStartHyphenEdit);
@@ -253,7 +254,7 @@
 }
 
 // TODO: Respect trailing line end spaces.
-void GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
+bool GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
     double width = mMeasuredText.widths[range.getStart()];
 
     // Starting from + 1 since at least one character needs to be assigned to a line.
@@ -269,7 +270,7 @@
 
             // This method only breaks at the first longest offset, since we may want to hyphenate
             // the rest of the word.
-            return;
+            return false;
         } else {
             width += w;
         }
@@ -278,6 +279,7 @@
     // Reaching here means even one character (or cluster) doesn't fit the line.
     // Give up and break at the end of this range.
     breakLineAt(range.getEnd(), mLineWidth, 0, 0, EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
+    return true;
 }
 
 void GreedyLineBreaker::updateLineWidth(uint16_t c, float width) {
@@ -292,15 +294,18 @@
     }
 }
 
-void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker) {
+void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker,
+                                         bool doHyphenation) {
     while (mLineWidth > mLineWidthLimit) {
         const Range lineRange(getPrevLineBreakOffset(), offset);  // The range we need to address.
         if (tryLineBreakWithWordBreak()) {
             continue;  // The word in the new line may still be too long for the line limit.
-        } else if (tryLineBreakWithHyphenation(lineRange, breaker)) {
+        } else if (doHyphenation && tryLineBreakWithHyphenation(lineRange, breaker)) {
             continue;  // TODO: we may be able to return here.
         } else {
-            doLineBreakWithGraphemeBounds(lineRange);
+            if (doLineBreakWithGraphemeBounds(lineRange)) {
+                return;
+            }
         }
     }
 
@@ -337,8 +342,10 @@
             updateLineWidth(mTextBuf[i], mMeasuredText.widths[i]);
 
             if ((i + 1) == nextWordBoundaryOffset) {
-                // Only process line break at word boundary.
-                processLineBreak(i + 1, &wordBreaker);
+                // Only process line break at word boundary and the run can break into some pieces.
+                if (run->canBreak() || nextWordBoundaryOffset == range.getEnd()) {
+                    processLineBreak(i + 1, &wordBreaker, run->canBreak());
+                }
                 nextWordBoundaryOffset = wordBreaker.next();
             }
         }
@@ -359,15 +366,12 @@
     for (const auto& breakPoint : mBreakPoints) {
         // TODO: compute these during line breaking if these takes longer time.
         bool hasTabChar = false;
-        MinikinExtent extent = {0.0, 0.0, 0.0};
         for (uint32_t i = prevBreakOffset; i < breakPoint.offset; ++i) {
-            const uint16_t c = mTextBuf[i];
-            hasTabChar |= c == CHAR_TAB;
-            if (!isLineSpaceExcludeChar(c)) {
-                extent.extendBy(mMeasuredText.extents[i]);
-            }
+            hasTabChar |= mTextBuf[i] == CHAR_TAB;
         }
 
+        MinikinExtent extent =
+                mMeasuredText.getExtent(mTextBuf, Range(prevBreakOffset, breakPoint.offset));
         out.breakPoints.push_back(breakPoint.offset);
         out.widths.push_back(breakPoint.lineWidth);
         out.ascents.push_back(extent.ascent);
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index d05a56e..f37ae1f 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -29,7 +29,6 @@
 #include <log/log.h>
 #include <unicode/ubidi.h>
 #include <unicode/utf16.h>
-#include <utils/JenkinsHash.h>
 #include <utils/LruCache.h>
 
 #include "minikin/Emoji.h"
@@ -39,233 +38,34 @@
 #include "minikin/Macros.h"
 
 #include "BidiUtils.h"
+#include "LayoutSplitter.h"
 #include "LayoutUtils.h"
 #include "LocaleListCache.h"
 #include "MinikinInternal.h"
 
 namespace minikin {
 
-namespace {
-
-struct SkiaArguments {
-    const MinikinFont* font;
-    const MinikinPaint* paint;
-    FontFakery fakery;
-};
-
-static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
-                                                       hb_codepoint_t glyph, void* /* userData */) {
-    SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
-    float advance = args->font->GetHorizontalAdvance(glyph, *args->paint, args->fakery);
-    return 256 * advance + 0.5;
-}
-
-static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
-                                                  hb_codepoint_t /* glyph */,
-                                                  hb_position_t* /* x */, hb_position_t* /* y */,
-                                                  void* /* userData */) {
-    // Just return true, following the way that Harfbuzz-FreeType implementation does.
-    return true;
-}
-
-// TODO: Initialize in Zygote if it helps.
-hb_font_funcs_t* getFontFuncs() {
-    static hb_font_funcs_t* fontFuncs = nullptr;
-    static std::once_flag once;
-    std::call_once(once, [&]() {
-        fontFuncs = hb_font_funcs_create();
-        // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
-        // return the wrong value if the font uses hinting aggressively.
-        hb_font_funcs_set_glyph_h_advance_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
-        hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
-        hb_font_funcs_make_immutable(fontFuncs);
-    });
-    return fontFuncs;
-}
-
-// TODO: Initialize in Zygote if it helps.
-hb_font_funcs_t* getFontFuncsForEmoji() {
-    static hb_font_funcs_t* fontFuncs = nullptr;
-    static std::once_flag once;
-    std::call_once(once, [&]() {
-        fontFuncs = hb_font_funcs_create();
-        // Don't override the h_advance function since we use HarfBuzz's implementation for emoji
-        // for performance reasons.
-        // Note that it is technically possible for a TrueType font to have outline and embedded
-        // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
-        // case.
-        hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
-        hb_font_funcs_make_immutable(fontFuncs);
-    });
-    return fontFuncs;
-}
-
-}  // namespace
-
-void MinikinRect::join(const MinikinRect& r) {
-    if (isEmpty()) {
-        set(r);
-    } else if (!r.isEmpty()) {
-        mLeft = std::min(mLeft, r.mLeft);
-        mTop = std::min(mTop, r.mTop);
-        mRight = std::max(mRight, r.mRight);
-        mBottom = std::max(mBottom, r.mBottom);
-    }
-}
-
-void Layout::reset() {
-    mGlyphs.clear();
-    mFaces.clear();
-    mBounds.setEmpty();
-    mAdvances.clear();
-    mExtents.clear();
-    mAdvance = 0;
-}
-
-static bool isColorBitmapFont(const HbFontUniquePtr& font) {
-    HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T'));
-    return cbdt;
-}
-
-static float HBFixedToFloat(hb_position_t v) {
-    return scalbnf(v, -8);
-}
-
-static hb_position_t HBFloatToFixed(float v) {
-    return scalbnf(v, +8);
-}
-
-void Layout::dump() const {
-    for (size_t i = 0; i < mGlyphs.size(); i++) {
-        const LayoutGlyph& glyph = mGlyphs[i];
-        std::cout << glyph.glyph_id << ": " << glyph.x << ", " << glyph.y << std::endl;
-    }
-}
-
-uint8_t Layout::findOrPushBackFace(const FakedFont& face) {
-    MINIKIN_ASSERT(mFaces.size() < MAX_FAMILY_COUNT, "mFaces must not exceeds %d",
-                   MAX_FAMILY_COUNT);
-    uint8_t ix = 0;
-    for (; ix < mFaces.size(); ix++) {
-        if (mFaces[ix].font == face.font) {
-            return ix;
-        }
-    }
-    ix = mFaces.size();
-    mFaces.push_back(face);
-    return ix;
-}
-
-static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
-    UChar32 result;
-    U16_NEXT(chars, *iter, (ssize_t)len, result);
-    if (U_IS_SURROGATE(result)) {  // isolated surrogate
-        result = 0xFFFDu;          // U+FFFD REPLACEMENT CHARACTER
-    }
-    return (hb_codepoint_t)result;
-}
-
-static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
-    if (size_t(*iter) == len) {
-        return HB_SCRIPT_UNKNOWN;
-    }
-    uint32_t cp = decodeUtf16(chars, len, iter);
-    hb_unicode_funcs_t* unicode_func = hb_unicode_funcs_get_default();
-    hb_script_t current_script = hb_unicode_script(unicode_func, cp);
-    for (;;) {
-        if (size_t(*iter) == len) break;
-        const ssize_t prev_iter = *iter;
-        cp = decodeUtf16(chars, len, iter);
-        const hb_script_t script = hb_unicode_script(unicode_func, cp);
-        if (script != current_script) {
-            if (current_script == HB_SCRIPT_INHERITED || current_script == HB_SCRIPT_COMMON) {
-                current_script = script;
-            } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {
-                continue;
-            } else {
-                *iter = prev_iter;
-                break;
-            }
-        }
-    }
-    if (current_script == HB_SCRIPT_INHERITED) {
-        current_script = HB_SCRIPT_COMMON;
-    }
-
-    return current_script;
-}
-
-/**
- * Disable certain scripts (mostly those with cursive connection) from having letterspacing
- * applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
- */
-static bool isScriptOkForLetterspacing(hb_script_t script) {
-    return !(script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_NKO ||
-             script == HB_SCRIPT_PSALTER_PAHLAVI || script == HB_SCRIPT_MANDAIC ||
-             script == HB_SCRIPT_MONGOLIAN || script == HB_SCRIPT_PHAGS_PA ||
-             script == HB_SCRIPT_DEVANAGARI || script == HB_SCRIPT_BENGALI ||
-             script == HB_SCRIPT_GURMUKHI || script == HB_SCRIPT_MODI ||
-             script == HB_SCRIPT_SHARADA || script == HB_SCRIPT_SYLOTI_NAGRI ||
-             script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM);
-}
-
 void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
                       const MinikinPaint& paint, StartHyphenEdit startHyphen,
                       EndHyphenEdit endHyphen) {
     const uint32_t count = range.getLength();
-    reset();
     mAdvances.resize(count, 0);
-    mExtents.resize(count);
-
+    mGlyphs.reserve(count);
     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,
-                          nullptr);
+                          startHyphen, endHyphen, this, nullptr);
     }
 }
 
-void Layout::doLayoutWithPrecomputedPieces(const U16StringPiece& textBuf, const Range& range,
-                                           Bidi bidiFlags, const MinikinPaint& paint,
-                                           StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
-                                           const LayoutPieces& lpIn) {
-    const uint32_t count = range.getLength();
-    reset();
-    mAdvances.resize(count, 0);
-    mExtents.resize(count);
-
-    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, 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,
-                          LayoutPieces* pieces) {
+                          EndHyphenEdit endHyphen, float* advances) {
     float advance = 0;
     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
         const size_t offset = range.toRangeOffset(runInfo.range.getStart());
         float* advancesForRun = advances ? advances + offset : nullptr;
-        MinikinExtent* extentsForRun = extents ? extents + offset : nullptr;
         advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0, startHyphen,
-                                     endHyphen, nullptr, nullptr, advancesForRun, extentsForRun,
-                                     nullptr, pieces);
+                                     endHyphen, nullptr, advancesForRun);
     }
     return advance;
 }
@@ -273,134 +73,69 @@
 float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
                                 const MinikinPaint& paint, size_t dstStart,
                                 StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
-                                const LayoutPieces* lpIn, Layout* layout, float* advances,
-                                MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
+                                Layout* layout, float* advances) {
     if (!range.isValid()) {
         return 0.0f;  // ICU failed to retrieve the bidi run?
     }
-    const uint16_t* buf = textBuf.data();
-    const uint32_t bufSize = textBuf.size();
-    const uint32_t start = range.getStart();
-    const uint32_t end = range.getEnd();
     float advance = 0;
-    if (!isRtl) {
-        // left to right
-        uint32_t wordstart =
-                start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize);
-        uint32_t wordend;
-        for (size_t iter = start; iter < end; iter = wordend) {
-            wordend = getNextWordBreakForCache(buf, iter, bufSize);
-            const uint32_t wordcount = std::min(end, wordend) - iter;
-            const uint32_t offset = iter - start;
-            advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount,
-                                    wordend - wordstart, isRtl, paint, iter - dstStart,
-                                    // Only apply hyphen to the first or last word in the string.
-                                    iter == start ? startHyphen : StartHyphenEdit::NO_EDIT,
-                                    wordend >= end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn,
-                                    layout, advances ? advances + offset : nullptr,
-                                    extents ? extents + offset : nullptr, bounds, lpOut);
-            wordstart = wordend;
-        }
-    } else {
-        // right to left
-        uint32_t wordstart;
-        uint32_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
-        for (size_t iter = end; iter > start; iter = wordstart) {
-            wordstart = getPrevWordBreakForCache(buf, iter, bufSize);
-            uint32_t bufStart = std::max(start, wordstart);
-            const uint32_t offset = bufStart - start;
-            advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
-                                    wordend - wordstart, isRtl, paint, bufStart - dstStart,
-                                    // Only apply hyphen to the first (rightmost) or last (leftmost)
-                                    // word in the string.
-                                    wordstart <= start ? startHyphen : StartHyphenEdit::NO_EDIT,
-                                    iter == end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn, layout,
-                                    advances ? advances + offset : nullptr,
-                                    extents ? extents + offset : nullptr, bounds, lpOut);
-            wordend = wordstart;
-        }
+    for (const auto[context, piece] : LayoutSplitter(textBuf, range, isRtl)) {
+        // Hyphenation only applies to the start/end of run.
+        const StartHyphenEdit pieceStartHyphen =
+                (piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT;
+        const EndHyphenEdit pieceEndHyphen =
+                (piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT;
+        float* advancesForRun =
+                advances ? advances + (piece.getStart() - range.getStart()) : nullptr;
+        advance += doLayoutWord(textBuf.data() + context.getStart(),
+                                piece.getStart() - context.getStart(), piece.getLength(),
+                                context.getLength(), isRtl, paint, piece.getStart() - dstStart,
+                                pieceStartHyphen, pieceEndHyphen, layout, advancesForRun);
     }
     return advance;
 }
 
 class LayoutAppendFunctor {
 public:
-    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),
-              mPaint(paint),
-              mDir(dir),
-              mStartEdit(startEdit),
-              mEndEdit(endEdit),
-              mLayout(layout),
+    LayoutAppendFunctor(Layout* layout, float* advances, float* totalAdvance, uint32_t outOffset,
+                        float wordSpacing)
+            : mLayout(layout),
               mAdvances(advances),
-              mExtents(extents),
-              mPieces(pieces),
               mTotalAdvance(totalAdvance),
-              mBounds(bounds),
               mOutOffset(outOffset),
               mWordSpacing(wordSpacing) {}
 
-    void operator()(const Layout& layout) {
+    void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
         if (mLayout) {
-            mLayout->appendLayout(layout, mOutOffset, mWordSpacing);
+            mLayout->appendLayout(layoutPiece, mOutOffset, mWordSpacing);
         }
         if (mAdvances) {
-            layout.getAdvances(mAdvances);
+            const std::vector<float>& advances = layoutPiece.advances();
+            std::copy(advances.begin(), advances.end(), mAdvances);
         }
         if (mTotalAdvance) {
-            *mTotalAdvance = layout.getAdvance();
-        }
-        if (mExtents) {
-            layout.getExtents(mExtents);
-        }
-        if (mBounds) {
-            mBounds->join(layout.getBounds());
-        }
-        if (mPieces) {
-            mPieces->insert(mTextBuf, mRange, mPaint, mDir, mStartEdit, mEndEdit, layout);
+            *mTotalAdvance = layoutPiece.advance();
         }
     }
 
 private:
-    const U16StringPiece& mTextBuf;
-    const Range& mRange;
-    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;
 };
 
 float Layout::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, MinikinRect* bounds, LayoutPieces* lpOut) {
+                           StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* layout,
+                           float* advances) {
     float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
-    float totalAdvance;
+    float totalAdvance = 0;
 
     const U16StringPiece textBuf(buf, bufSize);
     const Range range(start, start + count);
-    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);
-    }
+    LayoutAppendFunctor f(layout, advances, &totalAdvance, bufStart, wordSpacing);
+    LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen, f);
 
     if (wordSpacing != 0) {
         totalAdvance += wordSpacing;
@@ -411,414 +146,22 @@
     return totalAdvance;
 }
 
-static void addFeatures(const std::string& str, std::vector<hb_feature_t>* features) {
-    SplitIterator it(str, ',');
-    while (it.hasNext()) {
-        StringPiece featureStr = it.next();
-        static hb_feature_t feature;
-        /* We do not allow setting features on ranges.  As such, reject any
-         * setting that has non-universal range. */
-        if (hb_feature_from_string(featureStr.data(), featureStr.size(), &feature) &&
-            feature.start == 0 && feature.end == (unsigned int)-1) {
-            features->push_back(feature);
-        }
+void Layout::appendLayout(const LayoutPiece& src, size_t start, float extraAdvance) {
+    for (size_t i = 0; i < src.glyphCount(); i++) {
+        mGlyphs.emplace_back(src.fontAt(i), src.glyphIdAt(i), mAdvance + src.pointAt(i).x,
+                             src.pointAt(i).y);
     }
-}
-
-static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) {
-    hb_codepoint_t glyph;
-    if (preferredHyphen == 0x058A    /* ARMENIAN_HYPHEN */
-        || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
-        || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
-        if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
-            return preferredHyphen;
-        } else {
-            // The original hyphen requested was not supported. Let's try and see if the
-            // Unicode hyphen is supported.
-            preferredHyphen = CHAR_HYPHEN;
-        }
-    }
-    if (preferredHyphen == CHAR_HYPHEN) { /* HYPHEN */
-        // Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the preferred hyphen.
-        // Note that we intentionally don't do anything special if the font doesn't have a
-        // HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
-        if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
-            return 0x002D;  // HYPHEN-MINUS
-        }
-    }
-    return preferredHyphen;
-}
-
-template <typename HyphenEdit>
-static inline void addHyphenToHbBuffer(const HbBufferUniquePtr& buffer, const HbFontUniquePtr& font,
-                                       HyphenEdit hyphen, uint32_t cluster) {
-    const uint32_t* chars;
-    size_t size;
-    std::tie(chars, size) = getHyphenString(hyphen);
-    for (size_t i = 0; i < size; i++) {
-        hb_buffer_add(buffer.get(), determineHyphenChar(chars[i], font.get()), cluster);
-    }
-}
-
-// Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
-// to translate cluster values returned by HarfBuzz to input indices.
-static inline uint32_t addToHbBuffer(const HbBufferUniquePtr& buffer, const uint16_t* buf,
-                                     size_t start, size_t count, size_t bufSize,
-                                     ssize_t scriptRunStart, ssize_t scriptRunEnd,
-                                     StartHyphenEdit inStartHyphen, EndHyphenEdit inEndHyphen,
-                                     const HbFontUniquePtr& hbFont) {
-    // Only hyphenate the very first script run for starting hyphens.
-    const StartHyphenEdit startHyphen =
-            (scriptRunStart == 0) ? inStartHyphen : StartHyphenEdit::NO_EDIT;
-    // Only hyphenate the very last script run for ending hyphens.
-    const EndHyphenEdit endHyphen =
-            (static_cast<size_t>(scriptRunEnd) == count) ? inEndHyphen : EndHyphenEdit::NO_EDIT;
-
-    // In the following code, we drop the pre-context and/or post-context if there is a
-    // hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
-    // contexts only for joining scripts at the moment, e.g. to determine if the first or
-    // last letter of a text range to shape should take a joining form based on an
-    // adjacent letter or joiner (that comes from the context).
-    //
-    // TODO: Revisit this for:
-    // 1. Desperate breaks for joining scripts like Arabic (where it may be better to keep
-    //    the context);
-    // 2. Special features like start-of-word font features (not implemented in HarfBuzz
-    //    yet).
-
-    // We don't have any start-of-line replacement edit yet, so we don't need to check for
-    // those.
-    if (isInsertion(startHyphen)) {
-        // A cluster value of zero guarantees that the inserted hyphen will be in the same
-        // cluster with the next codepoint, since there is no pre-context.
-        addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster */);
-    }
-
-    const uint16_t* hbText;
-    int hbTextLength;
-    unsigned int hbItemOffset;
-    unsigned int hbItemLength = scriptRunEnd - scriptRunStart;  // This is >= 1.
-
-    const bool hasEndInsertion = isInsertion(endHyphen);
-    const bool hasEndReplacement = isReplacement(endHyphen);
-    if (hasEndReplacement) {
-        // Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
-        // don't need to worry about non-BMP characters yet since replacements are only done for
-        // code units at the moment.
-        hbItemLength -= 1;
-    }
-
-    if (startHyphen == StartHyphenEdit::NO_EDIT) {
-        // No edit at the beginning. Use the whole pre-context.
-        hbText = buf;
-        hbItemOffset = start + scriptRunStart;
-    } else {
-        // There's an edit at the beginning. Drop the pre-context and start the buffer at where we
-        // want to start shaping.
-        hbText = buf + start + scriptRunStart;
-        hbItemOffset = 0;
-    }
-
-    if (endHyphen == EndHyphenEdit::NO_EDIT) {
-        // No edit at the end, use the whole post-context.
-        hbTextLength = (buf + bufSize) - hbText;
-    } else {
-        // There is an edit at the end. Drop the post-context.
-        hbTextLength = hbItemOffset + hbItemLength;
-    }
-
-    hb_buffer_add_utf16(buffer.get(), hbText, hbTextLength, hbItemOffset, hbItemLength);
-
-    unsigned int numCodepoints;
-    hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer.get(), &numCodepoints);
-
-    // Add the hyphen at the end, if there's any.
-    if (hasEndInsertion || hasEndReplacement) {
-        // When a hyphen is inserted, by assigning the added hyphen and the last
-        // codepoint added to the HarfBuzz buffer to the same cluster, we can make sure
-        // that they always remain in the same cluster, even if the last codepoint gets
-        // merged into another cluster (for example when it's a combining mark).
-        //
-        // When a replacement happens instead, we want it to get the cluster value of
-        // the character it's replacing, which is one "codepoint length" larger than
-        // the last cluster. But since the character replaced is always just one
-        // code unit, we can just add 1.
-        uint32_t hyphenCluster;
-        if (numCodepoints == 0) {
-            // Nothing was added to the HarfBuzz buffer. This can only happen if
-            // we have a replacement that is replacing a one-code unit script run.
-            hyphenCluster = 0;
-        } else {
-            hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t)hasEndReplacement;
-        }
-        addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
-        // Since we have just added to the buffer, cpInfo no longer necessarily points to
-        // the right place. Refresh it.
-        cpInfo = hb_buffer_get_glyph_infos(buffer.get(), nullptr /* we don't need the size */);
-    }
-    return cpInfo[0].cluster;
-}
-
-void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-                         bool isRtl, const MinikinPaint& paint, StartHyphenEdit startHyphen,
-                         EndHyphenEdit endHyphen) {
-    HbBufferUniquePtr buffer(hb_buffer_create());
-    std::vector<FontCollection::Run> items;
-    paint.font->itemize(buf + start, count, paint, &items);
-
-    std::vector<hb_feature_t> features;
-    // Disable default-on non-required ligature features if letter-spacing
-    // See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property
-    // "When the effective spacing between two characters is not zero (due to
-    // either justification or a non-zero value of letter-spacing), user agents
-    // should not apply optional ligatures."
-    if (fabs(paint.letterSpacing) > 0.03) {
-        static const hb_feature_t no_liga = {HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u};
-        static const hb_feature_t no_clig = {HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u};
-        features.push_back(no_liga);
-        features.push_back(no_clig);
-    }
-    addFeatures(paint.fontFeatureSettings, &features);
-
-    std::vector<HbFontUniquePtr> hbFonts;
-    double size = paint.size;
-    double scaleX = paint.scaleX;
-
-    float x = mAdvance;
-    float y = 0;
-    for (int run_ix = isRtl ? items.size() - 1 : 0;
-         isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
-         isRtl ? --run_ix : ++run_ix) {
-        FontCollection::Run& run = items[run_ix];
-        const FakedFont& fakedFont = run.fakedFont;
-        const uint8_t font_ix = findOrPushBackFace(fakedFont);
-        if (hbFonts.size() == font_ix) {  // findOrPushBackFace push backed the new face.
-            // We override some functions which are not thread safe.
-            HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.font->baseFont().get()));
-            hb_font_set_funcs(
-                    font.get(), isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
-                    new SkiaArguments({fakedFont.font->typeface().get(), &paint, fakedFont.fakery}),
-                    [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
-            hbFonts.push_back(std::move(font));
-        }
-        const HbFontUniquePtr& hbFont = hbFonts[font_ix];
-
-        MinikinExtent verticalExtent;
-        fakedFont.font->typeface()->GetFontExtent(&verticalExtent, paint, fakedFont.fakery);
-        std::fill(&mExtents[run.start], &mExtents[run.end], verticalExtent);
-
-        hb_font_set_ppem(hbFont.get(), size * scaleX, size);
-        hb_font_set_scale(hbFont.get(), HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
-
-        const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
-
-        // TODO: if there are multiple scripts within a font in an RTL run,
-        // we need to reorder those runs. This is unlikely with our current
-        // font stack, but should be done for correctness.
-
-        // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
-        // and count.
-        ssize_t scriptRunEnd;
-        for (ssize_t scriptRunStart = run.start; scriptRunStart < run.end;
-             scriptRunStart = scriptRunEnd) {
-            scriptRunEnd = scriptRunStart;
-            hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
-            // After the last line, scriptRunEnd is guaranteed to have increased, since the only
-            // time getScriptRun does not increase its iterator is when it has already reached the
-            // end of the buffer. But that can't happen, since if we have already reached the end
-            // of the buffer, we should have had (scriptRunEnd == run.end), which means
-            // (scriptRunStart == run.end) which is impossible due to the exit condition of the for
-            // loop. So we can be sure that scriptRunEnd > scriptRunStart.
-
-            double letterSpace = 0.0;
-            double letterSpaceHalfLeft = 0.0;
-            double letterSpaceHalfRight = 0.0;
-
-            if (paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
-                letterSpace = paint.letterSpacing * size * scaleX;
-                if ((paint.paintFlags & LinearTextFlag) == 0) {
-                    letterSpace = round(letterSpace);
-                    letterSpaceHalfLeft = floor(letterSpace * 0.5);
-                } else {
-                    letterSpaceHalfLeft = letterSpace * 0.5;
-                }
-                letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
-            }
-
-            hb_buffer_clear_contents(buffer.get());
-            hb_buffer_set_script(buffer.get(), script);
-            hb_buffer_set_direction(buffer.get(), isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
-            const LocaleList& localeList = LocaleListCache::getById(paint.localeListId);
-            if (localeList.size() != 0) {
-                hb_language_t hbLanguage = localeList.getHbLanguage(0);
-                for (size_t i = 0; i < localeList.size(); ++i) {
-                    if (localeList[i].supportsHbScript(script)) {
-                        hbLanguage = localeList.getHbLanguage(i);
-                        break;
-                    }
-                }
-                hb_buffer_set_language(buffer.get(), hbLanguage);
-            }
-
-            const uint32_t clusterStart =
-                    addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart, scriptRunEnd,
-                                  startHyphen, endHyphen, hbFont);
-
-            hb_shape(hbFont.get(), buffer.get(), features.empty() ? NULL : &features[0],
-                     features.size());
-            unsigned int numGlyphs;
-            hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs);
-            hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer.get(), NULL);
-
-            // At this point in the code, the cluster values in the info buffer correspond to the
-            // input characters with some shift. The cluster value clusterStart corresponds to the
-            // first character passed to HarfBuzz, which is at buf[start + scriptRunStart] whose
-            // advance needs to be saved into mAdvances[scriptRunStart]. So cluster values need to
-            // be reduced by (clusterStart - scriptRunStart) to get converted to indices of
-            // mAdvances.
-            const ssize_t clusterOffset = clusterStart - scriptRunStart;
-
-            if (numGlyphs) {
-                mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
-                x += letterSpaceHalfLeft;
-            }
-            for (unsigned int i = 0; i < numGlyphs; i++) {
-                const size_t clusterBaseIndex = info[i].cluster - clusterOffset;
-                if (i > 0 && info[i - 1].cluster != info[i].cluster) {
-                    mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
-                    mAdvances[clusterBaseIndex] += letterSpaceHalfLeft;
-                    x += letterSpace;
-                }
-
-                hb_codepoint_t glyph_ix = info[i].codepoint;
-                float xoff = HBFixedToFloat(positions[i].x_offset);
-                float yoff = -HBFixedToFloat(positions[i].y_offset);
-                xoff += yoff * paint.skewX;
-                LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff};
-                mGlyphs.push_back(glyph);
-                float xAdvance = HBFixedToFloat(positions[i].x_advance);
-                if ((paint.paintFlags & LinearTextFlag) == 0) {
-                    xAdvance = roundf(xAdvance);
-                }
-                MinikinRect glyphBounds;
-                hb_glyph_extents_t extents = {};
-                if (is_color_bitmap_font &&
-                    hb_font_get_glyph_extents(hbFont.get(), glyph_ix, &extents)) {
-                    // Note that it is technically possible for a TrueType font to have outline and
-                    // embedded bitmap at the same time. We ignore modified bbox of hinted outline
-                    // glyphs in that case.
-                    glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));
-                    glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));
-                    glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width));
-                    glyphBounds.mBottom =
-                            roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
-                } else {
-                    fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint,
-                                                          fakedFont.fakery);
-                }
-                glyphBounds.offset(xoff, yoff);
-
-                if (clusterBaseIndex < count) {
-                    mAdvances[clusterBaseIndex] += xAdvance;
-                } else {
-                    ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex,
-                          start, count);
-                }
-                glyphBounds.offset(x, y);
-                mBounds.join(glyphBounds);
-                x += xAdvance;
-            }
-            if (numGlyphs) {
-                mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
-                x += letterSpaceHalfRight;
-            }
-        }
-    }
-    mAdvance = x;
-}
-
-void Layout::appendLayout(const Layout& src, size_t start, float extraAdvance) {
-    int fontMapStack[16];
-    int* fontMap;
-    if (src.mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
-        fontMap = fontMapStack;
-    } else {
-        fontMap = new int[src.mFaces.size()];
-    }
-    for (size_t i = 0; i < src.mFaces.size(); i++) {
-        uint8_t font_ix = findOrPushBackFace(src.mFaces[i]);
-        fontMap[i] = font_ix;
-    }
-    int x0 = mAdvance;
-    for (size_t i = 0; i < src.mGlyphs.size(); i++) {
-        const LayoutGlyph& srcGlyph = src.mGlyphs[i];
-        int font_ix = fontMap[srcGlyph.font_ix];
-        unsigned int glyph_id = srcGlyph.glyph_id;
-        float x = x0 + srcGlyph.x;
-        float y = srcGlyph.y;
-        LayoutGlyph glyph = {font_ix, glyph_id, x, y};
-        mGlyphs.push_back(glyph);
-    }
-    for (size_t i = 0; i < src.mAdvances.size(); i++) {
-        mAdvances[i + start] = src.mAdvances[i];
+    const std::vector<float>& advances = src.advances();
+    for (size_t i = 0; i < advances.size(); i++) {
+        mAdvances[i + start] = advances[i];
         if (i == 0) {
             mAdvances[start] += extraAdvance;
         }
-        mExtents[i + start] = src.mExtents[i];
     }
-    MinikinRect srcBounds(src.mBounds);
-    srcBounds.offset(x0, 0);
+    MinikinRect srcBounds(src.bounds());
+    srcBounds.offset(mAdvance, 0);
     mBounds.join(srcBounds);
-    mAdvance += src.mAdvance + extraAdvance;
-
-    if (fontMap != fontMapStack) {
-        delete[] fontMap;
-    }
-}
-
-size_t Layout::nGlyphs() const {
-    return mGlyphs.size();
-}
-
-const MinikinFont* Layout::getFont(int i) const {
-    const LayoutGlyph& glyph = mGlyphs[i];
-    return mFaces[glyph.font_ix].font->typeface().get();
-}
-
-FontFakery Layout::getFakery(int i) const {
-    const LayoutGlyph& glyph = mGlyphs[i];
-    return mFaces[glyph.font_ix].fakery;
-}
-
-unsigned int Layout::getGlyphId(int i) const {
-    const LayoutGlyph& glyph = mGlyphs[i];
-    return glyph.glyph_id;
-}
-
-float Layout::getX(int i) const {
-    const LayoutGlyph& glyph = mGlyphs[i];
-    return glyph.x;
-}
-
-float Layout::getY(int i) const {
-    const LayoutGlyph& glyph = mGlyphs[i];
-    return glyph.y;
-}
-
-float Layout::getAdvance() const {
-    return mAdvance;
-}
-
-void Layout::getAdvances(float* advances) const {
-    memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float));
-}
-
-void Layout::getExtents(MinikinExtent* extents) const {
-    memcpy(extents, &mExtents[0], mExtents.size() * sizeof(MinikinExtent));
-}
-
-void Layout::getBounds(MinikinRect* bounds) const {
-    bounds->set(mBounds);
+    mAdvance += src.advance() + extraAdvance;
 }
 
 void Layout::purgeCaches() {
diff --git a/libs/minikin/LayoutCore.cpp b/libs/minikin/LayoutCore.cpp
new file mode 100644
index 0000000..ddf31b6
--- /dev/null
+++ b/libs/minikin/LayoutCore.cpp
@@ -0,0 +1,555 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "minikin/LayoutCore.h"
+
+#include <cmath>
+#include <iostream>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <hb-icu.h>
+#include <hb-ot.h>
+#include <log/log.h>
+#include <unicode/ubidi.h>
+#include <unicode/utf16.h>
+#include <utils/LruCache.h>
+
+#include "minikin/Emoji.h"
+#include "minikin/HbUtils.h"
+#include "minikin/LayoutCache.h"
+#include "minikin/LayoutPieces.h"
+#include "minikin/Macros.h"
+
+#include "BidiUtils.h"
+#include "LayoutUtils.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+namespace {
+
+struct SkiaArguments {
+    const MinikinFont* font;
+    const MinikinPaint* paint;
+    FontFakery fakery;
+};
+
+// Returns true if the character needs to be excluded for the line spacing.
+inline bool isLineSpaceExcludeChar(uint16_t c) {
+    return c == CHAR_LINE_FEED || c == CHAR_CARRIAGE_RETURN;
+}
+
+static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
+                                                       hb_codepoint_t glyph, void* /* userData */) {
+    SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
+    float advance = args->font->GetHorizontalAdvance(glyph, *args->paint, args->fakery);
+    return 256 * advance + 0.5;
+}
+
+static void harfbuzzGetGlyphHorizontalAdvances(hb_font_t* /* hbFont */, void* fontData,
+                                               unsigned int count,
+                                               const hb_codepoint_t* first_glyph,
+                                               unsigned glyph_stride, hb_position_t* first_advance,
+                                               unsigned advance_stride, void* /* userData */) {
+    SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
+    std::vector<uint16_t> glyphVec(count);
+    std::vector<float> advVec(count);
+
+    const hb_codepoint_t* glyph = first_glyph;
+    for (uint32_t i = 0; i < count; ++i) {
+        glyphVec[i] = *glyph;
+        glyph = reinterpret_cast<const hb_codepoint_t*>(reinterpret_cast<const uint8_t*>(glyph) +
+                                                        glyph_stride);
+    }
+
+    args->font->GetHorizontalAdvances(glyphVec.data(), count, *args->paint, args->fakery,
+                                      advVec.data());
+
+    hb_position_t* advances = first_advance;
+    for (uint32_t i = 0; i < count; ++i) {
+        *advances = HBFloatToFixed(advVec[i]);
+        advances = reinterpret_cast<hb_position_t*>(reinterpret_cast<uint8_t*>(advances) +
+                                                    advance_stride);
+    }
+}
+
+static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
+                                                  hb_codepoint_t /* glyph */,
+                                                  hb_position_t* /* x */, hb_position_t* /* y */,
+                                                  void* /* userData */) {
+    // Just return true, following the way that Harfbuzz-FreeType implementation does.
+    return true;
+}
+
+hb_font_funcs_t* getFontFuncs() {
+    static hb_font_funcs_t* fontFuncs = nullptr;
+    static std::once_flag once;
+    std::call_once(once, [&]() {
+        fontFuncs = hb_font_funcs_create();
+        // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
+        // return the wrong value if the font uses hinting aggressively.
+        hb_font_funcs_set_glyph_h_advance_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
+        hb_font_funcs_set_glyph_h_advances_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvances, 0,
+                                                0);
+        hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
+        hb_font_funcs_make_immutable(fontFuncs);
+    });
+    return fontFuncs;
+}
+
+hb_font_funcs_t* getFontFuncsForEmoji() {
+    static hb_font_funcs_t* fontFuncs = nullptr;
+    static std::once_flag once;
+    std::call_once(once, [&]() {
+        fontFuncs = hb_font_funcs_create();
+        // Don't override the h_advance function since we use HarfBuzz's implementation for emoji
+        // for performance reasons.
+        // Note that it is technically possible for a TrueType font to have outline and embedded
+        // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
+        // case.
+        hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
+        hb_font_funcs_make_immutable(fontFuncs);
+    });
+    return fontFuncs;
+}
+
+static bool isColorBitmapFont(const HbFontUniquePtr& font) {
+    HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T'));
+    return cbdt;
+}
+
+static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
+    UChar32 result;
+    U16_NEXT(chars, *iter, (ssize_t)len, result);
+    if (U_IS_SURROGATE(result)) {  // isolated surrogate
+        result = 0xFFFDu;          // U+FFFD REPLACEMENT CHARACTER
+    }
+    return (hb_codepoint_t)result;
+}
+
+static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
+    if (size_t(*iter) == len) {
+        return HB_SCRIPT_UNKNOWN;
+    }
+    uint32_t cp = decodeUtf16(chars, len, iter);
+    hb_unicode_funcs_t* unicode_func = hb_unicode_funcs_get_default();
+    hb_script_t current_script = hb_unicode_script(unicode_func, cp);
+    for (;;) {
+        if (size_t(*iter) == len) break;
+        const ssize_t prev_iter = *iter;
+        cp = decodeUtf16(chars, len, iter);
+        const hb_script_t script = hb_unicode_script(unicode_func, cp);
+        if (script != current_script) {
+            if (current_script == HB_SCRIPT_INHERITED || current_script == HB_SCRIPT_COMMON) {
+                current_script = script;
+            } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {
+                continue;
+            } else {
+                *iter = prev_iter;
+                break;
+            }
+        }
+    }
+    if (current_script == HB_SCRIPT_INHERITED) {
+        current_script = HB_SCRIPT_COMMON;
+    }
+
+    return current_script;
+}
+
+/**
+ * Disable certain scripts (mostly those with cursive connection) from having letterspacing
+ * applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
+ */
+static bool isScriptOkForLetterspacing(hb_script_t script) {
+    return !(script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_NKO ||
+             script == HB_SCRIPT_PSALTER_PAHLAVI || script == HB_SCRIPT_MANDAIC ||
+             script == HB_SCRIPT_MONGOLIAN || script == HB_SCRIPT_PHAGS_PA ||
+             script == HB_SCRIPT_DEVANAGARI || script == HB_SCRIPT_BENGALI ||
+             script == HB_SCRIPT_GURMUKHI || script == HB_SCRIPT_MODI ||
+             script == HB_SCRIPT_SHARADA || script == HB_SCRIPT_SYLOTI_NAGRI ||
+             script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM);
+}
+
+static void addFeatures(const std::string& str, std::vector<hb_feature_t>* features) {
+    SplitIterator it(str, ',');
+    while (it.hasNext()) {
+        StringPiece featureStr = it.next();
+        static hb_feature_t feature;
+        /* We do not allow setting features on ranges.  As such, reject any
+         * setting that has non-universal range. */
+        if (hb_feature_from_string(featureStr.data(), featureStr.size(), &feature) &&
+            feature.start == 0 && feature.end == (unsigned int)-1) {
+            features->push_back(feature);
+        }
+    }
+}
+
+static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) {
+    hb_codepoint_t glyph;
+    if (preferredHyphen == 0x058A    /* ARMENIAN_HYPHEN */
+        || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
+        || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
+        if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
+            return preferredHyphen;
+        } else {
+            // The original hyphen requested was not supported. Let's try and see if the
+            // Unicode hyphen is supported.
+            preferredHyphen = CHAR_HYPHEN;
+        }
+    }
+    if (preferredHyphen == CHAR_HYPHEN) { /* HYPHEN */
+        // Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the preferred hyphen.
+        // Note that we intentionally don't do anything special if the font doesn't have a
+        // HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
+        if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
+            return 0x002D;  // HYPHEN-MINUS
+        }
+    }
+    return preferredHyphen;
+}
+
+template <typename HyphenEdit>
+static inline void addHyphenToHbBuffer(const HbBufferUniquePtr& buffer, const HbFontUniquePtr& font,
+                                       HyphenEdit hyphen, uint32_t cluster) {
+    const uint32_t* chars;
+    size_t size;
+    std::tie(chars, size) = getHyphenString(hyphen);
+    for (size_t i = 0; i < size; i++) {
+        hb_buffer_add(buffer.get(), determineHyphenChar(chars[i], font.get()), cluster);
+    }
+}
+
+// Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
+// to translate cluster values returned by HarfBuzz to input indices.
+static inline uint32_t addToHbBuffer(const HbBufferUniquePtr& buffer, const uint16_t* buf,
+                                     size_t start, size_t count, size_t bufSize,
+                                     ssize_t scriptRunStart, ssize_t scriptRunEnd,
+                                     StartHyphenEdit inStartHyphen, EndHyphenEdit inEndHyphen,
+                                     const HbFontUniquePtr& hbFont) {
+    // Only hyphenate the very first script run for starting hyphens.
+    const StartHyphenEdit startHyphen =
+            (scriptRunStart == 0) ? inStartHyphen : StartHyphenEdit::NO_EDIT;
+    // Only hyphenate the very last script run for ending hyphens.
+    const EndHyphenEdit endHyphen =
+            (static_cast<size_t>(scriptRunEnd) == count) ? inEndHyphen : EndHyphenEdit::NO_EDIT;
+
+    // In the following code, we drop the pre-context and/or post-context if there is a
+    // hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
+    // contexts only for joining scripts at the moment, e.g. to determine if the first or
+    // last letter of a text range to shape should take a joining form based on an
+    // adjacent letter or joiner (that comes from the context).
+    //
+    // TODO: Revisit this for:
+    // 1. Desperate breaks for joining scripts like Arabic (where it may be better to keep
+    //    the context);
+    // 2. Special features like start-of-word font features (not implemented in HarfBuzz
+    //    yet).
+
+    // We don't have any start-of-line replacement edit yet, so we don't need to check for
+    // those.
+    if (isInsertion(startHyphen)) {
+        // A cluster value of zero guarantees that the inserted hyphen will be in the same
+        // cluster with the next codepoint, since there is no pre-context.
+        addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster */);
+    }
+
+    const uint16_t* hbText;
+    int hbTextLength;
+    unsigned int hbItemOffset;
+    unsigned int hbItemLength = scriptRunEnd - scriptRunStart;  // This is >= 1.
+
+    const bool hasEndInsertion = isInsertion(endHyphen);
+    const bool hasEndReplacement = isReplacement(endHyphen);
+    if (hasEndReplacement) {
+        // Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
+        // don't need to worry about non-BMP characters yet since replacements are only done for
+        // code units at the moment.
+        hbItemLength -= 1;
+    }
+
+    if (startHyphen == StartHyphenEdit::NO_EDIT) {
+        // No edit at the beginning. Use the whole pre-context.
+        hbText = buf;
+        hbItemOffset = start + scriptRunStart;
+    } else {
+        // There's an edit at the beginning. Drop the pre-context and start the buffer at where we
+        // want to start shaping.
+        hbText = buf + start + scriptRunStart;
+        hbItemOffset = 0;
+    }
+
+    if (endHyphen == EndHyphenEdit::NO_EDIT) {
+        // No edit at the end, use the whole post-context.
+        hbTextLength = (buf + bufSize) - hbText;
+    } else {
+        // There is an edit at the end. Drop the post-context.
+        hbTextLength = hbItemOffset + hbItemLength;
+    }
+
+    hb_buffer_add_utf16(buffer.get(), hbText, hbTextLength, hbItemOffset, hbItemLength);
+
+    unsigned int numCodepoints;
+    hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer.get(), &numCodepoints);
+
+    // Add the hyphen at the end, if there's any.
+    if (hasEndInsertion || hasEndReplacement) {
+        // When a hyphen is inserted, by assigning the added hyphen and the last
+        // codepoint added to the HarfBuzz buffer to the same cluster, we can make sure
+        // that they always remain in the same cluster, even if the last codepoint gets
+        // merged into another cluster (for example when it's a combining mark).
+        //
+        // When a replacement happens instead, we want it to get the cluster value of
+        // the character it's replacing, which is one "codepoint length" larger than
+        // the last cluster. But since the character replaced is always just one
+        // code unit, we can just add 1.
+        uint32_t hyphenCluster;
+        if (numCodepoints == 0) {
+            // Nothing was added to the HarfBuzz buffer. This can only happen if
+            // we have a replacement that is replacing a one-code unit script run.
+            hyphenCluster = 0;
+        } else {
+            hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t)hasEndReplacement;
+        }
+        addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
+        // Since we have just added to the buffer, cpInfo no longer necessarily points to
+        // the right place. Refresh it.
+        cpInfo = hb_buffer_get_glyph_infos(buffer.get(), nullptr /* we don't need the size */);
+    }
+    return cpInfo[0].cluster;
+}
+
+}  // namespace
+
+LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool isRtl,
+                         const MinikinPaint& paint, StartHyphenEdit startHyphen,
+                         EndHyphenEdit endHyphen) {
+    const uint16_t* buf = textBuf.data();
+    const size_t start = range.getStart();
+    const size_t count = range.getLength();
+    const size_t bufSize = textBuf.size();
+
+    mAdvances.resize(count, 0);  // Need zero filling.
+
+    // Usually the number of glyphs are less than number of code units.
+    mFontIndices.reserve(count);
+    mGlyphIds.reserve(count);
+    mPoints.reserve(count);
+
+    HbBufferUniquePtr buffer(hb_buffer_create());
+    std::vector<FontCollection::Run> items = paint.font->itemize(
+            textBuf.substr(range), paint.fontStyle, paint.localeListId, paint.familyVariant);
+
+    std::vector<hb_feature_t> features;
+    // Disable default-on non-required ligature features if letter-spacing
+    // See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property
+    // "When the effective spacing between two characters is not zero (due to
+    // either justification or a non-zero value of letter-spacing), user agents
+    // should not apply optional ligatures."
+    if (fabs(paint.letterSpacing) > 0.03) {
+        static const hb_feature_t no_liga = {HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u};
+        static const hb_feature_t no_clig = {HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u};
+        features.push_back(no_liga);
+        features.push_back(no_clig);
+    }
+    addFeatures(paint.fontFeatureSettings, &features);
+
+    std::vector<HbFontUniquePtr> hbFonts;
+    double size = paint.size;
+    double scaleX = paint.scaleX;
+
+    std::unordered_map<const Font*, uint32_t> fontMap;
+
+    float x = 0;
+    float y = 0;
+    for (int run_ix = isRtl ? items.size() - 1 : 0;
+         isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
+         isRtl ? --run_ix : ++run_ix) {
+        FontCollection::Run& run = items[run_ix];
+        const FakedFont& fakedFont = run.fakedFont;
+        auto it = fontMap.find(fakedFont.font);
+        uint8_t font_ix;
+        if (it == fontMap.end()) {
+            // First time to see this font.
+            font_ix = mFonts.size();
+            mFonts.push_back(fakedFont);
+            fontMap.insert(std::make_pair(fakedFont.font, font_ix));
+
+            // We override some functions which are not thread safe.
+            HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.font->baseFont().get()));
+            hb_font_set_funcs(
+                    font.get(), isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
+                    new SkiaArguments({fakedFont.font->typeface().get(), &paint, fakedFont.fakery}),
+                    [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
+            hbFonts.push_back(std::move(font));
+        } else {
+            font_ix = it->second;
+        }
+        const HbFontUniquePtr& hbFont = hbFonts[font_ix];
+
+        bool needExtent = false;
+        for (int i = run.start; i < run.end; ++i) {
+            if (!isLineSpaceExcludeChar(buf[i])) {
+                needExtent = true;
+                break;
+            }
+        }
+        if (needExtent) {
+            MinikinExtent verticalExtent;
+            fakedFont.font->typeface()->GetFontExtent(&verticalExtent, paint, fakedFont.fakery);
+            mExtent.extendBy(verticalExtent);
+        }
+
+        hb_font_set_ppem(hbFont.get(), size * scaleX, size);
+        hb_font_set_scale(hbFont.get(), HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
+
+        const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
+
+        // TODO: if there are multiple scripts within a font in an RTL run,
+        // we need to reorder those runs. This is unlikely with our current
+        // font stack, but should be done for correctness.
+
+        // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
+        // and count.
+        ssize_t scriptRunEnd;
+        for (ssize_t scriptRunStart = run.start; scriptRunStart < run.end;
+             scriptRunStart = scriptRunEnd) {
+            scriptRunEnd = scriptRunStart;
+            hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
+            // After the last line, scriptRunEnd is guaranteed to have increased, since the only
+            // time getScriptRun does not increase its iterator is when it has already reached the
+            // end of the buffer. But that can't happen, since if we have already reached the end
+            // of the buffer, we should have had (scriptRunEnd == run.end), which means
+            // (scriptRunStart == run.end) which is impossible due to the exit condition of the for
+            // loop. So we can be sure that scriptRunEnd > scriptRunStart.
+
+            double letterSpace = 0.0;
+            double letterSpaceHalfLeft = 0.0;
+            double letterSpaceHalfRight = 0.0;
+
+            if (paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
+                letterSpace = paint.letterSpacing * size * scaleX;
+                if ((paint.fontFlags & LinearMetrics_Flag) == 0) {
+                    letterSpace = round(letterSpace);
+                    letterSpaceHalfLeft = floor(letterSpace * 0.5);
+                } else {
+                    letterSpaceHalfLeft = letterSpace * 0.5;
+                }
+                letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
+            }
+
+            hb_buffer_clear_contents(buffer.get());
+            hb_buffer_set_script(buffer.get(), script);
+            hb_buffer_set_direction(buffer.get(), isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
+            const LocaleList& localeList = LocaleListCache::getById(paint.localeListId);
+            if (localeList.size() != 0) {
+                hb_language_t hbLanguage = localeList.getHbLanguage(0);
+                for (size_t i = 0; i < localeList.size(); ++i) {
+                    if (localeList[i].supportsHbScript(script)) {
+                        hbLanguage = localeList.getHbLanguage(i);
+                        break;
+                    }
+                }
+                hb_buffer_set_language(buffer.get(), hbLanguage);
+            }
+
+            const uint32_t clusterStart =
+                    addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart, scriptRunEnd,
+                                  startHyphen, endHyphen, hbFont);
+
+            hb_shape(hbFont.get(), buffer.get(), features.empty() ? NULL : &features[0],
+                     features.size());
+            unsigned int numGlyphs;
+            hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs);
+            hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer.get(), NULL);
+
+            // At this point in the code, the cluster values in the info buffer correspond to the
+            // input characters with some shift. The cluster value clusterStart corresponds to the
+            // first character passed to HarfBuzz, which is at buf[start + scriptRunStart] whose
+            // advance needs to be saved into mAdvances[scriptRunStart]. So cluster values need to
+            // be reduced by (clusterStart - scriptRunStart) to get converted to indices of
+            // mAdvances.
+            const ssize_t clusterOffset = clusterStart - scriptRunStart;
+
+            if (numGlyphs) {
+                mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
+                x += letterSpaceHalfLeft;
+            }
+            for (unsigned int i = 0; i < numGlyphs; i++) {
+                const size_t clusterBaseIndex = info[i].cluster - clusterOffset;
+                if (i > 0 && info[i - 1].cluster != info[i].cluster) {
+                    mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
+                    mAdvances[clusterBaseIndex] += letterSpaceHalfLeft;
+                    x += letterSpace;
+                }
+
+                hb_codepoint_t glyph_ix = info[i].codepoint;
+                float xoff = HBFixedToFloat(positions[i].x_offset);
+                float yoff = -HBFixedToFloat(positions[i].y_offset);
+                xoff += yoff * paint.skewX;
+                mFontIndices.push_back(font_ix);
+                mGlyphIds.push_back(glyph_ix);
+                mPoints.emplace_back(x + xoff, y + yoff);
+                float xAdvance = HBFixedToFloat(positions[i].x_advance);
+                if ((paint.fontFlags & LinearMetrics_Flag) == 0) {
+                    xAdvance = roundf(xAdvance);
+                }
+                MinikinRect glyphBounds;
+                hb_glyph_extents_t extents = {};
+                if (is_color_bitmap_font &&
+                    hb_font_get_glyph_extents(hbFont.get(), glyph_ix, &extents)) {
+                    // Note that it is technically possible for a TrueType font to have outline and
+                    // embedded bitmap at the same time. We ignore modified bbox of hinted outline
+                    // glyphs in that case.
+                    glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));
+                    glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));
+                    glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width));
+                    glyphBounds.mBottom =
+                            roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
+                } else {
+                    fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint,
+                                                          fakedFont.fakery);
+                }
+                glyphBounds.offset(xoff, yoff);
+
+                if (clusterBaseIndex < count) {
+                    mAdvances[clusterBaseIndex] += xAdvance;
+                } else {
+                    ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex,
+                          start, count);
+                }
+                glyphBounds.offset(x, y);
+                mBounds.join(glyphBounds);
+                x += xAdvance;
+            }
+            if (numGlyphs) {
+                mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
+                x += letterSpaceHalfRight;
+            }
+        }
+    }
+    mFontIndices.shrink_to_fit();
+    mGlyphIds.shrink_to_fit();
+    mPoints.shrink_to_fit();
+    mAdvance = x;
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/LayoutSplitter.h b/libs/minikin/LayoutSplitter.h
new file mode 100644
index 0000000..319a702
--- /dev/null
+++ b/libs/minikin/LayoutSplitter.h
@@ -0,0 +1,128 @@
+/*
+ * 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_SPLITTER_H
+#define MINIKIN_LAYOUT_SPLITTER_H
+
+#define LOG_TAG "Minikin"
+
+#include "minikin/Layout.h"
+
+#include <memory>
+
+#include <unicode/ubidi.h>
+
+#include "minikin/Macros.h"
+#include "minikin/U16StringPiece.h"
+
+#include "LayoutUtils.h"
+
+namespace minikin {
+
+// LayoutSplitter split the input text into recycle-able pieces.
+//
+// LayoutSplitter basically splits the text before and after space characters.
+//
+// Here is an example of how the LayoutSplitter split the text into layout pieces.
+// Input:
+//   Text          : T h i s _ i s _ a n _ e x a m p l e _ t e x t .
+//   Range         :            |-------------------|
+//
+// Output:
+//   Context Range :          |---|-|---|-|-------------|
+//   Piece Range   :            |-|-|---|-|---------|
+//
+// Input:
+//   Text          : T h i s _ i s _ a n _ e x a m p l e _ t e x t .
+//   Range         :                          |-------|
+//
+// Output:
+//   Context Range :                      |-------------|
+//   Piece Range   :                          |-------|
+class LayoutSplitter {
+public:
+    LayoutSplitter(const U16StringPiece& textBuf, const Range& range, bool isRtl)
+            : mTextBuf(textBuf), mRange(range), mIsRtl(isRtl) {}
+
+    class iterator {
+    public:
+        bool operator==(const iterator& o) const { return mPos == o.mPos && mParent == o.mParent; }
+
+        bool operator!=(const iterator& o) const { return !(*this == o); }
+
+        std::pair<Range, Range> operator*() const {
+            return std::make_pair(mContextRange, mPieceRange);
+        }
+
+        iterator& operator++() {
+            const U16StringPiece& textBuf = mParent->mTextBuf;
+            const Range& range = mParent->mRange;
+            if (mParent->mIsRtl) {
+                mPos = mPieceRange.getStart();
+                mContextRange.setStart(getPrevWordBreakForCache(textBuf, mPos));
+                mContextRange.setEnd(mPos);
+                mPieceRange.setStart(std::max(mContextRange.getStart(), range.getStart()));
+                mPieceRange.setEnd(mPos);
+            } else {
+                mPos = mPieceRange.getEnd();
+                mContextRange.setStart(mPos);
+                mContextRange.setEnd(getNextWordBreakForCache(textBuf, mPos));
+                mPieceRange.setStart(mPos);
+                mPieceRange.setEnd(std::min(mContextRange.getEnd(), range.getEnd()));
+            }
+            return *this;
+        }
+
+    private:
+        friend class LayoutSplitter;
+
+        iterator(const LayoutSplitter* parent, uint32_t pos) : mParent(parent), mPos(pos) {
+            const U16StringPiece& textBuf = mParent->mTextBuf;
+            const Range& range = mParent->mRange;
+            if (parent->mIsRtl) {
+                mContextRange.setStart(getPrevWordBreakForCache(textBuf, pos));
+                mContextRange.setEnd(getNextWordBreakForCache(textBuf, pos == 0 ? 0 : pos - 1));
+                mPieceRange.setStart(std::max(mContextRange.getStart(), range.getStart()));
+                mPieceRange.setEnd(pos);
+            } else {
+                mContextRange.setStart(
+                        getPrevWordBreakForCache(textBuf, pos == range.getEnd() ? pos : pos + 1));
+                mContextRange.setEnd(getNextWordBreakForCache(textBuf, pos));
+                mPieceRange.setStart(pos);
+                mPieceRange.setEnd(std::min(mContextRange.getEnd(), range.getEnd()));
+            }
+        }
+
+        const LayoutSplitter* mParent;
+        uint32_t mPos;
+        Range mContextRange;
+        Range mPieceRange;
+    };
+
+    iterator begin() const { return iterator(this, mIsRtl ? mRange.getEnd() : mRange.getStart()); }
+    iterator end() const { return iterator(this, mIsRtl ? mRange.getStart() : mRange.getEnd()); }
+
+private:
+    U16StringPiece mTextBuf;
+    Range mRange;  // The range in the original buffer. Used for range check.
+    bool mIsRtl;   // The paragraph direction.
+
+    MINIKIN_PREVENT_COPY_AND_ASSIGN(LayoutSplitter);
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_LAYOUT_SPLITTER_H
diff --git a/libs/minikin/LayoutUtils.cpp b/libs/minikin/LayoutUtils.cpp
index e79ea8c..3c258cf 100644
--- a/libs/minikin/LayoutUtils.cpp
+++ b/libs/minikin/LayoutUtils.cpp
@@ -48,14 +48,14 @@
 /**
  * Return offset of previous word break. It is either < offset or == 0.
  */
-size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
+uint32_t getPrevWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset) {
     if (offset == 0) return 0;
-    if (offset > len) offset = len;
-    if (isWordBreakBefore(chars[offset - 1])) {
+    if (offset > textBuf.size()) offset = textBuf.size();
+    if (isWordBreakBefore(textBuf[offset - 1])) {
         return offset - 1;
     }
-    for (size_t i = offset - 1; i > 0; i--) {
-        if (isWordBreakBefore(chars[i]) || isWordBreakAfter(chars[i - 1])) {
+    for (uint32_t i = offset - 1; i > 0; i--) {
+        if (isWordBreakBefore(textBuf[i]) || isWordBreakAfter(textBuf[i - 1])) {
             return i;
         }
     }
@@ -65,20 +65,20 @@
 /**
  * Return offset of next word break. It is either > offset or == len.
  */
-size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
-    if (offset >= len) return len;
-    if (isWordBreakAfter(chars[offset])) {
+uint32_t getNextWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset) {
+    if (offset >= textBuf.size()) return textBuf.size();
+    if (isWordBreakAfter(textBuf[offset])) {
         return offset + 1;
     }
-    for (size_t i = offset + 1; i < len; i++) {
+    for (uint32_t i = offset + 1; i < textBuf.size(); i++) {
         // No need to check isWordBreakAfter(chars[i - 1]) since it is checked
         // in previous iteration.  Note that isWordBreakBefore returns true
         // whenever isWordBreakAfter returns true.
-        if (isWordBreakBefore(chars[i])) {
+        if (isWordBreakBefore(textBuf[i])) {
             return i;
         }
     }
-    return len;
+    return textBuf.size();
 }
 
 }  // namespace minikin
diff --git a/libs/minikin/LayoutUtils.h b/libs/minikin/LayoutUtils.h
index 3128148..3c5c4d1 100644
--- a/libs/minikin/LayoutUtils.h
+++ b/libs/minikin/LayoutUtils.h
@@ -19,6 +19,8 @@
 
 #include <cstdint>
 
+#include "minikin/U16StringPiece.h"
+
 namespace minikin {
 
 /*
@@ -33,7 +35,7 @@
  * kerning or complex script processing. This is necessarily a
  * heuristic, but should be accurate most of the time.
  */
-size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
+uint32_t getPrevWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset);
 
 /**
  * Return offset of next word break. It is either > offset or == len.
@@ -42,7 +44,7 @@
  * kerning or complex script processing. This is necessarily a
  * heuristic, but should be accurate most of the time.
  */
-size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
+uint32_t getNextWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset);
 
 }  // namespace minikin
 #endif  // MINIKIN_LAYOUT_UTILS_H
diff --git a/libs/minikin/LineBreakerUtil.h b/libs/minikin/LineBreakerUtil.h
index 64f4371..eb058cc 100644
--- a/libs/minikin/LineBreakerUtil.h
+++ b/libs/minikin/LineBreakerUtil.h
@@ -55,11 +55,6 @@
            || c == 0x3000;
 }
 
-// Returns true if the character needs to be excluded for the line spacing.
-inline bool isLineSpaceExcludeChar(uint16_t c) {
-    return c == CHAR_LINE_FEED || c == CHAR_CARRIAGE_RETURN;
-}
-
 inline Locale getEffectiveLocale(uint32_t localeListId) {
     const LocaleList& localeList = LocaleListCache::getById(localeListId);
     return localeList.empty() ? Locale() : localeList[0];
@@ -89,14 +84,14 @@
         auto hyphenPart = contextRange.split(i);
         U16StringPiece firstText = textBuf.substr(hyphenPart.first);
         U16StringPiece secondText = textBuf.substr(hyphenPart.second);
-        const float first = run.measureHyphenPiece(firstText, Range(0, firstText.size()),
-                                                   StartHyphenEdit::NO_EDIT /* start hyphen edit */,
-                                                   editForThisLine(hyph) /* end hyphen edit */,
-                                                   nullptr /* advances */, pieces);
-        const float second = run.measureHyphenPiece(secondText, Range(0, secondText.size()),
-                                                    editForNextLine(hyph) /* start hyphen edit */,
-                                                    EndHyphenEdit::NO_EDIT /* end hyphen edit */,
-                                                    nullptr /* advances */, pieces);
+        const float first =
+                run.measureHyphenPiece(firstText, Range(0, firstText.size()),
+                                       StartHyphenEdit::NO_EDIT /* start hyphen edit */,
+                                       editForThisLine(hyph) /* end hyphen edit */, pieces);
+        const float second =
+                run.measureHyphenPiece(secondText, Range(0, secondText.size()),
+                                       editForNextLine(hyph) /* start hyphen edit */,
+                                       EndHyphenEdit::NO_EDIT /* end hyphen edit */, pieces);
 
         out->emplace_back(i, hyph, first, second);
     }
@@ -153,7 +148,6 @@
     // The user of CharProcessor must call updateLocaleIfNecessary with valid locale at least one
     // time before feeding characters.
     void updateLocaleIfNecessary(const Run& run) {
-        // Update locale if necessary.
         uint32_t newLocaleListId = run.getLocaleListId();
         if (localeListId != newLocaleListId) {
             Locale locale = getEffectiveLocale(newLocaleListId);
@@ -164,11 +158,13 @@
     }
 
     // Process one character.
-    void feedChar(uint32_t idx, uint16_t c, float w) {
+    void feedChar(uint32_t idx, uint16_t c, float w, bool canBreakHere) {
         if (idx == nextWordBreak) {
-            prevWordBreak = nextWordBreak;
+            if (canBreakHere) {
+                prevWordBreak = nextWordBreak;
+                sumOfCharWidthsAtPrevWordBreak = sumOfCharWidths;
+            }
             nextWordBreak = breaker.next();
-            sumOfCharWidthsAtPrevWordBreak = sumOfCharWidths;
         }
         if (isWordSpace(c)) {
             rawSpaceCount += 1;
diff --git a/libs/minikin/Locale.cpp b/libs/minikin/Locale.cpp
index 4209413..c1ec389 100644
--- a/libs/minikin/Locale.cpp
+++ b/libs/minikin/Locale.cpp
@@ -34,8 +34,9 @@
     return LocaleListCache::getId(locales);
 }
 
-// Check if a language code supports emoji according to its subtag
-static bool isEmojiSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) {
+// Check if a language code supports extension such as emoji and line break etc. according to its
+// subtag
+static bool isSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) {
     if (bufLen < subtagLen) {
         return false;
     }
@@ -206,7 +207,7 @@
         }
     }
 
-    mEmojiStyle = resolveEmojiStyle(input.data(), input.length());
+    resolveUnicodeExtension(input.data(), input.length());
 
 finalize:
     if (mEmojiStyle == EmojiStyle::EMPTY) {
@@ -214,23 +215,58 @@
     }
 }
 
+void Locale::resolveUnicodeExtension(const char* buf, size_t length) {
+    static const char kPrefix[] = "-u-";
+    const char* pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
+    if (pos != buf + length) {
+        pos += strlen(kPrefix);
+        const size_t remainingLength = length - (pos - buf);
+        mLBStyle = resolveLineBreakStyle(pos, remainingLength);
+        mEmojiStyle = resolveEmojiStyle(pos, remainingLength);
+    }
+}
+
 // static
-EmojiStyle Locale::resolveEmojiStyle(const char* buf, size_t length) {
-    // First, lookup emoji subtag.
-    // 10 is the length of "-u-em-text", which is the shortest emoji subtag,
-    // unnecessary comparison can be avoided if total length is smaller than 10.
-    const size_t kMinSubtagLength = 10;
+// Lookup line break subtag and determine the line break style.
+LineBreakStyle Locale::resolveLineBreakStyle(const char* buf, size_t length) {
+    // 8 is the length of "-u-lb-loose", which is the shortest line break subtag,
+    // unnecessary comparison can be avoided if total length is smaller than 11.
+    const size_t kMinSubtagLength = 8;
     if (length >= kMinSubtagLength) {
-        static const char kPrefix[] = "-u-em-";
+        static const char kPrefix[] = "lb-";
         const char* pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
         if (pos != buf + length) {  // found
             pos += strlen(kPrefix);
             const size_t remainingLength = length - (pos - buf);
-            if (isEmojiSubtag(pos, remainingLength, "emoji", 5)) {
+            if (isSubtag(pos, remainingLength, "loose", 5)) {
+                return LineBreakStyle::LOOSE;
+            } else if (isSubtag(pos, remainingLength, "normal", 6)) {
+                return LineBreakStyle::NORMAL;
+            } else if (isSubtag(pos, remainingLength, "strict", 6)) {
+                return LineBreakStyle::STRICT;
+            }
+        }
+    }
+    return LineBreakStyle::EMPTY;
+}
+
+// static
+// Lookup emoji subtag and determine the emoji style.
+EmojiStyle Locale::resolveEmojiStyle(const char* buf, size_t length) {
+    // 7 is the length of "-u-em-text", which is the shortest emoji subtag,
+    // unnecessary comparison can be avoided if total length is smaller than 10.
+    const size_t kMinSubtagLength = 7;
+    if (length >= kMinSubtagLength) {
+        static const char kPrefix[] = "em-";
+        const char* pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
+        if (pos != buf + length) {  // found
+            pos += strlen(kPrefix);
+            const size_t remainingLength = length - (pos - buf);
+            if (isSubtag(pos, remainingLength, "emoji", 5)) {
                 return EmojiStyle::EMOJI;
-            } else if (isEmojiSubtag(pos, remainingLength, "text", 4)) {
+            } else if (isSubtag(pos, remainingLength, "text", 4)) {
                 return EmojiStyle::TEXT;
-            } else if (isEmojiSubtag(pos, remainingLength, "default", 7)) {
+            } else if (isSubtag(pos, remainingLength, "default", 7)) {
                 return EmojiStyle::DEFAULT;
             }
         }
@@ -291,7 +327,7 @@
 }
 
 std::string Locale::getString() const {
-    char buf[24];
+    char buf[32] = {};
     size_t i;
     if (mLanguage == NO_LANGUAGE) {
         buf[0] = 'u';
@@ -330,6 +366,42 @@
                 MINIKIN_ASSERT(false, "Must not reached.");
         }
     }
+    // Add line break unicode extension.
+    if (mLBStyle != LineBreakStyle::EMPTY) {
+        buf[i++] = '-';
+        buf[i++] = 'u';
+        buf[i++] = '-';
+        buf[i++] = 'l';
+        buf[i++] = 'b';
+        buf[i++] = '-';
+        switch (mLBStyle) {
+            case LineBreakStyle::LOOSE:
+                buf[i++] = 'l';
+                buf[i++] = 'o';
+                buf[i++] = 'o';
+                buf[i++] = 's';
+                buf[i++] = 'e';
+                break;
+            case LineBreakStyle::NORMAL:
+                buf[i++] = 'n';
+                buf[i++] = 'o';
+                buf[i++] = 'r';
+                buf[i++] = 'm';
+                buf[i++] = 'a';
+                buf[i++] = 'l';
+                break;
+            case LineBreakStyle::STRICT:
+                buf[i++] = 's';
+                buf[i++] = 't';
+                buf[i++] = 'r';
+                buf[i++] = 'i';
+                buf[i++] = 'c';
+                buf[i++] = 't';
+                break;
+            default:
+                MINIKIN_ASSERT(false, "Must not reached.");
+        }
+    }
     return std::string(buf, i);
 }
 
diff --git a/libs/minikin/Locale.h b/libs/minikin/Locale.h
index f030f92..8052d6c 100644
--- a/libs/minikin/Locale.h
+++ b/libs/minikin/Locale.h
@@ -63,12 +63,20 @@
     TEXT = 3,     // Text (black/white) emoji style is specified.
 };
 
+// Enum for line break style.
+enum class LineBreakStyle : uint8_t {
+    EMPTY = 0,   // No line break style is specified.
+    LOOSE = 1,   // line break style is loose.
+    NORMAL = 2,  // line break style is normal.
+    STRICT = 3,  // line break style is strict.
+};
+
 // Locale is a compact representation of a BCP 47 language tag.
 // It does not capture all possible information, only what directly affects text layout:
 // font rendering, hyphenation, word breaking, etc.
 struct Locale {
 public:
-    enum class Variant : uint16_t {  // Up to 12 bits
+    enum class Variant : uint16_t {
         NO_VARIANT = 0x0000,
         GERMAN_1901_ORTHOGRAPHY = 0x0001,
         GERMAN_1996_ORTHOGRAPHY = 0x0002,
@@ -81,7 +89,8 @@
               mRegion(NO_REGION),
               mSubScriptBits(0ul),
               mVariant(Variant::NO_VARIANT),
-              mEmojiStyle(EmojiStyle::EMPTY) {}
+              mEmojiStyle(EmojiStyle::EMPTY),
+              mLBStyle(LineBreakStyle::EMPTY) {}
 
     // Parse from string
     Locale(const StringPiece& buf);
@@ -89,7 +98,7 @@
     bool operator==(const Locale other) const {
         return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
                mRegion == other.mRegion && mVariant == other.mVariant &&
-               mEmojiStyle == other.mEmojiStyle;
+               mLBStyle == other.mLBStyle && mEmojiStyle == other.mEmojiStyle;
     }
 
     bool operator!=(const Locale other) const { return !(*this == other); }
@@ -98,10 +107,12 @@
     inline bool hasScript() const { return mScript != NO_SCRIPT; }
     inline bool hasRegion() const { return mRegion != NO_REGION; }
     inline bool hasVariant() const { return mVariant != Variant::NO_VARIANT; }
+    inline bool hasLBStyle() const { return mLBStyle != LineBreakStyle::EMPTY; }
     inline bool hasEmojiStyle() const { return mEmojiStyle != EmojiStyle::EMPTY; }
 
     inline bool isSupported() const {
-        return hasLanguage() || hasScript() || hasRegion() || hasVariant() || hasEmojiStyle();
+        return hasLanguage() || hasScript() || hasRegion() || hasVariant() || hasLBStyle() ||
+               hasEmojiStyle();
     }
 
     inline bool isUnsupported() const { return !isSupported(); }
@@ -121,9 +132,18 @@
     // 0 = no match, 1 = script match, 2 = script and primary language match.
     int calcScoreFor(const LocaleList& supported) const;
 
+    // Identifier pattern:
+    // |-------|-------|-------|-------|-------|-------|-------|-------|
+    // lllllllllllllll                                                   Language Code
+    //                ssssssssssssssssssss                               Script Code
+    //                                    rrrrrrrrrrrrrrr                Region Code
+    //                                                   ee              Emoji Style
+    //                                                     bb            Line Break Style
+    //                                                       XXXXXXXX    Free
+    //                                                               vv  German Variant
     uint64_t getIdentifier() const {
         return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 29) | ((uint64_t)mRegion << 14) |
-               ((uint64_t)mEmojiStyle << 12) | (uint64_t)mVariant;
+               ((uint64_t)mEmojiStyle << 12) | ((uint64_t)mLBStyle << 10) | (uint64_t)mVariant;
     }
 
     Locale getPartialLocale(SubtagBits bits) const;
@@ -157,9 +177,13 @@
     Variant mVariant;
 
     EmojiStyle mEmojiStyle;
+    LineBreakStyle mLBStyle;
+
+    void resolveUnicodeExtension(const char* buf, size_t length);
 
     static uint8_t scriptToSubScriptBits(uint32_t rawScript);
 
+    static LineBreakStyle resolveLineBreakStyle(const char* buf, size_t length);
     static EmojiStyle resolveEmojiStyle(const char* buf, size_t length);
     static EmojiStyle scriptToEmojiStyle(uint32_t script);
 
diff --git a/libs/minikin/MeasuredText.cpp b/libs/minikin/MeasuredText.cpp
index bbc6091..e5c8dd5 100644
--- a/libs/minikin/MeasuredText.cpp
+++ b/libs/minikin/MeasuredText.cpp
@@ -19,30 +19,130 @@
 
 #include "minikin/Layout.h"
 
+#include "BidiUtils.h"
+#include "LayoutSplitter.h"
 #include "LayoutUtils.h"
 #include "LineBreakerUtil.h"
 
 namespace minikin {
 
+// Helper class for composing character advances.
+class AdvancesCompositor {
+public:
+    AdvancesCompositor(std::vector<float>* outAdvances, LayoutPieces* outPieces)
+            : mOutAdvances(outAdvances), mOutPieces(outPieces) {}
+
+    void setNextRange(const Range& range, bool dir) {
+        mRange = range;
+        mDir = dir;
+    }
+
+    void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
+        const std::vector<float>& advances = layoutPiece.advances();
+        std::copy(advances.begin(), advances.end(), mOutAdvances->begin() + mRange.getStart());
+
+        if (mOutPieces != nullptr) {
+            mOutPieces->insert(mRange, 0 /* no edit */, layoutPiece, mDir, paint);
+        }
+    }
+
+private:
+    Range mRange;
+    bool mDir;
+    std::vector<float>* mOutAdvances;
+    LayoutPieces* mOutPieces;
+};
+
+void StyleRun::getMetrics(const U16StringPiece& textBuf, std::vector<float>* advances,
+                          LayoutPieces* precomputed, LayoutPieces* outPieces) const {
+    AdvancesCompositor compositor(advances, outPieces);
+    const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+    const uint32_t paintId =
+            (precomputed == nullptr) ? LayoutPieces::kNoPaintId : precomputed->findPaintId(mPaint);
+    for (const BidiText::RunInfo info : BidiText(textBuf, mRange, bidiFlag)) {
+        for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+            compositor.setNextRange(piece, info.isRtl);
+            if (paintId == LayoutPieces::kNoPaintId) {
+                LayoutCache::getInstance().getOrCreate(
+                        textBuf.substr(context), piece - context.getStart(), mPaint, info.isRtl,
+                        StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, compositor);
+            } else {
+                precomputed->getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
+                                         StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
+                                         compositor);
+            }
+        }
+    }
+}
+
+// Helper class for composing total amount of advance
+class TotalAdvanceCompositor {
+public:
+    TotalAdvanceCompositor(LayoutPieces* outPieces) : mTotalAdvance(0), mOutPieces(outPieces) {}
+
+    void setNextContext(const Range& range, HyphenEdit edit, bool dir) {
+        mRange = range;
+        mEdit = edit;
+        mDir = dir;
+    }
+
+    void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
+        mTotalAdvance += layoutPiece.advance();
+        if (mOutPieces != nullptr) {
+            mOutPieces->insert(mRange, mEdit, layoutPiece, mDir, paint);
+        }
+    }
+
+    float advance() const { return mTotalAdvance; }
+
+private:
+    float mTotalAdvance;
+    Range mRange;
+    HyphenEdit mEdit;
+    bool mDir;
+    LayoutPieces* mOutPieces;
+};
+
+float StyleRun::measureHyphenPiece(const U16StringPiece& textBuf, const Range& range,
+                                   StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+                                   LayoutPieces* pieces) const {
+    TotalAdvanceCompositor compositor(pieces);
+    const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+    for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+        for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+            const StartHyphenEdit startEdit =
+                    piece.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
+            const EndHyphenEdit endEdit =
+                    piece.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
+
+            compositor.setNextContext(piece, packHyphenEdit(startEdit, endEdit), info.isRtl);
+            LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
+                                                   piece - context.getStart(), mPaint, info.isRtl,
+                                                   startEdit, endEdit, compositor);
+        }
+    }
+    return compositor.advance();
+}
+
 void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation,
-                           bool computeLayout) {
+                           bool computeLayout, MeasuredText* hint) {
     if (textBuf.size() == 0) {
         return;
     }
+
     LayoutPieces* piecesOut = computeLayout ? &layoutPieces : nullptr;
     CharProcessor proc(textBuf);
     for (const auto& run : runs) {
         const Range& range = run->getRange();
-        const uint32_t runOffset = range.getStart();
-        run->getMetrics(textBuf, widths.data() + runOffset, extents.data() + runOffset, piecesOut);
+        run->getMetrics(textBuf, &widths, hint ? &hint->layoutPieces : nullptr, piecesOut);
 
-        if (!computeHyphenation || !run->canHyphenate()) {
+        if (!computeHyphenation || !run->canBreak()) {
             continue;
         }
 
         proc.updateLocaleIfNecessary(*run);
         for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
-            proc.feedChar(i, textBuf[i], widths[i]);
+            proc.feedChar(i, textBuf[i], widths[i], run->canBreak());
 
             const uint32_t nextCharOffset = i + 1;
             if (nextCharOffset != proc.nextWordBreak) {
@@ -55,30 +155,171 @@
     }
 }
 
-void MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
-                               const MinikinPaint& paint, Bidi bidiFlags,
-                               StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
-                               Layout* layout) {
-    layout->doLayoutWithPrecomputedPieces(textBuf, range, bidiFlags, paint, startHyphen, endHyphen,
-                                          layoutPieces);
+// Helper class for composing Layout object.
+class LayoutCompositor {
+public:
+    LayoutCompositor(Layout* outLayout, float extraAdvance)
+            : mOutLayout(outLayout), mExtraAdvance(extraAdvance) {}
+
+    void setOutOffset(uint32_t outOffset) { mOutOffset = outOffset; }
+
+    void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
+        mOutLayout->appendLayout(layoutPiece, mOutOffset, mExtraAdvance);
+    }
+
+    uint32_t mOutOffset;
+    Layout* mOutLayout;
+    float mExtraAdvance;
+};
+
+void StyleRun::appendLayout(const U16StringPiece& textBuf, const Range& range,
+                            const Range& /* context */, const LayoutPieces& pieces,
+                            const MinikinPaint& paint, uint32_t outOrigin,
+                            StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+                            Layout* outLayout) const {
+    float wordSpacing = range.getLength() == 1 && isWordSpace(textBuf[range.getStart()])
+                                ? mPaint.wordSpacing
+                                : 0;
+    bool canUsePrecomputedResult = mPaint == paint;
+
+    LayoutCompositor compositor(outLayout, wordSpacing);
+    const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+    const uint32_t paintId = pieces.findPaintId(mPaint);
+    for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+        for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+            compositor.setOutOffset(piece.getStart() - outOrigin);
+            const StartHyphenEdit startEdit =
+                    range.getStart() == piece.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
+            const EndHyphenEdit endEdit =
+                    range.getEnd() == piece.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
+
+            if (canUsePrecomputedResult) {
+                pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl, startEdit, endEdit,
+                                   paintId, compositor);
+            } else {
+                LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
+                                                       piece - context.getStart(), paint,
+                                                       info.isRtl, startEdit, endEdit, compositor);
+            }
+        }
+    }
 }
 
-MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) {
-    MinikinRect rect;
-    float advance = 0.0f;
+// Helper class for composing bounding box.
+class BoundsCompositor {
+public:
+    BoundsCompositor() : mAdvance(0) {}
+
+    void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
+        MinikinRect tmpBounds = layoutPiece.bounds();
+        tmpBounds.offset(mAdvance, 0);
+        mBounds.join(tmpBounds);
+        mAdvance += layoutPiece.advance();
+    }
+
+    const MinikinRect& bounds() const { return mBounds; }
+    float advance() const { return mAdvance; }
+
+private:
+    float mAdvance;
+    MinikinRect mBounds;
+};
+
+std::pair<float, MinikinRect> StyleRun::getBounds(const U16StringPiece& textBuf, const Range& range,
+                                                  const LayoutPieces& pieces) const {
+    BoundsCompositor compositor;
+    const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+    const uint32_t paintId = pieces.findPaintId(mPaint);
+    for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+        for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+            pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
+                               StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
+                               compositor);
+        }
+    }
+    return std::make_pair(compositor.advance(), compositor.bounds());
+}
+
+// Helper class for composing total extent.
+class ExtentCompositor {
+public:
+    ExtentCompositor() {}
+
+    void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
+        mExtent.extendBy(layoutPiece.extent());
+    }
+
+    const MinikinExtent& extent() const { return mExtent; }
+
+private:
+    MinikinExtent mExtent;
+};
+
+MinikinExtent StyleRun::getExtent(const U16StringPiece& textBuf, const Range& range,
+                                  const LayoutPieces& pieces) const {
+    ExtentCompositor compositor;
+    Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+    const uint32_t paintId = pieces.findPaintId(mPaint);
+    for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+        for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+            pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
+                               StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
+                               compositor);
+        }
+    }
+    return compositor.extent();
+}
+
+Layout MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
+                                 const Range& contextRange, const MinikinPaint& paint,
+                                 StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) {
+    Layout outLayout(range.getLength());
     for (const auto& run : runs) {
         const Range& runRange = run->getRange();
         if (!Range::intersects(range, runRange)) {
             continue;
         }
-        std::pair<float, MinikinRect> next =
+        const Range targetRange = Range::intersection(runRange, range);
+        StartHyphenEdit startEdit =
+                targetRange.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
+        EndHyphenEdit endEdit =
+                targetRange.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
+        run->appendLayout(textBuf, targetRange, contextRange, layoutPieces, paint, range.getStart(),
+                          startEdit, endEdit, &outLayout);
+    }
+    return outLayout;
+}
+
+MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) const {
+    MinikinRect rect;
+    float totalAdvance = 0.0f;
+
+    for (const auto& run : runs) {
+        const Range& runRange = run->getRange();
+        if (!Range::intersects(range, runRange)) {
+            continue;
+        }
+        auto[advance, bounds] =
                 run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
-        MinikinRect nextRect = next.second;
-        nextRect.offset(advance, 0);
-        rect.join(nextRect);
-        advance += next.first;
+        bounds.offset(totalAdvance, 0);
+        rect.join(bounds);
+        totalAdvance += advance;
     }
     return rect;
 }
 
+MinikinExtent MeasuredText::getExtent(const U16StringPiece& textBuf, const Range& range) const {
+    MinikinExtent extent;
+    for (const auto& run : runs) {
+        const Range& runRange = run->getRange();
+        if (!Range::intersects(range, runRange)) {
+            continue;
+        }
+        MinikinExtent runExtent =
+                run->getExtent(textBuf, Range::intersection(runRange, range), layoutPieces);
+        extent.extendBy(runExtent);
+    }
+    return extent;
+}
+
 }  // namespace minikin
diff --git a/libs/minikin/OptimalLineBreaker.cpp b/libs/minikin/OptimalLineBreaker.cpp
index 5b3a6fc..3e6319b 100644
--- a/libs/minikin/OptimalLineBreaker.cpp
+++ b/libs/minikin/OptimalLineBreaker.cpp
@@ -221,7 +221,7 @@
 
         // Compute penalty parameters.
         float hyphenPenalty = 0.0f;
-        if (run->canHyphenate()) {
+        if (run->canBreak()) {
             auto penalties = computePenalties(*run, lineWidth, frequency, isJustified);
             hyphenPenalty = penalties.first;
             result.linePenalty = std::max(penalties.second, result.linePenalty);
@@ -231,7 +231,7 @@
 
         for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
             MINIKIN_ASSERT(textBuf[i] != CHAR_TAB, "TAB is not supported in optimal line breaker");
-            proc.feedChar(i, textBuf[i], measured.widths[i]);
+            proc.feedChar(i, textBuf[i], measured.widths[i], run->canBreak());
 
             const uint32_t nextCharOffset = i + 1;
             if (nextCharOffset != proc.nextWordBreak) {
@@ -255,7 +255,8 @@
                               proc, hyphenPenalty, isRtl, &result);
 
             // We skip breaks for zero-width characters inside replacement spans.
-            if (nextCharOffset == range.getEnd() || measured.widths[nextCharOffset] > 0) {
+            if (run->getPaint() != nullptr || nextCharOffset == range.getEnd() ||
+                measured.widths[nextCharOffset] > 0) {
                 const float penalty = hyphenPenalty * proc.wordBreakPenalty();
                 result.pushWordBreak(nextCharOffset, proc.sumOfCharWidths, proc.effectiveWidth,
                                      penalty, proc.rawSpaceCount, proc.effectiveSpaceCount, isRtl);
@@ -284,25 +285,8 @@
     LineBreakResult finishBreaksOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
                                         const std::vector<OptimalBreaksData>& breaksData,
                                         const std::vector<Candidate>& candidates);
-
-    MinikinExtent computeMaxExtent(const U16StringPiece& textBuf, const MeasuredText& measured,
-                                   uint32_t start, uint32_t end) const;
 };
 
-// Find the needed extent between the start and end ranges. start is inclusive and end is exclusive.
-// Both are indices of the source string.
-MinikinExtent LineBreakOptimizer::computeMaxExtent(const U16StringPiece& textBuf,
-                                                   const MeasuredText& measured, uint32_t start,
-                                                   uint32_t end) const {
-    MinikinExtent res = {0.0, 0.0, 0.0};
-    for (uint32_t j = start; j < end; j++) {
-        if (!isLineSpaceExcludeChar(textBuf[j])) {
-            res.extendBy(measured.extents[j]);
-        }
-    }
-    return res;
-}
-
 // Follow "prev" links in candidates array, and copy to result arrays.
 LineBreakResult LineBreakOptimizer::finishBreaksOptimal(
         const U16StringPiece& textBuf, const MeasuredText& measured,
@@ -318,7 +302,7 @@
 
         result.breakPoints.push_back(cand.offset);
         result.widths.push_back(cand.postBreak - prev.preBreak);
-        MinikinExtent extent = computeMaxExtent(textBuf, measured, prev.offset, cand.offset);
+        MinikinExtent extent = measured.getExtent(textBuf, Range(prev.offset, cand.offset));
         result.ascents.push_back(extent.ascent);
         result.descents.push_back(extent.descent);
 
diff --git a/libs/minikin/SystemFonts.cpp b/libs/minikin/SystemFonts.cpp
new file mode 100644
index 0000000..287fc61
--- /dev/null
+++ b/libs/minikin/SystemFonts.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "minikin/SystemFonts.h"
+
+namespace minikin {
+
+SystemFonts& SystemFonts::getInstance() {
+    static SystemFonts systemFonts;
+    return systemFonts;
+}
+
+std::shared_ptr<FontCollection> SystemFonts::findFontCollectionInternal(
+        const std::string& familyName) const {
+    auto it = mSystemFallbacks.find(familyName);
+    if (it != mSystemFallbacks.end()) {
+        return it->second;
+    }
+    // TODO: Lookup by PostScript name.
+    return mDefaultFallback;
+}
+
+}  // namespace minikin
diff --git a/tests/Android.bp b/tests/Android.bp
index 9f6d534..1d8c019 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -8,10 +8,13 @@
         "data/Cherokee.ttf",
         "data/ColorEmojiFont.ttf",
         "data/ColorTextMixedEmojiFont.ttf",
+        "data/CustomExtent.ttf",
         "data/Emoji.ttf",
+        "data/Hiragana.ttf",
         "data/Italic.ttf",
         "data/Ja.ttf",
         "data/Ko.ttf",
+        "data/Ligature.ttf",
         "data/LayoutTestFont.ttf",
         "data/MultiAxis.ttf",
         "data/NoCmapFormat14.ttf",
diff --git a/tests/data/CustomExtent.ttf b/tests/data/CustomExtent.ttf
new file mode 100644
index 0000000..7079677
--- /dev/null
+++ b/tests/data/CustomExtent.ttf
Binary files differ
diff --git a/tests/data/CustomExtent.ttx b/tests/data/CustomExtent.ttx
new file mode 100644
index 0000000..1aba9df
--- /dev/null
+++ b/tests/data/CustomExtent.ttx
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="100"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="0"/>
+    <descent value="0"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="1600"/>
+    <sTypoDescender value="-400"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="50" lsb="0"/>
+    <mtx name="1em" width="100" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+      <map code="0x3042" name="1em" /> <!-- HIRAGANA LETTER A -->
+      <map code="0x3044" name="1em" /> <!-- HIRAGANA LETTER I -->
+      <map code="0x3046" name="1em" /> <!-- HIRAGANA LETTER U -->
+      <map code="0x3048" name="1em" /> <!-- HIRAGANA LETTER E -->
+      <map code="0x304A" name="1em" /> <!-- HIRAGANA LETTER O -->
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="100" yMax="100">
+        <contour>
+            <pt x="0" y="0" on="1" />
+            <pt x="100" y="0" on="1" />
+            <pt x="100" y="100" on="1" />
+            <pt x="0" y="100" on="1" />
+        </contour>
+        <instructions><assembly></assembly></instructions>
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for Hiragana
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for Hiragana
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/data/Hiragana.ttf b/tests/data/Hiragana.ttf
new file mode 100644
index 0000000..9634f0f
--- /dev/null
+++ b/tests/data/Hiragana.ttf
Binary files differ
diff --git a/tests/data/Hiragana.ttx b/tests/data/Hiragana.ttx
new file mode 100644
index 0000000..f0f5c44
--- /dev/null
+++ b/tests/data/Hiragana.ttx
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="2em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="100"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="0"/>
+    <descent value="0"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="1600"/>
+    <sTypoDescender value="-400"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="50" lsb="0"/>
+    <mtx name="2em" width="200" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+      <map code="0x3042" name="2em" /> <!-- HIRAGANA LETTER A -->
+      <map code="0x3044" name="2em" /> <!-- HIRAGANA LETTER I -->
+      <map code="0x3046" name="2em" /> <!-- HIRAGANA LETTER U -->
+      <map code="0x3048" name="2em" /> <!-- HIRAGANA LETTER E -->
+      <map code="0x304A" name="2em" /> <!-- HIRAGANA LETTER O -->
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="2em" xMin="0" yMin="0" xMax="200" yMax="200">
+        <contour>
+            <pt x="0" y="0" on="1" />
+            <pt x="200" y="0" on="1" />
+            <pt x="200" y="200" on="1" />
+            <pt x="0" y="200" on="1" />
+        </contour>
+        <instructions><assembly></assembly></instructions>
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for Hiragana
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for Hiragana
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/data/LayoutTestFont.ttf b/tests/data/LayoutTestFont.ttf
index 7076023..2b7cc4b 100644
--- a/tests/data/LayoutTestFont.ttf
+++ b/tests/data/LayoutTestFont.ttf
Binary files differ
diff --git a/tests/data/LayoutTestFont.ttx b/tests/data/LayoutTestFont.ttx
index 7971a03..22f66f0 100644
--- a/tests/data/LayoutTestFont.ttx
+++ b/tests/data/LayoutTestFont.ttx
@@ -156,13 +156,69 @@
   <glyf>
     <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
     <TTGlyph name="0em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="7em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="10em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="50em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="100em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="100" yMax="100">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="0" y="100" on="1" />
+        <pt x="100" y="100" on="1" />
+        <pt x="100" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="300" yMax="300">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="0" y="300" on="1" />
+        <pt x="300" y="300" on="1" />
+        <pt x="300" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="5em" xMin="0" yMin="0" xMax="500" yMax="500">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="0" y="500" on="1" />
+        <pt x="500" y="500" on="1" />
+        <pt x="500" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="7em" xMin="0" yMin="0" xMax="700" yMax="700">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="0" y="700" on="1" />
+        <pt x="700" y="700" on="1" />
+        <pt x="700" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="10em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="0" y="1000" on="1" />
+        <pt x="1000" y="1000" on="1" />
+        <pt x="1000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="50em" xMin="0" yMin="0" xMax="5000" yMax="5000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="0" y="5000" on="1" />
+        <pt x="5000" y="5000" on="1" />
+        <pt x="5000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="100em" xMin="0" yMin="0" xMax="10000" yMax="10000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="0" y="10000" on="1" />
+        <pt x="10000" y="10000" on="1" />
+        <pt x="10000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
   </glyf>
 
   <name>
diff --git a/tests/data/Ligature.ttf b/tests/data/Ligature.ttf
new file mode 100644
index 0000000..a7503f0
--- /dev/null
+++ b/tests/data/Ligature.ttf
Binary files differ
diff --git a/tests/data/Ligature.ttx b/tests/data/Ligature.ttx
new file mode 100644
index 0000000..1f494a2
--- /dev/null
+++ b/tests/data/Ligature.ttx
@@ -0,0 +1,362 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="f"/>
+    <GlyphID id="3" name="i"/>
+    <GlyphID id="4" name="fi"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="100"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="0"/>
+    <descent value="0"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="50" lsb="0"/>
+    <mtx name="1em" width="100" lsb="0"/>
+    <mtx name="f" width="100" lsb="0"/>
+    <mtx name="i" width="100" lsb="0"/>
+    <mtx name="fi" width="100" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+      <map code="0x0020" name="1em" /> <!-- ' ' -->
+      <map code="0x0021" name="1em" /> <!-- '!' -->
+      <map code="0x0022" name="1em" /> <!-- '"' -->
+      <map code="0x0023" name="1em" /> <!-- '#' -->
+      <map code="0x0024" name="1em" /> <!-- '$' -->
+      <map code="0x0025" name="1em" /> <!-- '%' -->
+      <map code="0x0026" name="1em" /> <!-- '&' -->
+      <map code="0x0027" name="1em" /> <!-- ''' -->
+      <map code="0x0028" name="1em" /> <!-- '(' -->
+      <map code="0x0029" name="1em" /> <!-- ')' -->
+      <map code="0x002A" name="1em" /> <!-- '*' -->
+      <map code="0x002B" name="1em" /> <!-- '+' -->
+      <map code="0x002C" name="1em" /> <!-- ''' -->
+      <map code="0x002D" name="1em" /> <!-- '-' -->
+      <map code="0x002E" name="1em" /> <!-- '.' -->
+      <map code="0x002F" name="1em" /> <!-- '/' -->
+      <map code="0x0030" name="1em" /> <!-- '0' -->
+      <map code="0x0031" name="1em" /> <!-- '1' -->
+      <map code="0x0032" name="1em" /> <!-- '2' -->
+      <map code="0x0033" name="1em" /> <!-- '3' -->
+      <map code="0x0034" name="1em" /> <!-- '4' -->
+      <map code="0x0035" name="1em" /> <!-- '5' -->
+      <map code="0x0036" name="1em" /> <!-- '6' -->
+      <map code="0x0037" name="1em" /> <!-- '7' -->
+      <map code="0x0038" name="1em" /> <!-- '8' -->
+      <map code="0x0039" name="1em" /> <!-- '9' -->
+      <map code="0x003A" name="1em" /> <!-- ':' -->
+      <map code="0x003B" name="1em" /> <!-- ';' -->
+      <map code="0x003C" name="1em" /> <!-- '<' -->
+      <map code="0x003D" name="1em" /> <!-- '=' -->
+      <map code="0x003E" name="1em" /> <!-- '>' -->
+      <map code="0x003F" name="1em" /> <!-- '?' -->
+      <map code="0x0040" name="1em" /> <!-- '@' -->
+      <map code="0x0041" name="1em" /> <!-- 'A' -->
+      <map code="0x0042" name="1em" /> <!-- 'B' -->
+      <map code="0x0043" name="1em" /> <!-- 'C' -->
+      <map code="0x0044" name="1em" /> <!-- 'D' -->
+      <map code="0x0045" name="1em" /> <!-- 'E' -->
+      <map code="0x0046" name="1em" /> <!-- 'F' -->
+      <map code="0x0047" name="1em" /> <!-- 'G' -->
+      <map code="0x0048" name="1em" /> <!-- 'H' -->
+      <map code="0x0049" name="1em" /> <!-- 'I' -->
+      <map code="0x004A" name="1em" /> <!-- 'J' -->
+      <map code="0x004B" name="1em" /> <!-- 'K' -->
+      <map code="0x004C" name="1em" /> <!-- 'L' -->
+      <map code="0x004D" name="1em" /> <!-- 'M' -->
+      <map code="0x004E" name="1em" /> <!-- 'N' -->
+      <map code="0x004F" name="1em" /> <!-- 'O' -->
+      <map code="0x0050" name="1em" /> <!-- 'P' -->
+      <map code="0x0051" name="1em" /> <!-- 'Q' -->
+      <map code="0x0052" name="1em" /> <!-- 'R' -->
+      <map code="0x0053" name="1em" /> <!-- 'S' -->
+      <map code="0x0054" name="1em" /> <!-- 'T' -->
+      <map code="0x0055" name="1em" /> <!-- 'U' -->
+      <map code="0x0056" name="1em" /> <!-- 'V' -->
+      <map code="0x0057" name="1em" /> <!-- 'W' -->
+      <map code="0x0058" name="1em" /> <!-- 'X' -->
+      <map code="0x0059" name="1em" /> <!-- 'Y' -->
+      <map code="0x005A" name="1em" /> <!-- 'Z' -->
+      <map code="0x005B" name="1em" /> <!-- '[' -->
+      <map code="0x005C" name="1em" /> <!-- '\' -->
+      <map code="0x005D" name="1em" /> <!-- ']' -->
+      <map code="0x005E" name="1em" /> <!-- '^' -->
+      <map code="0x005F" name="1em" /> <!-- '_' -->
+      <map code="0x0060" name="1em" /> <!-- '`' -->
+      <map code="0x0061" name="1em" /> <!-- 'a' -->
+      <map code="0x0062" name="1em" /> <!-- 'b' -->
+      <map code="0x0063" name="1em" /> <!-- 'c' -->
+      <map code="0x0064" name="1em" /> <!-- 'd' -->
+      <map code="0x0065" name="1em" /> <!-- 'e' -->
+      <map code="0x0066" name="f" /> <!-- 'f' -->
+      <map code="0x0067" name="1em" /> <!-- 'g' -->
+      <map code="0x0068" name="1em" /> <!-- 'h' -->
+      <map code="0x0069" name="i" /> <!-- 'i' -->
+      <map code="0x006A" name="1em" /> <!-- 'j' -->
+      <map code="0x006B" name="1em" /> <!-- 'k' -->
+      <map code="0x006C" name="1em" /> <!-- 'l' -->
+      <map code="0x006D" name="1em" /> <!-- 'm' -->
+      <map code="0x006E" name="1em" /> <!-- 'n' -->
+      <map code="0x006F" name="1em" /> <!-- 'o' -->
+      <map code="0x0070" name="1em" /> <!-- 'p' -->
+      <map code="0x0071" name="1em" /> <!-- 'q' -->
+      <map code="0x0072" name="1em" /> <!-- 'r' -->
+      <map code="0x0073" name="1em" /> <!-- 's' -->
+      <map code="0x0074" name="1em" /> <!-- 't' -->
+      <map code="0x0075" name="1em" /> <!-- 'u' -->
+      <map code="0x0076" name="1em" /> <!-- 'v' -->
+      <map code="0x0077" name="1em" /> <!-- 'w' -->
+      <map code="0x0078" name="1em" /> <!-- 'x' -->
+      <map code="0x0079" name="1em" /> <!-- 'y' -->
+      <map code="0x007A" name="1em" /> <!-- 'z' -->
+      <map code="0x007B" name="1em" /> <!-- '{' -->
+      <map code="0x007C" name="1em" /> <!-- '|' -->
+      <map code="0x007D" name="1em" /> <!-- '}' -->
+      <map code="0x007E" name="1em" /> <!-- '~' -->
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="100" yMax="100">
+        <contour>
+            <pt x="0" y="0" on="1" />
+            <pt x="100" y="0" on="1" />
+            <pt x="100" y="100" on="1" />
+            <pt x="0" y="100" on="1" />
+        </contour>
+        <instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="f" xMin="0" yMin="0" xMax="100" yMax="100">
+        <contour>
+            <pt x="0" y="0" on="1" />
+            <pt x="100" y="0" on="1" />
+            <pt x="100" y="100" on="1" />
+            <pt x="0" y="100" on="1" />
+        </contour>
+        <instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="i" xMin="0" yMin="0" xMax="100" yMax="100">
+        <contour>
+            <pt x="0" y="0" on="1" />
+            <pt x="100" y="0" on="1" />
+            <pt x="100" y="100" on="1" />
+            <pt x="0" y="100" on="1" />
+        </contour>
+        <instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="fi" xMin="0" yMin="0" xMax="100" yMax="100">
+        <contour>
+            <pt x="0" y="0" on="1" />
+            <pt x="100" y="0" on="1" />
+            <pt x="100" y="100" on="1" />
+            <pt x="0" y="100" on="1" />
+        </contour>
+        <instructions><assembly></assembly></instructions>
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for ASCII with Ligature
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for ASCII
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+  <GSUB>
+    <Version value="0x00010000"/>
+    <ScriptList>
+      <ScriptRecord index="0">
+        <ScriptTag value="DFLT" />
+        <Script>
+          <DefaultLangSys>
+            <ReqFeatureIndex value="65535"/>
+            <FeatureIndex index="0" value="0"/>
+            <FeatureIndex index="1" value="1"/>
+          </DefaultLangSys>
+        </Script>
+      </ScriptRecord>
+    </ScriptList>
+    <FeatureList>
+      <FeatureRecord index="0">
+        <FeatureTag value="ccmp"/>
+        <Feature>
+          <LookupListIndex index="0" value="0" />
+        </Feature>
+      </FeatureRecord>
+      <FeatureRecord index="1">
+        <FeatureTag value="liga"/>
+        <Feature>
+          <LookupListIndex index="1" value="1" />
+        </Feature>
+      </FeatureRecord>
+    </FeatureList>
+    <LookupList>
+      <Lookup index="0">
+        <LookupType value="4" />
+        <LookupFlag value="0" />
+        <LigatureSubst index="0" Format="1">
+          <LigatureSet glyph="f">
+            <Ligature components="i" glyph="fi" />
+          </LigatureSet>
+        </LigatureSubst>
+      </Lookup>
+      <Lookup index="1">
+        <LookupType value="4" />
+        <LookupFlag value="0" />
+        <LigatureSubst index="0" Format="1">
+          <LigatureSet glyph="f">
+            <Ligature components="f" glyph="fi" />
+          </LigatureSet>
+        </LigatureSubst>
+      </Lookup>
+    </LookupList>
+  </GSUB>
+
+</ttFont>
diff --git a/tests/perftests/FontCollection.cpp b/tests/perftests/FontCollection.cpp
index 819f125..15d1c5e 100644
--- a/tests/perftests/FontCollection.cpp
+++ b/tests/perftests/FontCollection.cpp
@@ -21,6 +21,7 @@
 #include <benchmark/benchmark.h>
 
 #include "minikin/LocaleList.h"
+#include "minikin/MinikinPaint.h"
 
 #include "FontTestUtils.h"
 #include "MinikinInternal.h"
@@ -95,7 +96,8 @@
 
     while (state.KeepRunning()) {
         result.clear();
-        collection->itemize(buffer, utf16_length, paint, &result);
+        collection->itemize(U16StringPiece(buffer, utf16_length), paint.fontStyle,
+                            paint.localeListId, paint.familyVariant);
     }
 }
 
diff --git a/tests/stresstest/MultithreadTest.cpp b/tests/stresstest/MultithreadTest.cpp
index 5d5ae03..560b517 100644
--- a/tests/stresstest/MultithreadTest.cpp
+++ b/tests/stresstest/MultithreadTest.cpp
@@ -26,6 +26,7 @@
 
 #include "minikin/FontCollection.h"
 #include "minikin/Macros.h"
+#include "minikin/MinikinPaint.h"
 
 #include "FontTestUtils.h"
 #include "MinikinInternal.h"
@@ -73,15 +74,13 @@
 
         for (int j = 0; j < LAYOUT_COUNT_PER_COLLECTION; ++j) {
             // Generates 10 of 3-letter words so that the word sometimes hit the cache.
-            Layout layout;
             std::vector<uint16_t> text = generateTestText(&mt, 3, 10);
-            layout.doLayout(text, Range(0, text.size()), Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                            EndHyphenEdit::NO_EDIT);
-            std::vector<float> advances(text.size());
-            layout.getAdvances(advances.data());
-            for (size_t k = 0; k < advances.size(); ++k) {
+            Layout layout(text, Range(0, text.size()), Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                          EndHyphenEdit::NO_EDIT);
+            for (size_t k = 0; k < text.size(); ++k) {
                 // All characters in Ascii.ttf has 1.0em horizontal advance.
-                LOG_ALWAYS_FATAL_IF(advances[k] != 10.0f, "Memory corruption detected.");
+                LOG_ALWAYS_FATAL_IF(layout.getCharAdvance(k) != 10.0f,
+                                    "Memory corruption detected.");
             }
         }
     }
diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp
index 4f5ae23..0971a06 100644
--- a/tests/unittest/Android.bp
+++ b/tests/unittest/Android.bp
@@ -43,16 +43,20 @@
         "BidiUtilsTest.cpp",
         "CmapCoverageTest.cpp",
         "EmojiTest.cpp",
+        "FontTest.cpp",
         "FontCollectionTest.cpp",
         "FontCollectionItemizeTest.cpp",
         "FontFamilyTest.cpp",
         "FontLanguageListCacheTest.cpp",
         "FontUtilsTest.cpp",
+        "HasherTest.cpp",
         "HyphenatorMapTest.cpp",
         "HyphenatorTest.cpp",
         "GraphemeBreakTests.cpp",
         "GreedyLineBreakerTest.cpp",
         "LayoutCacheTest.cpp",
+        "LayoutCoreTest.cpp",
+        "LayoutSplitterTest.cpp",
         "LayoutTest.cpp",
         "LayoutUtilsTest.cpp",
         "LocaleListTest.cpp",
@@ -61,6 +65,7 @@
         "OptimalLineBreakerTest.cpp",
         "SparseBitSetTest.cpp",
         "StringPieceTest.cpp",
+        "SystemFontsTest.cpp",
         "TestMain.cpp",
         "UnicodeUtilsTest.cpp",
         "WordBreakerTests.cpp",
diff --git a/tests/unittest/AndroidLineBreakerHelperTest.cpp b/tests/unittest/AndroidLineBreakerHelperTest.cpp
index ea977c0..db47e9d 100644
--- a/tests/unittest/AndroidLineBreakerHelperTest.cpp
+++ b/tests/unittest/AndroidLineBreakerHelperTest.cpp
@@ -26,7 +26,7 @@
     const std::vector<float> EMPTY;
     {
         AndroidLineWidth lineWidth(-10 /* first width */, 1 /* first count */, 0 /* rest width */,
-                                   EMPTY, EMPTY, EMPTY, 0);
+                                   EMPTY, 0);
 
         EXPECT_LE(0.0f, lineWidth.getMin());
         for (int i = 0; i < LINE_COUNT; ++i) {
@@ -35,7 +35,7 @@
     }
     {
         AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, -10 /* rest width */,
-                                   EMPTY, EMPTY, EMPTY, 0);
+                                   EMPTY, 0);
 
         EXPECT_LE(0.0f, lineWidth.getMin());
         for (int i = 0; i < LINE_COUNT; ++i) {
@@ -45,7 +45,7 @@
     {
         std::vector<float> indents = {10};
         AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, 0 /* rest width */,
-                                   indents, EMPTY, EMPTY, 0);
+                                   indents, 0);
 
         EXPECT_LE(0.0f, lineWidth.getMin());
         for (int i = 0; i < LINE_COUNT; ++i) {
diff --git a/tests/unittest/EmojiTest.cpp b/tests/unittest/EmojiTest.cpp
index 32ffde7..643a112 100644
--- a/tests/unittest/EmojiTest.cpp
+++ b/tests/unittest/EmojiTest.cpp
@@ -32,6 +32,19 @@
     EXPECT_TRUE(isEmoji(0x1F6F7));  // SLED
     EXPECT_TRUE(isEmoji(0x1F9E6));  // SOCKS
 
+    EXPECT_TRUE(isEmoji(0x1F6D5));  // HINDU TEMPLE
+    EXPECT_TRUE(isEmoji(0x1F7E7));  // ORANGE SQUARE
+    EXPECT_TRUE(isEmoji(0x1F9CF));  // DEAF PERSON
+    EXPECT_TRUE(isEmoji(0x1F9CE));  // PERSON KNEELING
+    EXPECT_TRUE(isEmoji(0x1F9A6));  // OTTER
+    EXPECT_TRUE(isEmoji(0x1F9A9));  // FLAMINGO
+    EXPECT_TRUE(isEmoji(0x1F9C6));  // FALAFEL
+    EXPECT_TRUE(isEmoji(0x1F9AA));  // OYSTER
+    EXPECT_TRUE(isEmoji(0x1FA82));  // PARACHUTE
+    EXPECT_TRUE(isEmoji(0x1FA80));  // YO-YO
+    EXPECT_TRUE(isEmoji(0x1FA70));  // BALLET SHOES
+    EXPECT_TRUE(isEmoji(0x1FA79));  // ADHESIVE BANDAGE
+
     EXPECT_FALSE(isEmoji(0x0000));   // <control>
     EXPECT_FALSE(isEmoji(0x0061));   // LATIN SMALL LETTER A
     EXPECT_FALSE(isEmoji(0x1F93B));  // MODERN PENTATHLON
diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp
index 2fbba98..8cd95aa 100644
--- a/tests/unittest/FontCollectionItemizeTest.cpp
+++ b/tests/unittest/FontCollectionItemizeTest.cpp
@@ -22,6 +22,7 @@
 
 #include "minikin/FontFamily.h"
 #include "minikin/LocaleList.h"
+#include "minikin/MinikinPaint.h"
 
 #include "FontTestUtils.h"
 #include "FreeTypeMinikinFontForTest.h"
@@ -43,6 +44,7 @@
 const char kLatinItalicFont[] = "Italic.ttf";
 const char kZH_HansFont[] = "ZhHans.ttf";
 const char kZH_HantFont[] = "ZhHant.ttf";
+const char kAsciiFont[] = "Ascii.ttf";
 
 const char kEmojiXmlFile[] = "emoji.xml";
 const char kNoGlyphFont[] = "NoGlyphFont.ttf";
@@ -54,37 +56,48 @@
 const char kNoCmapFormat14Font[] = "VariationSelectorTest-Regular.ttf";
 
 // Utility functions for calling itemize function.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str, FontStyle style,
-             const std::string& localeList, std::vector<FontCollection::Run>* result) {
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+                                         const char* str, FontStyle style,
+                                         const std::string& localeList) {
     const size_t BUF_SIZE = 256;
     uint16_t buf[BUF_SIZE];
     size_t len;
 
-    result->clear();
     ParseUnicode(buf, BUF_SIZE, str, &len, NULL);
     const uint32_t localeListId = registerLocaleList(localeList);
-    MinikinPaint paint(collection);
-    paint.fontStyle = style;
-    paint.localeListId = localeListId;
-    collection->itemize(buf, len, paint, result);
+    auto result = collection->itemize(U16StringPiece(buf, len), style, localeListId,
+                                      FamilyVariant::DEFAULT);
+
+    // Check the same result has returned by calling with maxRun.
+    for (uint32_t runMax = 1; runMax <= result.size(); runMax++) {
+        auto resultWithRunMax = collection->itemize(U16StringPiece(buf, len), style, localeListId,
+                                                    FamilyVariant::DEFAULT, runMax);
+        EXPECT_EQ(runMax, resultWithRunMax.size());
+        for (uint32_t i = 0; i < runMax; ++i) {
+            EXPECT_EQ(result[i].start, resultWithRunMax[i].start);
+            EXPECT_EQ(result[i].end, resultWithRunMax[i].end);
+            EXPECT_EQ(result[i].fakedFont, resultWithRunMax[i].fakedFont);
+        }
+    }
+    return result;
 }
 
 // Overloaded version for default font style.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str,
-             const std::string& localeList, std::vector<FontCollection::Run>* result) {
-    itemize(collection, str, FontStyle(), localeList, result);
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+                                         const char* str, const std::string& localeList) {
+    return itemize(collection, str, FontStyle(), localeList);
 }
 
 // Overloaded version for empty locale list id.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str, FontStyle style,
-             std::vector<FontCollection::Run>* result) {
-    itemize(collection, str, style, "", result);
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+                                         const char* str, FontStyle style) {
+    return itemize(collection, str, style, "");
 }
 
 // Overloaded version for default font style and empty locale list id.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str,
-             std::vector<FontCollection::Run>* result) {
-    itemize(collection, str, FontStyle(), "", result);
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+                                         const char* str) {
+    return itemize(collection, str, FontStyle(), "");
 }
 
 // Utility function to obtain font path associated with run.
@@ -101,14 +114,13 @@
 
 TEST(FontCollectionItemizeTest, itemize_latin) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
     const FontStyle kRegularStyle = FontStyle();
     const FontStyle kItalicStyle = FontStyle(FontStyle::Slant::ITALIC);
     const FontStyle kBoldStyle = FontStyle(FontStyle::Weight::BOLD);
     const FontStyle kBoldItalicStyle = FontStyle(FontStyle::Weight::BOLD, FontStyle::Slant::ITALIC);
 
-    itemize(collection, "'a' 'b' 'c' 'd' 'e'", kRegularStyle, &runs);
+    auto runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kRegularStyle);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -116,7 +128,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "'a' 'b' 'c' 'd' 'e'", kItalicStyle, &runs);
+    runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kItalicStyle);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -124,7 +136,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldStyle, &runs);
+    runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldStyle);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -132,7 +144,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldItalicStyle, &runs);
+    runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldItalicStyle);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -142,7 +154,7 @@
 
     // Continue if the specific characters (e.g. hyphen, comma, etc.) is
     // followed.
-    itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle, &runs);
+    runs = itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -150,7 +162,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle, &runs);
+    runs = itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -160,7 +172,7 @@
 
     // U+0301 (COMBINING ACUTE ACCENT) must be in the same run with preceding
     // chars if the font supports it.
-    itemize(collection, "'a' U+0301", kRegularStyle, &runs);
+    runs = itemize(collection, "'a' U+0301", kRegularStyle);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -173,36 +185,35 @@
     // The regular font and the Cherokee font both support U+0301 (COMBINING ACUTE ACCENT). Since
     // it's a combining mark, it should come from whatever font the base character comes from.
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
-    itemize(collection, "'a' U+0301", &runs);
+    auto runs = itemize(collection, "'a' U+0301");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
     // CHEROKEE LETTER A, COMBINING ACUTE ACCENT
-    itemize(collection, "U+13A0 U+0301", &runs);
+    runs = itemize(collection, "U+13A0 U+0301");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
 
     // CHEROKEE LETTER A, COMBINING ACUTE ACCENT, COMBINING ACUTE ACCENT
-    itemize(collection, "U+13A0 U+0301 U+0301", &runs);
+    runs = itemize(collection, "U+13A0 U+0301 U+0301");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
 
-    itemize(collection, "U+0301", &runs);
+    runs = itemize(collection, "U+0301");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
     // COMBINING ACUTE ACCENT, CHEROKEE LETTER A, COMBINING ACUTE ACCENT
-    itemize(collection, "U+0301 U+13A0 U+0301", &runs);
+    runs = itemize(collection, "U+0301 U+13A0 U+0301");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -214,9 +225,8 @@
 
 TEST(FontCollectionItemizeTest, itemize_emoji) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
-    itemize(collection, "U+1F469 U+1F467", &runs);
+    auto runs = itemize(collection, "U+1F469 U+1F467");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
@@ -226,7 +236,7 @@
 
     // U+20E3(COMBINING ENCLOSING KEYCAP) must be in the same run with preceding
     // character if the font supports.
-    itemize(collection, "'0' U+20E3", &runs);
+    runs = itemize(collection, "'0' U+20E3");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -234,7 +244,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "U+1F470 U+20E3", &runs);
+    runs = itemize(collection, "U+1F470 U+20E3");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
@@ -242,7 +252,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "U+242EE U+1F470 U+20E3", &runs);
+    runs = itemize(collection, "U+242EE U+1F470 U+20E3");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -258,7 +268,7 @@
 
     // Currently there is no fonts which has a glyph for 'a' + U+20E3, so they
     // are splitted into two.
-    itemize(collection, "'a' U+20E3", &runs);
+    runs = itemize(collection, "'a' U+20E3");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -275,10 +285,9 @@
 
 TEST(FontCollectionItemizeTest, itemize_non_latin) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", "ja-JP", &runs);
+    auto runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", "ja-JP");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -287,7 +296,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // All Korean Hangul characters.
-    itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", "en-US", &runs);
+    runs = itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", "en-US");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
@@ -297,7 +306,7 @@
 
     // All Han characters ja, zh-Hans font having.
     // Japanese font should be selected if the specified language is Japanese.
-    itemize(collection, "U+81ED U+82B1 U+5FCD", "ja-JP", &runs);
+    runs = itemize(collection, "U+81ED U+82B1 U+5FCD", "ja-JP");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
@@ -307,7 +316,7 @@
 
     // Simplified Chinese font should be selected if the specified language is Simplified
     // Chinese.
-    itemize(collection, "U+81ED U+82B1 U+5FCD", "zh-Hans", &runs);
+    runs = itemize(collection, "U+81ED U+82B1 U+5FCD", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
@@ -317,7 +326,7 @@
 
     // Fallbacks to other fonts if there is no glyph in the specified language's
     // font. There is no character U+4F60 in Japanese.
-    itemize(collection, "U+81ED U+4F60 U+5FCD", "ja-JP", &runs);
+    runs = itemize(collection, "U+81ED U+4F60 U+5FCD", "ja-JP");
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -338,7 +347,7 @@
     EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());
 
     // Tone mark.
-    itemize(collection, "U+4444 U+302D", "", &runs);
+    runs = itemize(collection, "U+4444 U+302D", "");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -349,7 +358,7 @@
     // Both zh-Hant and ja fonts support U+242EE, but zh-Hans doesn't.
     // Here, ja and zh-Hant font should have the same score but ja should be selected since it is
     // listed before zh-Hant.
-    itemize(collection, "U+242EE", "zh-Hans", &runs);
+    runs = itemize(collection, "U+242EE", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -360,9 +369,8 @@
 
 TEST(FontCollectionItemizeTest, itemize_mixed) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
-    itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", "en-US", &runs);
+    auto runs = itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", "en-US");
     ASSERT_EQ(5U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -397,7 +405,6 @@
 
 TEST(FontCollectionItemizeTest, itemize_variationSelector) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
     // A glyph for U+4FAE is provided by both Japanese font and Simplified
     // Chinese font. Also a glyph for U+242EE is provided by both Japanese and
@@ -406,19 +413,19 @@
 
     // U+4FAE is available in both zh_Hans and ja font, but U+4FAE,U+FE00 is
     // only available in ja font.
-    itemize(collection, "U+4FAE", "zh-Hans", &runs);
+    auto runs = itemize(collection, "U+4FAE", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
     EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
 
-    itemize(collection, "U+4FAE U+FE00", "zh-Hans", &runs);
+    runs = itemize(collection, "U+4FAE U+FE00", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+4FAE U+4FAE U+FE00", "zh-Hans", &runs);
+    runs = itemize(collection, "U+4FAE U+4FAE U+FE00", "zh-Hans");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -427,7 +434,7 @@
     EXPECT_EQ(3, runs[1].end);
     EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", "zh-Hans", &runs);
+    runs = itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", "zh-Hans");
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -440,14 +447,14 @@
     EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+4FAE U+FE00 U+FE00", "zh-Hans", &runs);
+    runs = itemize(collection, "U+4FAE U+FE00 U+FE00", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+242EE U+FE0E.
-    itemize(collection, "U+4FAE U+FE0E", "zh-Hans", &runs);
+    runs = itemize(collection, "U+4FAE U+FE0E", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -456,19 +463,19 @@
     // Surrogate pairs handling.
     // U+242EE is available in ja font and zh_Hant font.
     // U+242EE U+FE00 is available only in ja font.
-    itemize(collection, "U+242EE", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+FE00", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+FE00", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+242EE U+FE00", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+242EE U+FE00", "zh-Hant");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -477,7 +484,7 @@
     EXPECT_EQ(5, runs[1].end);
     EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", "zh-Hant");
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -490,27 +497,27 @@
     EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+242EE U+FE00 U+FE00", "zh-Hans", &runs);
+    runs = itemize(collection, "U+242EE U+FE00 U+FE00", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+242EE U+FE0E
-    itemize(collection, "U+242EE U+FE0E", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+FE0E", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
     // Isolated variation selector supplement.
-    itemize(collection, "U+FE00", "", &runs);
+    runs = itemize(collection, "U+FE00", "");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
     EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
 
-    itemize(collection, "U+FE00", "zh-Hant", &runs);
+    runs = itemize(collection, "U+FE00", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -518,14 +525,14 @@
 
     // First font family (Regular.ttf) supports U+203C but doesn't support U+203C U+FE0F.
     // Emoji.ttf font supports U+203C U+FE0F.  Emoji.ttf should be selected.
-    itemize(collection, "U+203C U+FE0F", "zh-Hant", &runs);
+    runs = itemize(collection, "U+203C U+FE0F", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
 
     // First font family (Regular.ttf) supports U+203C U+FE0E.
-    itemize(collection, "U+203C U+FE0E", "zh-Hant", &runs);
+    runs = itemize(collection, "U+203C U+FE0E", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -534,7 +541,6 @@
 
 TEST(FontCollectionItemizeTest, itemize_variationSelectorSupplement) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
     // A glyph for U+845B is provided by both Japanese font and Simplified
     // Chinese font. Also a glyph for U+242EE is provided by both Japanese and
@@ -543,19 +549,19 @@
 
     // U+845B is available in both zh_Hans and ja font, but U+845B,U+E0100 is
     // only available in ja font.
-    itemize(collection, "U+845B", "zh-Hans", &runs);
+    auto runs = itemize(collection, "U+845B", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
     EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
 
-    itemize(collection, "U+845B U+E0100", "zh-Hans", &runs);
+    runs = itemize(collection, "U+845B U+E0100", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+845B U+845B U+E0100", "zh-Hans", &runs);
+    runs = itemize(collection, "U+845B U+845B U+E0100", "zh-Hans");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -564,7 +570,7 @@
     EXPECT_EQ(4, runs[1].end);
     EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+845B U+845B U+E0100 U+845B", "zh-Hans", &runs);
+    runs = itemize(collection, "U+845B U+845B U+E0100 U+845B", "zh-Hans");
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -577,14 +583,14 @@
     EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+845B U+E0100 U+E0100", "zh-Hans", &runs);
+    runs = itemize(collection, "U+845B U+E0100 U+E0100", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+845B U+E01E0.
-    itemize(collection, "U+845B U+E01E0", "zh-Hans", &runs);
+    runs = itemize(collection, "U+845B U+E01E0", "zh-Hans");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
@@ -594,19 +600,19 @@
     // Surrogate pairs handling.
     // U+242EE is available in ja font and zh_Hant font.
     // U+242EE U+E0100 is available only in ja font.
-    itemize(collection, "U+242EE", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+E0101", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+E0101", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+242EE U+E0101", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+242EE U+E0101", "zh-Hant");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -615,7 +621,7 @@
     EXPECT_EQ(6, runs[1].end);
     EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", "zh-Hant");
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -628,27 +634,27 @@
     EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+242EE U+E0100 U+E0100", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+E0100 U+E0100", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(6, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+242EE U+E01E0.
-    itemize(collection, "U+242EE U+E01E0", "zh-Hant", &runs);
+    runs = itemize(collection, "U+242EE U+E01E0", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
     EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
     // Isolated variation selector supplement.
-    itemize(collection, "U+E0100", "", &runs);
+    runs = itemize(collection, "U+E0100", "");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
 
-    itemize(collection, "U+E0100", "zh-Hant", &runs);
+    runs = itemize(collection, "U+E0100", "zh-Hant");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -657,31 +663,29 @@
 
 TEST(FontCollectionItemizeTest, itemize_no_crash) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
     // Broken Surrogate pairs. Check only not crashing.
-    itemize(collection, "'a' U+D83D 'a'", &runs);
-    itemize(collection, "'a' U+DC69 'a'", &runs);
-    itemize(collection, "'a' U+D83D U+D83D 'a'", &runs);
-    itemize(collection, "'a' U+DC69 U+DC69 'a'", &runs);
+    auto runs = itemize(collection, "'a' U+D83D 'a'");
+    runs = itemize(collection, "'a' U+DC69 'a'");
+    runs = itemize(collection, "'a' U+D83D U+D83D 'a'");
+    runs = itemize(collection, "'a' U+DC69 U+DC69 'a'");
 
     // Isolated variation selector. Check only not crashing.
-    itemize(collection, "U+FE00 U+FE00", &runs);
-    itemize(collection, "U+E0100 U+E0100", &runs);
-    itemize(collection, "U+FE00 U+E0100", &runs);
-    itemize(collection, "U+E0100 U+FE00", &runs);
+    runs = itemize(collection, "U+FE00 U+FE00");
+    runs = itemize(collection, "U+E0100 U+E0100");
+    runs = itemize(collection, "U+FE00 U+E0100");
+    runs = itemize(collection, "U+E0100 U+FE00");
 
     // Tone mark only. Check only not crashing.
-    itemize(collection, "U+302D", &runs);
-    itemize(collection, "U+302D U+302D", &runs);
+    runs = itemize(collection, "U+302D");
+    runs = itemize(collection, "U+302D U+302D");
 
     // Tone mark and variation selector mixed. Check only not crashing.
-    itemize(collection, "U+FE00 U+302D U+E0100", &runs);
+    runs = itemize(collection, "U+FE00 U+302D U+E0100");
 }
 
 TEST(FontCollectionItemizeTest, itemize_fakery) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
     FontStyle kBoldStyle(FontStyle::Weight::BOLD);
     FontStyle kItalicStyle(FontStyle::Slant::ITALIC);
@@ -691,7 +695,7 @@
     // the differences between desired and actual font style.
 
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldStyle, "ja-JP", &runs);
+    auto runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldStyle, "ja-JP");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -700,7 +704,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kItalicStyle, "ja-JP", &runs);
+    runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kItalicStyle, "ja-JP");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -709,7 +713,7 @@
     EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldItalicStyle, "ja-JP", &runs);
+    runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldItalicStyle, "ja-JP");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
@@ -730,9 +734,7 @@
 
     std::shared_ptr<FontCollection> collection(new FontCollection(families));
 
-    std::vector<FontCollection::Run> runs;
-
-    itemize(collection, "U+717D U+FE02", &runs);
+    auto runs = itemize(collection, "U+717D U+FE02");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -741,63 +743,62 @@
 
 TEST(FontCollectionItemizeTest, itemize_format_chars) {
     auto collection = buildFontCollectionFromXml(kItemizeFontXml);
-    std::vector<FontCollection::Run> runs;
 
-    itemize(collection, "'a' U+061C 'b'", &runs);
+    auto runs = itemize(collection, "'a' U+061C 'b'");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "'a' U+200D 'b'", &runs);
+    runs = itemize(collection, "'a' U+200D 'b'");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+3042 U+061C U+3042", &runs);
+    runs = itemize(collection, "U+3042 U+061C U+3042");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C 'b'", &runs);
+    runs = itemize(collection, "U+061C 'b'");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C U+3042", &runs);
+    runs = itemize(collection, "U+061C U+3042");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C", &runs);
+    runs = itemize(collection, "U+061C");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C U+061C U+061C", &runs);
+    runs = itemize(collection, "U+061C U+061C U+061C");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+200D U+20E3", &runs);
+    runs = itemize(collection, "U+200D U+20E3");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
 
-    itemize(collection, "U+200D", &runs);
+    runs = itemize(collection, "U+200D");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
     EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+20E3", &runs);
+    runs = itemize(collection, "U+20E3");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
@@ -927,8 +928,9 @@
                 std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kNoGlyphFont));
         std::vector<Font> fonts;
         fonts.push_back(Font::Builder(firstFamilyMinikinFont).build());
-        auto firstFamily = std::make_shared<FontFamily>(
-                registerLocaleList("und"), FontFamily::Variant::DEFAULT, std::move(fonts));
+        auto firstFamily =
+                std::make_shared<FontFamily>(registerLocaleList("und"), FamilyVariant::DEFAULT,
+                                             std::move(fonts), false /* isCustomFallback */);
         families.push_back(firstFamily);
 
         // Prepare font families
@@ -941,16 +943,15 @@
                     std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kJAFont));
             std::vector<Font> fonts;
             fonts.push_back(Font::Builder(minikinFont).build());
-            auto family =
-                    std::make_shared<FontFamily>(registerLocaleList(testCase.fontLocales[i]),
-                                                 FontFamily::Variant::DEFAULT, std::move(fonts));
+            auto family = std::make_shared<FontFamily>(registerLocaleList(testCase.fontLocales[i]),
+                                                       FamilyVariant::DEFAULT, std::move(fonts),
+                                                       false /* isCustomFallback */);
             families.push_back(family);
             fontLocaleIdxMap.insert(std::make_pair(minikinFont.get(), i));
         }
         std::shared_ptr<FontCollection> collection(new FontCollection(families));
         // Do itemize
-        std::vector<FontCollection::Run> runs;
-        itemize(collection, "U+9AA8", testCase.userPreferredLocale, &runs);
+        auto runs = itemize(collection, "U+9AA8", testCase.userPreferredLocale);
         ASSERT_EQ(1U, runs.size());
         ASSERT_NE(nullptr, runs[0].fakedFont.font);
 
@@ -1266,8 +1267,7 @@
         SCOPED_TRACE("Test for \"" + testCase.testString + "\" with locales " +
                      testCase.requestedLocales);
 
-        std::vector<FontCollection::Run> runs;
-        itemize(collection, testCase.testString.c_str(), testCase.requestedLocales, &runs);
+        auto runs = itemize(collection, testCase.testString.c_str(), testCase.requestedLocales);
         ASSERT_EQ(1U, runs.size());
         EXPECT_EQ(testCase.expectedFont, getFontName(runs[0]));
     }
@@ -1275,11 +1275,10 @@
 
 TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) {
     auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
-    std::vector<FontCollection::Run> runs;
 
     // U+00A9 is a text default emoji which is only available in TextEmojiFont.ttf.
     // TextEmojiFont.ttf should be selected.
-    itemize(collection, "U+00A9 U+FE0E", &runs);
+    auto runs = itemize(collection, "U+00A9 U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1287,7 +1286,7 @@
 
     // U+00A9 is a text default emoji which is only available in ColorEmojiFont.ttf.
     // ColorEmojiFont.ttf should be selected.
-    itemize(collection, "U+00AE U+FE0E", &runs);
+    runs = itemize(collection, "U+00AE U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1296,7 +1295,7 @@
 
     // U+203C is a text default emoji which is available in both TextEmojiFont.ttf and
     // ColorEmojiFont.ttf. TextEmojiFont.ttf should be selected.
-    itemize(collection, "U+203C U+FE0E", &runs);
+    runs = itemize(collection, "U+203C U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1304,7 +1303,7 @@
 
     // U+2049 is a text default emoji which is not available either TextEmojiFont.ttf or
     // ColorEmojiFont.ttf. No font should be selected.
-    itemize(collection, "U+2049 U+FE0E", &runs);
+    runs = itemize(collection, "U+2049 U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1312,7 +1311,7 @@
 
     // U+231A is a emoji default emoji which is available only in TextEmojifFont.
     // TextEmojiFont.ttf sohuld be selected.
-    itemize(collection, "U+231A U+FE0E", &runs);
+    runs = itemize(collection, "U+231A U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1320,7 +1319,7 @@
 
     // U+231B is a emoji default emoji which is available only in ColorEmojiFont.ttf.
     // ColorEmojiFont.ttf should be selected.
-    itemize(collection, "U+231B U+FE0E", &runs);
+    runs = itemize(collection, "U+231B U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1330,7 +1329,7 @@
     // U+23E9 is a emoji default emoji which is available in both TextEmojiFont.ttf and
     // ColorEmojiFont.ttf. TextEmojiFont.ttf should be selected even if U+23E9 is emoji default
     // emoji since U+FE0E is appended.
-    itemize(collection, "U+23E9 U+FE0E", &runs);
+    runs = itemize(collection, "U+23E9 U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1338,7 +1337,7 @@
 
     // U+23EA is a emoji default emoji but which is not available in either TextEmojiFont.ttf or
     // ColorEmojiFont.ttf. No font should be selected.
-    itemize(collection, "U+23EA U+FE0E", &runs);
+    runs = itemize(collection, "U+23EA U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1346,7 +1345,7 @@
 
     // U+26FA U+FE0E is specified but ColorTextMixedEmojiFont has a variation sequence U+26F9 U+FE0F
     // in its cmap, so ColorTextMixedEmojiFont should be selected instaed of ColorEmojiFont.
-    itemize(collection, "U+26FA U+FE0E", &runs);
+    runs = itemize(collection, "U+26FA U+FE0E");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1355,11 +1354,10 @@
 
 TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) {
     auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
-    std::vector<FontCollection::Run> runs;
 
     // U+00A9 is a text default emoji which is available only in TextEmojiFont.ttf.
     // TextEmojiFont.ttf shoudl be selected.
-    itemize(collection, "U+00A9 U+FE0F", &runs);
+    auto runs = itemize(collection, "U+00A9 U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1368,7 +1366,7 @@
 
     // U+00AE is a text default emoji which is available only in ColorEmojiFont.ttf.
     // ColorEmojiFont.ttf should be selected.
-    itemize(collection, "U+00AE U+FE0F", &runs);
+    runs = itemize(collection, "U+00AE U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1377,7 +1375,7 @@
     // U+203C is a text default emoji which is available in both TextEmojiFont.ttf and
     // ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected even if U+203C is a text default
     // emoji since U+FF0F is appended.
-    itemize(collection, "U+203C U+FE0F", &runs);
+    runs = itemize(collection, "U+203C U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1385,7 +1383,7 @@
 
     // U+2049 is a text default emoji which is not available in either TextEmojiFont.ttf or
     // ColorEmojiFont.ttf. No font should be selected.
-    itemize(collection, "U+2049 U+FE0F", &runs);
+    runs = itemize(collection, "U+2049 U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1393,7 +1391,7 @@
 
     // U+231A is a emoji default emoji which is available only in TextEmojiFont.ttf.
     // TextEmojiFont.ttf should be selected.
-    itemize(collection, "U+231A U+FE0F", &runs);
+    runs = itemize(collection, "U+231A U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1402,7 +1400,7 @@
 
     // U+231B is a emoji default emoji which is available only in ColorEmojiFont.ttf.
     // ColorEmojiFont.ttf should be selected.
-    itemize(collection, "U+231B U+FE0F", &runs);
+    runs = itemize(collection, "U+231B U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1410,7 +1408,7 @@
 
     // U+23E9 is a emoji default emoji which is available in both TextEmojiFont.ttf and
     // ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected.
-    itemize(collection, "U+23E9 U+FE0F", &runs);
+    runs = itemize(collection, "U+23E9 U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1418,7 +1416,7 @@
 
     // U+23EA is a emoji default emoji which is not available in either TextEmojiFont.ttf or
     // ColorEmojiFont.ttf. No font should be selected.
-    itemize(collection, "U+23EA U+FE0F", &runs);
+    runs = itemize(collection, "U+23EA U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1426,7 +1424,7 @@
 
     // U+26F9 U+FE0F is specified but ColorTextMixedEmojiFont has a variation sequence U+26F9 U+FE0F
     // in its cmap, so ColorTextMixedEmojiFont should be selected instaed of ColorEmojiFont.
-    itemize(collection, "U+26F9 U+FE0F", &runs);
+    runs = itemize(collection, "U+26F9 U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1435,24 +1433,23 @@
 
 TEST(FontCollectionItemizeTest, itemize_emojiSelection_with_skinTone) {
     auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
-    std::vector<FontCollection::Run> runs;
 
     // TextEmoji font is selected since it is listed before ColorEmoji font.
-    itemize(collection, "U+261D", &runs);
+    auto runs = itemize(collection, "U+261D");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
     EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // If skin tone is specified, it should be colored.
-    itemize(collection, "U+261D U+1F3FD", &runs);
+    runs = itemize(collection, "U+261D U+1F3FD");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
     EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // Still color font is selected if an emoji variation selector is specified.
-    itemize(collection, "U+261D U+FE0F U+1F3FD", &runs);
+    runs = itemize(collection, "U+261D U+FE0F U+1F3FD");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
@@ -1460,7 +1457,7 @@
 
     // Text font should be selected if a text variation selector is specified and skin tone is
     // rendered by itself.
-    itemize(collection, "U+261D U+FE0E U+1F3FD", &runs);
+    runs = itemize(collection, "U+261D U+FE0E U+1F3FD");
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
@@ -1472,16 +1469,15 @@
 
 TEST(FontCollectionItemizeTest, itemize_PrivateUseArea) {
     auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
-    std::vector<FontCollection::Run> runs;
 
     // Should not set nullptr to the result run. (Issue 26808815)
-    itemize(collection, "U+FEE10", &runs);
+    auto runs = itemize(collection, "U+FEE10");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
 
-    itemize(collection, "U+FEE40 U+FE4C5", &runs);
+    runs = itemize(collection, "U+FEE40 U+FE4C5");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
@@ -1490,21 +1486,20 @@
 
 TEST(FontCollectionItemizeTest, itemize_genderBalancedEmoji) {
     auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
-    std::vector<FontCollection::Run> runs;
 
-    itemize(collection, "U+1F469 U+200D U+1F373", &runs);
+    auto runs = itemize(collection, "U+1F469 U+200D U+1F373");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
     EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
-    itemize(collection, "U+1F469 U+200D U+2695 U+FE0F", &runs);
+    runs = itemize(collection, "U+1F469 U+200D U+2695 U+FE0F");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
     EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
-    itemize(collection, "U+1F469 U+200D U+2695", &runs);
+    runs = itemize(collection, "U+1F469 U+200D U+2695");
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
@@ -1525,11 +1520,10 @@
 
     // Both fontA/fontB support U+35A8 but don't support U+35A8 U+E0100. The first font should be
     // selected.
-    std::vector<FontCollection::Run> runs;
-    itemize(collection, "U+35A8 U+E0100", &runs);
+    auto runs = itemize(collection, "U+35A8 U+E0100");
     EXPECT_EQ(familyA->getFont(0), runs[0].fakedFont.font);
 
-    itemize(reversedCollection, "U+35A8 U+E0100", &runs);
+    runs = itemize(reversedCollection, "U+35A8 U+E0100");
     EXPECT_EQ(familyB->getFont(0), runs[0].fakedFont.font);
 }
 
@@ -1549,11 +1543,10 @@
 
     // Both hasCmapFormat14Font/noCmapFormat14Font support U+5380 but don't support U+5380 U+E0100.
     // The first font should be selected.
-    std::vector<FontCollection::Run> runs;
-    itemize(collection, "U+5380 U+E0100", &runs);
+    auto runs = itemize(collection, "U+5380 U+E0100");
     EXPECT_EQ(hasCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
 
-    itemize(reversedCollection, "U+5380 U+E0100", &runs);
+    runs = itemize(reversedCollection, "U+5380 U+E0100");
     EXPECT_EQ(noCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
 }
 
@@ -1565,48 +1558,65 @@
     std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, textEmojiFamily,
                                                          colorEmojiFamily};
     auto collection = std::make_shared<FontCollection>(families);
-    std::vector<FontCollection::Run> runs;
     // Both textEmojiFamily and colorEmojiFamily supports U+203C and U+23E9.
     // U+203C is text default emoji, and U+23E9 is color default emoji.
-    itemize(collection, "U+203C", "en-US,en-Zsym", &runs);
+    auto runs = itemize(collection, "U+203C", "en-US,en-Zsym");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "en-US,en-Zsym", &runs);
+    runs = itemize(collection, "U+23E9", "en-US,en-Zsym");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
 
-    itemize(collection, "U+203C", "en-US,en-Zsye", &runs);
+    runs = itemize(collection, "U+203C", "en-US,en-Zsye");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "en-US,en-Zsye", &runs);
+    runs = itemize(collection, "U+23E9", "en-US,en-Zsye");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
 
-    itemize(collection, "U+203C", "ja-Zsym-JP", &runs);
+    runs = itemize(collection, "U+203C", "ja-Zsym-JP");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "ja-Zsym-JP", &runs);
+    runs = itemize(collection, "U+23E9", "ja-Zsym-JP");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
 
-    itemize(collection, "U+203C", "ja-Zsye-JP", &runs);
+    runs = itemize(collection, "U+203C", "ja-Zsye-JP");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "ja-Zsye-JP", &runs);
+    runs = itemize(collection, "U+23E9", "ja-Zsye-JP");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
 
-    itemize(collection, "U+203C", "ja-JP-u-em-text", &runs);
+    runs = itemize(collection, "U+203C", "ja-JP-u-em-text");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "ja-JP-u-em-text", &runs);
+    runs = itemize(collection, "U+23E9", "ja-JP-u-em-text");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
 
-    itemize(collection, "U+203C", "ja-JP-u-em-emoji", &runs);
+    runs = itemize(collection, "U+203C", "ja-JP-u-em-emoji");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "ja-JP-u-em-emoji", &runs);
+    runs = itemize(collection, "U+23E9", "ja-JP-u-em-emoji");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
 
-    itemize(collection, "U+203C", "ja-JP,und-Zsym", &runs);
+    runs = itemize(collection, "U+203C", "ja-JP,und-Zsym");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "ja-JP,und-Zsym", &runs);
+    runs = itemize(collection, "U+23E9", "ja-JP,und-Zsym");
     EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
 
-    itemize(collection, "U+203C", "ja-JP,und-Zsye", &runs);
+    runs = itemize(collection, "U+203C", "ja-JP,und-Zsye");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
-    itemize(collection, "U+23E9", "ja-JP,und-Zsye", &runs);
+    runs = itemize(collection, "U+23E9", "ja-JP,und-Zsye");
     EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
 }
 
+TEST(FontCollectionItemizeTest, customFallbackTest) {
+    auto firstFamily = buildFontFamily(kNoGlyphFont);
+    auto customFallbackFamily = buildFontFamily(kAsciiFont, "", true /* isCustomFallback */);
+    auto languageFamily = buildFontFamily(kAsciiFont, "ja-JP");
+
+    std::vector<std::shared_ptr<FontFamily>> families = {firstFamily, customFallbackFamily,
+                                                         languageFamily};
+
+    auto collection = std::make_shared<FontCollection>(families);
+
+    auto runs = itemize(collection, "'a'", "");
+    EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font);
+    runs = itemize(collection, "'a'", "en-US");
+    EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font);
+    runs = itemize(collection, "'a'", "ja-JP");
+    EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font);
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp
index 4eb6bcc..2b70faf 100644
--- a/tests/unittest/FontFamilyTest.cpp
+++ b/tests/unittest/FontFamilyTest.cpp
@@ -108,6 +108,14 @@
     EXPECT_EQ("de-Latn-DE-1901", createLocale("de-1901").getString());
     EXPECT_EQ("de-Latn-DE-1996", createLocale("de-DE-1996").getString());
 
+    // Line Break subtag
+    EXPECT_EQ("ja-Jpan-JP-u-lb-loose", createLocale("ja-JP-u-lb-loose").getString());
+    EXPECT_EQ("ja-Jpan-JP-u-lb-normal", createLocale("ja-JP-u-lb-normal").getString());
+    EXPECT_EQ("ja-Jpan-JP-u-lb-strict", createLocale("ja-JP-u-lb-strict").getString());
+    EXPECT_EQ("ja-Jpan-JP-u-lb-loose", createLocale("ja-JP-u-lb-loose-em-emoji").getString());
+    EXPECT_EQ("ja-Jpan-JP-u-lb-strict", createLocale("ja-JP-u-em-default-lb-strict").getString());
+    EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-bogus").getString());
+
     // Emoji subtag is dropped from getString().
     EXPECT_EQ("es-Latn-419", createLocale("es-419-u-em-emoji").getString());
     EXPECT_EQ("es-Latn-419", createLocale("es-Latn-419-u-em-emoji").getString());
@@ -116,6 +124,10 @@
     EXPECT_EQ("en-Latn-US", createLocale("und-Abcdefgh").getString());
 }
 
+TEST(LocaleTest, invalidLanguageTagTest) {  // just make sure no crash happens
+    LocaleListCache::getId("ja-JP-u-lb-lb-strict");
+}
+
 TEST(LocaleTest, testReconstruction) {
     EXPECT_EQ("en", createLocaleWithoutICUSanitization("en").getString());
     EXPECT_EQ("fil", createLocaleWithoutICUSanitization("fil").getString());
diff --git a/tests/unittest/FontTest.cpp b/tests/unittest/FontTest.cpp
new file mode 100644
index 0000000..ff2f9bc
--- /dev/null
+++ b/tests/unittest/FontTest.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "minikin/Font.h"
+
+#include <gtest/gtest.h>
+
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+
+namespace minikin {
+
+TEST(FontTest, CopyTest) {
+    auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf"));
+    {
+        Font font = Font::Builder(minikinFont).build();
+        {
+            Font copied(font);
+            EXPECT_EQ(font.typeface(), copied.typeface());
+            EXPECT_EQ(font.style(), copied.style());
+            EXPECT_EQ(font.baseFont(), copied.baseFont());
+        }
+        {
+            Font copied = font;
+            EXPECT_EQ(font.typeface(), copied.typeface());
+            EXPECT_EQ(font.style(), copied.style());
+            EXPECT_EQ(font.baseFont(), copied.baseFont());
+        }
+    }
+}
+
+}  // namespace minikin
diff --git a/tests/unittest/GraphemeBreakTests.cpp b/tests/unittest/GraphemeBreakTests.cpp
index bccd631..87e114c 100644
--- a/tests/unittest/GraphemeBreakTests.cpp
+++ b/tests/unittest/GraphemeBreakTests.cpp
@@ -256,17 +256,17 @@
     EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, "U+1F918 | U+1F3FF"));
     EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, "U+1F933 | U+1F3FF"));
     // Reptition of the tests above, with the knowledge that they are not ligated.
-    // const float unligated1_2[] = {1.0, 1.0, 0.0};
-    // const float unligated2_2[] = {1.0, 0.0, 1.0, 0.0};
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+261D | U+1F3FB"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+270C | U+1F3FB"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FB"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FC"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FD"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FE"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FF"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F918 | U+1F3FF"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F933 | U+1F3FF"));
+    const float unligated1_2[] = {1.0, 1.0, 0.0};
+    const float unligated2_2[] = {1.0, 0.0, 1.0, 0.0};
+    EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+261D | U+1F3FB"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+270C | U+1F3FB"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FB"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FC"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FD"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FE"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FF"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F918 | U+1F3FF"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F933 | U+1F3FF"));
 
     // adding extend characters between emoji base and modifier doesn't affect grapheme cluster
     EXPECT_FALSE(IsBreak("U+270C U+FE0E | U+1F3FB"));  // victory hand + text style + modifier
@@ -276,15 +276,9 @@
     EXPECT_FALSE(IsBreakWithAdvances(ligated1_1_2, "U+270C U+FE0E | U+1F3FB"));
     EXPECT_FALSE(IsBreakWithAdvances(ligated1_1_2, "U+270C U+FE0F | U+1F3FB"));
     // Reptition of the first two tests, with the knowledge that they are not ligated.
-    // const float unligated1_1_2[] = {1.0, 0.0, 1.0, 0.0};
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0E | U+1F3FB"));
-    // EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0F | U+1F3FB"));
-
-    // heart is not an emoji base
-    // EXPECT_TRUE(IsBreak("U+2764 | U+1F3FB"));         // heart + modifier
-    // EXPECT_TRUE(IsBreak("U+2764 U+FE0E | U+1F3FB"));  // heart + emoji style + modifier
-    // EXPECT_TRUE(IsBreak("U+2764 U+FE0F | U+1F3FB"));  // heart + emoji style + modifier
-    // EXPECT_TRUE(IsBreak("U+1F3FB | U+1F3FB"));        // modifier + modifier
+    const float unligated1_1_2[] = {1.0, 0.0, 1.0, 0.0};
+    EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0E | U+1F3FB"));
+    EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0F | U+1F3FB"));
 
     // rat is not an emoji modifer
     EXPECT_TRUE(IsBreak("U+1F466 | U+1F400"));  // boy + rat
@@ -318,4 +312,9 @@
     EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 5));
 }
 
+TEST(GraphemeBreak, startWithZWJ) {
+    // It used to be looking before the ZWJ char even if it is the start of the text.
+    IsBreak("U+200D | U+1F5E8");  // UB sanitizer will catch if minikin looks the char before ZWJ
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp
index ad5824d..13cc03c 100644
--- a/tests/unittest/GreedyLineBreakerTest.cpp
+++ b/tests/unittest/GreedyLineBreakerTest.cpp
@@ -39,6 +39,14 @@
 using line_breaker_test_helper::sameLineBreak;
 using line_breaker_test_helper::toString;
 
+// The ascent/descent of Ascii.ttf with text size = 10.
+constexpr float ASCENT = -80.0f;
+constexpr float DESCENT = 20.0f;
+
+// The ascent/descent of CustomExtent.ttf with text size = 10.
+constexpr float CUSTOM_ASCENT = -160.0f;
+constexpr float CUSTOM_DESCENT = 40.0f;
+
 class GreedyLineBreakerTest : public testing::Test {
 public:
     GreedyLineBreakerTest() {}
@@ -57,16 +65,24 @@
 
 protected:
     LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
-                                float charWidth, float lineWidth) {
-        return doLineBreak(textBuffer, doHyphenation, charWidth, "en-US", lineWidth);
+                                float lineWidth) {
+        return doLineBreak(textBuffer, doHyphenation, "en-US", lineWidth);
     }
 
     LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
-                                float charWidth, const std::string& lang, float lineWidth) {
+                                const std::string& lang, float lineWidth) {
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, textBuffer.size()), lang, charWidth);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuffer, false /* compute hyphenation */, false /* compute full layout */);
+        auto family1 = buildFontFamily("Ascii.ttf");
+        auto family2 = buildFontFamily("CustomExtent.ttf");
+        std::vector<std::shared_ptr<FontFamily>> families = {family1, family2};
+        auto fc = std::make_shared<FontCollection>(families);
+        MinikinPaint paint(fc);
+        paint.size = 10.0f;  // Make 1em=10px
+        paint.localeListId = LocaleListCache::getId(lang);
+        builder.addStyleRun(0, textBuffer.size(), std::move(paint), false);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuffer, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         RectangleLineWidth rectangleLineWidth(lineWidth);
         TabStops tabStops(nullptr, 0, 10);
         return breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops,
@@ -78,7 +94,6 @@
 };
 
 TEST_F(GreedyLineBreakerTest, testBreakWithoutHyphenation) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr bool NO_HYPHEN = false;  // No hyphenation in this test case.
     const std::vector<uint16_t> textBuf = utf8ToUtf16("This is an example text.");
 
@@ -86,198 +101,198 @@
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
     // Note that disable clang-format everywhere since aligned expectation is more readable.
     {
-        constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 1000;
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 240;
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 230;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is an example ", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."              ,  5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an example ", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."              ,  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 80;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an "     , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an "     , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 70;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an "     , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an "     , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 60;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "exampl", 6 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e "    , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "exampl", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e "    , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 50;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "examp" , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "le "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "examp" , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "le "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 40;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "exam"  , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ple "  , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text"  , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "."     , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "exam"  , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ple "  , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text"  , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "."     , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 30;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Thi" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "s "  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "mpl" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e "  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t."  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Thi" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "s "  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "exa" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "mpl" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e "  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "tex" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t."  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 20;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ex" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "am" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "pl" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xt" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "."  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Th" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ex" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "am" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "pl" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e " , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "te" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xt" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "."  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 10;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "m" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "l" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "m" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "l" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -285,7 +300,6 @@
 }
 
 TEST_F(GreedyLineBreakerTest, testBreakWithHyphenation) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr bool NO_HYPHEN = true;  // Do hyphenation in this test case.
     // "hyphenation" is hyphnated to "hy-phen-a-tion".
     const std::vector<uint16_t> textBuf = utf8ToUtf16("Hyphenation is hyphenation.");
@@ -296,262 +310,264 @@
 
     // Note that disable clang-format everywhere since aligned expectation is more readable.
     {
-        constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 1000;
         std::vector<LineBreakExpectation> expect = {
-                {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"Hyphenation is hyphenation.", 270, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT,
+                 DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 27 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 270;
         std::vector<LineBreakExpectation> expect = {
-                {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"Hyphenation is hyphenation.", 270, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT,
+                 DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 26 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 260;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hyphenation is " , 14 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hyphenation."    , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hyphenation is " , 140, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hyphenation."    , 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 170;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hyphenation is " , 14 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hyphenation."    , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hyphenation is " , 140, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hyphenation."    , 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 120;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hyphenation " , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "          , 2  * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hyphenation." , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hyphenation " , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "          , 20 , NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hyphenation." , 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 10 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 100;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 80;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 70;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hyphen-", 7 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ation " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "    , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hyphen-", 7 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ation." , 6 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hyphen-", 70, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ation " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "    , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hyphen-", 70, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ation." , 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 60;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hy-"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phena-", 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hy-"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phena-", 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hy-"   , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phena-", 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hy-"   , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phena-", 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 50;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hy-"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phen-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ation ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hy-"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phen-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "a-"    , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hy-"   , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phen-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ation ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hy-"   , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phen-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "a-"    , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 40;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hy-"  , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phen" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a-"   , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hy-"  , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phen" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a-"   , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tion" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "."    , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hy-"  , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phen" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a-"   , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hy-"  , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phen" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a-"   , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tion" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "."    , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 30;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hy-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phe", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "na-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tio", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hy-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "phe", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "na-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "tio", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hy-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phe", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "na-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tio", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n " , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hy-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "phe", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "na-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "tio", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 20;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Hy" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ph" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "en" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ti" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "on ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "hy" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ph" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "en" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ti" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "on" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "."  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Hy" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ph" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "en" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a-" , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ti" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "on ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "hy" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ph" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "en" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a-" , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ti" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "on" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "."  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 10;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "H" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "y" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "o" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "y" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "o" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "H" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "y" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "o" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "y" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "o" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -559,7 +575,6 @@
 }
 
 TEST_F(GreedyLineBreakerTest, testHyphenationStartLineChange) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr bool DO_HYPHEN = true;  // Do hyphenation in this test case.
     // "hyphenation" is hyphnated to "hy-phen-a-tion".
     const std::vector<uint16_t> textBuf = utf8ToUtf16("czerwono-niebieska");
@@ -570,37 +585,37 @@
 
     // Note that disable clang-format everywhere since aligned expectation is more readable.
     {
-        constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 1000;
         std::vector<LineBreakExpectation> expect = {
-                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, "pl", LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 18 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 180;
         std::vector<LineBreakExpectation> expect = {
-                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, "pl", LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 130;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                {"czerwono-" ,  9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"-niebieska", 10 * CHAR_WIDTH,    START_HYPHEN, NO_END_HYPHEN},
+                {"czerwono-" ,  90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"-niebieska", 100,    START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, "pl", LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -608,9 +623,8 @@
 }
 
 TEST_F(GreedyLineBreakerTest, testZeroWidthLine) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr bool DO_HYPHEN = true;  // Do hyphenation in this test case.
-    constexpr float LINE_WIDTH = 0 * CHAR_WIDTH;
+    constexpr float LINE_WIDTH = 0;
 
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
@@ -618,7 +632,7 @@
     {
         const auto textBuf = utf8ToUtf16("");
         std::vector<LineBreakExpectation> expect = {};
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -626,9 +640,9 @@
     {
         const auto textBuf = utf8ToUtf16("A");
         std::vector<LineBreakExpectation> expect = {
-                {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -636,10 +650,10 @@
     {
         const auto textBuf = utf8ToUtf16("AB");
         std::vector<LineBreakExpectation> expect = {
-                {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"B", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"B", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -656,9 +670,19 @@
         constexpr float LINE_WIDTH = 1.0;
         const auto textBuf = utf8ToUtf16("This is an example text.");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+        TabStops tabStops(nullptr, 0, 10);
+        const auto actual =
+                breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -667,9 +691,18 @@
         constexpr float LINE_WIDTH = 0.0;
         const auto textBuf = utf8ToUtf16("This is an example text.");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+        TabStops tabStops(nullptr, 0, 10);
+        const auto actual =
+                breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -683,18 +716,20 @@
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
 
-    constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+    constexpr float LINE_WIDTH = 240;
     const auto textBuf = utf8ToUtf16("This is an example text.");
     {
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuf, false /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
         TabStops tabStops(nullptr, 0, 0);
 
@@ -706,14 +741,16 @@
     }
     {
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuf, false /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
         TabStops tabStops(nullptr, 0, 0);
 
@@ -726,31 +763,30 @@
 }
 
 TEST_F(GreedyLineBreakerTest, testEmailOrUrl) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr bool DO_HYPHEN = true;  // Do hyphenation in this test case.
 
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
     {
-        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 240;
         const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an url: ", 150, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"http://a.b", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 240;
         const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a@example.com", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -764,19 +800,21 @@
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
 
-    constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+    constexpr float LINE_WIDTH = 240;
     {
         const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an url: ", 150, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"http://a.b", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuf, false /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
         TabStops tabStops(nullptr, 0, 0);
 
@@ -789,15 +827,17 @@
     {
         const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a@example.com", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuf, false /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
         TabStops tabStops(nullptr, 0, 0);
 
@@ -817,16 +857,18 @@
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
     {
-        constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 50;
         const auto textBuf = utf8ToUtf16("a \tb");
         std::vector<LineBreakExpectation> expect = {
-                {"a \tb", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"a \tb", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuf, false /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
         TabStops tabStops(nullptr, 0, CHAR_WIDTH);
 
@@ -838,5 +880,685 @@
     }
 }
 
+TEST_F(GreedyLineBreakerTest, ExtentTest) {
+    constexpr bool NO_HYPHEN = false;  // No hyphenation in this test case.
+    const std::vector<uint16_t> textBuf = utf8ToUtf16("The \u3042\u3044\u3046 is Japanese.");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    {
+        constexpr float LINE_WIDTH = 1000;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+                 CUSTOM_ASCENT, CUSTOM_DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 200;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+                 CUSTOM_ASCENT, CUSTOM_DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 190;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042\u3044\u3046 is ", 100, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042\u3044\u3046 ", 70, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 50;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3044\u3046 is ", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"Japan", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ese.", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 40;
+        std::vector<LineBreakExpectation> expect = {
+                {"The ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u3042\u3044\u3046 ", 30, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"Japa", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"nese", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 20;
+        std::vector<LineBreakExpectation> expect = {
+                {"Th", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u3042\u3044", 20, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3046 ", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"Ja", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"pa", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ne", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"se", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        std::vector<LineBreakExpectation> expect = {
+                {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u3042", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3044", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3046 ", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"J", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"n", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_SingleChar) {
+    constexpr float CHAR_WIDTH = 10.0;
+    constexpr bool DO_HYPHEN = true;  // Do hyphenation in this test case.
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    const auto textBuf = utf8ToUtf16("This is an example \u2639 text.");
+
+    // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 19), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(19, 20, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(20, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 100;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an ",   100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ",       70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u2639 ",        50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",          50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"an ",      20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u2639 ",  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",    50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"T",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"n ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"m",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"l",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                // TODO: This should be "\u2639 " since should not count the trailing line end space
+                {"\u2639", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {" ",       0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_MultipleChars) {
+    constexpr float CHAR_WIDTH = 10.0;
+    constexpr bool DO_HYPHEN = true;  // Do hyphenation in this test case.
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    const auto textBuf = utf8ToUtf16("This is an example text.");
+
+    // In this test case, assign a replacement run for "is an " with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 5), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(5, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 100;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an ",   100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ",       70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",          50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",   40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an ",  50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"example ",70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",   50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"T",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"m",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"l",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_CJK) {
+    constexpr float CHAR_WIDTH = 10.0;
+    constexpr bool DO_HYPHEN = true;  // Do hyphenation in this test case.
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    // Example string: "Today is a sunny day." in Japanese.
+    const auto textBuf = utf8ToUtf16("\u672C\u65E5\u306F\u6674\u5929\u306A\u308A");
+
+    // In this test case, assign a replacement run for "\u6674\u5929" with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 3), "ja-JP", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(3, 5, 5 * CHAR_WIDTH, LocaleListCache::getId("ja-JP"));
+        builder.addCustomRun<ConstantRun>(Range(5, textBuf.size()), "ja-JP", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 100;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F\u6674\u5929\u306A\u308A",
+                  100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F\u6674\u5929\u306A",
+                  90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u308A",
+                  10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 80;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F\u6674\u5929",
+                  80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u306A\u308A",
+                  20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 70;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929\u306A\u308A", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 60;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929\u306A", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u308A",             10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 50;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929",       50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u306A\u308A",       20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 40;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929",       50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u306A\u308A",       20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u65E5",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u306F",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u306A",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u308A",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_with_punctuation) {
+    constexpr float CHAR_WIDTH = 10.0;
+    constexpr bool DO_HYPHEN = true;  // Do hyphenation in this test case.
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    const auto textBuf = utf8ToUtf16("This (is an) example text.");
+
+    // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 6), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(6, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 1000;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) example text.",
+                  260, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 250;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) example ", 200, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",                  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 190;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example text.", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 120;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ",       70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",          50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 110;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",    40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(is an) ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",    50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 60;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",  40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {") ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"exam-",  50, NO_START_HYPHEN,    END_HYPHEN, ASCENT, DESCENT},
+                {"ple ",   30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 50;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",  40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an",  50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {") ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"exam-",  50, NO_START_HYPHEN,    END_HYPHEN, ASCENT, DESCENT},
+                {"ple ",   30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 40;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {") ",    10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ex-",   30, NO_START_HYPHEN,    END_HYPHEN, ASCENT, DESCENT},
+                {"am-",   30, NO_START_HYPHEN,    END_HYPHEN, ASCENT, DESCENT},
+                {"ple ",  30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text",  40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"T",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",    10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {") ",    10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"m",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p",    10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"l",    10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ",    10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testControllCharAfterSpace) {
+    constexpr bool NO_HYPHEN = false;  // No hyphenation in this test case.
+    const std::vector<uint16_t> textBuf = utf8ToUtf16("example \u2066example");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    {
+        constexpr float LINE_WIDTH = 90;
+        std::vector<LineBreakExpectation> expect = {
+                {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u2066example", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
 }  // namespace
 }  // namespace minikin
diff --git a/tests/unittest/HasherTest.cpp b/tests/unittest/HasherTest.cpp
new file mode 100644
index 0000000..8e11cc6
--- /dev/null
+++ b/tests/unittest/HasherTest.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "minikin/Hasher.h"
+
+#include <gtest/gtest.h>
+
+namespace minikin {
+
+TEST(HasherTest, hasherTest) {
+    EXPECT_EQ(Hasher().hash(), Hasher().hash());
+    EXPECT_EQ(Hasher().update(1).hash(), Hasher().update(1).hash());
+
+    uint16_t shorts1[] = {1, 2, 3, 4, 5};
+    uint16_t shorts2[] = {1, 2, 3, 4, 5};
+    EXPECT_EQ(Hasher().updateShorts(shorts1, 5).hash(), Hasher().updateShorts(shorts2, 5).hash());
+
+    Hasher hasher;
+    hasher.update(1);
+    EXPECT_EQ(hasher.hash(), hasher.hash());
+    hasher.update(2);
+    EXPECT_EQ(hasher.hash(), hasher.hash());
+}
+
+}  // namespace minikin
diff --git a/tests/unittest/LayoutCacheTest.cpp b/tests/unittest/LayoutCacheTest.cpp
index b7ad0ba..e0dd5f3 100644
--- a/tests/unittest/LayoutCacheTest.cpp
+++ b/tests/unittest/LayoutCacheTest.cpp
@@ -29,18 +29,19 @@
 class TestableLayoutCache : public LayoutCache {
 public:
     TestableLayoutCache(uint32_t maxEntries) : LayoutCache(maxEntries) {}
+    using LayoutCache::getCacheSize;
 };
 
 class LayoutCapture {
 public:
     LayoutCapture() {}
 
-    void operator()(const Layout& layout) { mLayout = &layout; }
+    void operator()(const LayoutPiece& layout, const MinikinPaint& /* dir */) { mLayout = &layout; }
 
-    const Layout* get() const { return mLayout; }
+    const LayoutPiece* get() const { return mLayout; }
 
 private:
-    const Layout* mLayout;
+    const LayoutPiece* mLayout;
 };
 
 TEST(LayoutCacheTest, cacheHitTest) {
@@ -198,11 +199,11 @@
         SCOPED_TRACE("Different paint flags");
         auto collection = buildFontCollection("Ascii.ttf");
         MinikinPaint paint1(collection);
-        paint1.paintFlags = 0;
+        paint1.fontFlags = 0;
         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
         MinikinPaint paint2(collection);
-        paint2.paintFlags = LinearTextFlag;
+        paint2.fontFlags = LinearMetrics_Flag;
         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
         EXPECT_NE(layout1.get(), layout2.get());
@@ -224,11 +225,11 @@
         SCOPED_TRACE("Different family variant");
         auto collection = buildFontCollection("Ascii.ttf");
         MinikinPaint paint1(collection);
-        paint1.familyVariant = FontFamily::Variant::DEFAULT;
+        paint1.familyVariant = FamilyVariant::DEFAULT;
         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
         MinikinPaint paint2(collection);
-        paint2.familyVariant = FontFamily::Variant::COMPACT;
+        paint2.familyVariant = FamilyVariant::COMPACT;
         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
         EXPECT_NE(layout1.get(), layout2.get());
@@ -260,7 +261,7 @@
                             EndHyphenEdit::NO_EDIT, layout1);
 
     for (char c = 'a'; c <= 'z'; c++) {
-        auto text1 = utf8ToUtf16(std::string(c, 10));
+        auto text1 = utf8ToUtf16(std::string(10, c));
         LayoutCapture layout2;
         layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
                                 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
@@ -272,4 +273,18 @@
     EXPECT_NE(layout1.get(), layout3.get());
 }
 
+TEST(LayoutCacheTest, cacheLengthLimitTest) {
+    auto text = utf8ToUtf16(std::string(130, 'a'));
+    Range range(0, text.size());
+    MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+    TestableLayoutCache layoutCache(140);
+
+    LayoutCapture layout;
+    layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+                            EndHyphenEdit::NO_EDIT, layout);
+
+    EXPECT_EQ(layoutCache.getCacheSize(), 0u);
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/LayoutCoreTest.cpp b/tests/unittest/LayoutCoreTest.cpp
new file mode 100644
index 0000000..ef972a0
--- /dev/null
+++ b/tests/unittest/LayoutCoreTest.cpp
@@ -0,0 +1,255 @@
+/*
+ * 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.
+ */
+
+#include "minikin/LayoutCore.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/FontCollection.h"
+#include "minikin/LayoutPieces.h"
+
+#include "FontTestUtils.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+namespace {
+
+static LayoutPiece buildLayout(const std::string& text, const MinikinPaint& paint) {
+    auto utf16 = utf8ToUtf16(text);
+    return LayoutPiece(utf16, Range(0, utf16.size()), false /* rtl */, paint,
+                       StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+}
+
+static LayoutPiece buildLayout(const std::string& text, const std::vector<std::string>& fonts) {
+    std::vector<std::shared_ptr<FontFamily>> families;
+    for (const auto& fontPath : fonts) {
+        families.push_back(buildFontFamily(fontPath));
+    }
+    auto fc = std::make_shared<FontCollection>(families);
+    MinikinPaint paint(fc);
+    paint.size = 10.0f;  // make 1em = 10px
+    return buildLayout(text, paint);
+}
+
+static LayoutPiece buildLayout(const std::string& text, const std::vector<std::string>& fonts,
+                               const std::string fontFeaturesSettings) {
+    std::vector<std::shared_ptr<FontFamily>> families;
+    for (const auto& fontPath : fonts) {
+        families.push_back(buildFontFamily(fontPath));
+    }
+    auto fc = std::make_shared<FontCollection>(families);
+    MinikinPaint paint(fc);
+    paint.size = 10.0f;  // make 1em = 10px
+    paint.fontFeatureSettings = fontFeaturesSettings;
+    return buildLayout(text, paint);
+}
+
+TEST(LayoutPieceTest, doLayoutTest) {
+    // The LayoutTestFont.ttf has following coverage, extent, width and bbox.
+    // Ascender: 10em, Descender: -2em
+    // U+0020: 10em, (0, 0) - (10, 10)
+    // U+002E (.): 10em, (0, 0) - (10, 10)
+    // U+0043 (C): 100em, (0, 0) - (100, 100)
+    // U+0049 (I): 1em, (0, 0) - (1, 1)
+    // U+004C (L): 50em, (0, 0) - (50, 50)
+    // U+0056 (V): 5em, (0, 0) - (5, 5)
+    // U+0058 (X): 10em, (0, 0) - (10, 10)
+    // U+005F (_): 0em, (0, 0) - (0, 0)
+    // U+FFFD (invalid surrogate will be replaced to this): 7em, (0, 0) - (7, 7)
+    // U+10331 (\uD800\uDF31): 10em, (0, 0) - (10, 10)
+    {
+        auto layout = buildLayout("I", {"LayoutTestFont.ttf"});
+        EXPECT_EQ(1u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_EQ(1u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(10.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("II", {"LayoutTestFont.ttf"});
+        EXPECT_EQ(2u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_TRUE(layout.fontAt(1).font);
+        EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(10.0f, layout.advances()[1]);
+        EXPECT_EQ(20.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("IV", {"LayoutTestFont.ttf"});
+        EXPECT_EQ(2u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 60.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_TRUE(layout.fontAt(1).font);
+        EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(50.0f, layout.advances()[1]);
+        EXPECT_EQ(60.0f, layout.advance());
+    }
+}
+
+TEST(LayoutPieceTest, doLayoutTest_MultiFont) {
+    // See doLayoutTest for the details of LayoutTestFont.ttf
+    // The Hiragana.ttf has following coverage, extent, width and bbox.
+    // Ascender: 16em, Descender: -4em
+    // U+3042: 2em, (0, 0) - (2, 2)
+    // U+3044: 2em, (0, 0) - (2, 2)
+    // U+3046: 2em, (0, 0) - (2, 2)
+    // U+3048: 2em, (0, 0) - (2, 2)
+    // U+304A: 2em, (0, 0) - (2, 2)
+    {
+        auto layout = buildLayout("I\u3042", {"LayoutTestFont.ttf", "Hiragana.ttf"});
+        EXPECT_EQ(2u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), layout.extent());
+        EXPECT_EQ(2u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_TRUE(layout.fontAt(1).font);
+        EXPECT_NE(layout.fontAt(0), layout.fontAt(1));
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(20.0f, layout.advances()[1]);
+        EXPECT_EQ(30.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("\u3042I", {"LayoutTestFont.ttf", "Hiragana.ttf"});
+        EXPECT_EQ(2u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(Point(20.0f, 0), layout.pointAt(1));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), layout.extent());
+        EXPECT_EQ(2u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_TRUE(layout.fontAt(1).font);
+        EXPECT_NE(layout.fontAt(0), layout.fontAt(1));
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(20.0f, layout.advances()[0]);
+        EXPECT_EQ(10.0f, layout.advances()[1]);
+        EXPECT_EQ(30.0f, layout.advance());
+    }
+}
+
+TEST(LayoutPieceTest, doLayoutTest_Ligature) {
+    // Ligature.ttf support all ASCII characters.
+    // Ascender: 8em, Descender: -2em
+    // U+0020..U+007E: 1em, (0, 0) - (1, 1)
+    // Also this has ligature entry for fi as "ccmp" feature, ff as "liga" feature.
+    {
+        auto layout = buildLayout("fi", {"Ligature.ttf"});
+        EXPECT_EQ(1u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(0.0f, layout.advances()[1]);  // Ligature assigns all width to the first char.
+        EXPECT_EQ(10.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("ff", {"Ligature.ttf"});
+        EXPECT_EQ(1u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(0.0f, layout.advances()[1]);  // Ligature assigns all width to the first char.
+        EXPECT_EQ(10.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("fi", {"Ligature.ttf"}, "'liga' off");
+        EXPECT_EQ(1u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(0.0f, layout.advances()[1]);  // Ligature assigns all width to the first char.
+        EXPECT_EQ(10.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("ff", {"Ligature.ttf"}, "'liga' off");
+        EXPECT_EQ(2u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_TRUE(layout.fontAt(1).font);
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(10.0f, layout.advances()[1]);
+        EXPECT_EQ(20.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("fii", {"Ligature.ttf"});
+        EXPECT_EQ(2u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_TRUE(layout.fontAt(1).font);
+        EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+        EXPECT_EQ(3u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(0.0f, layout.advances()[1]);  // Ligature assigns all width to the first char.
+        EXPECT_EQ(10.0f, layout.advances()[2]);
+        EXPECT_EQ(20.0f, layout.advance());
+    }
+    {
+        auto layout = buildLayout("if", {"Ligature.ttf"});
+        EXPECT_EQ(2u, layout.glyphCount());
+        EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+        EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+        EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+        EXPECT_EQ(1u, layout.fonts().size());
+        EXPECT_TRUE(layout.fontAt(0).font);
+        EXPECT_TRUE(layout.fontAt(1).font);
+        EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+        EXPECT_EQ(2u, layout.advances().size());
+        EXPECT_EQ(10.0f, layout.advances()[0]);
+        EXPECT_EQ(10.0f, layout.advances()[1]);
+        EXPECT_EQ(20.0f, layout.advance());
+    }
+}
+
+}  // namespace
+}  // namespace minikin
diff --git a/tests/unittest/LayoutSplitterTest.cpp b/tests/unittest/LayoutSplitterTest.cpp
new file mode 100644
index 0000000..4b75d9c
--- /dev/null
+++ b/tests/unittest/LayoutSplitterTest.cpp
@@ -0,0 +1,327 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "minikin/FontCollection.h"
+#include "minikin/LayoutPieces.h"
+
+#include "FontTestUtils.h"
+#include "LayoutSplitter.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+namespace {
+
+std::pair<std::vector<uint16_t>, Range> parseTestString(const std::string& text) {
+    auto utf16 = utf8ToUtf16(text);
+    Range range;
+    std::vector<uint16_t> outText;
+    for (uint16_t c : utf16) {
+        switch (c) {
+            case '(':
+                range.setStart(outText.size());
+                break;
+            case ')':
+                range.setEnd(outText.size());
+                break;
+            default:
+                outText.push_back(c);
+        }
+    }
+    return std::make_pair(outText, range);
+}
+
+std::pair<Range, Range> parseExpectString(const std::string& text) {
+    auto utf16 = utf8ToUtf16(text);
+    Range context;
+    Range piece;
+    uint32_t textPos = 0;
+    for (uint16_t c : utf16) {
+        switch (c) {
+            case '[':
+                context.setStart(textPos);
+                break;
+            case ']':
+                context.setEnd(textPos);
+                break;
+            case '(':
+                piece.setStart(textPos);
+                break;
+            case ')':
+                piece.setEnd(textPos);
+                break;
+            default:
+                textPos++;
+        }
+    }
+    return std::make_pair(context, piece);
+}
+
+std::string buildDebugString(const U16StringPiece& textBuf, const Range& context,
+                             const Range& piece) {
+    std::vector<uint16_t> out;
+    out.reserve(textBuf.size() + 4);
+    for (uint32_t i = 0; i < textBuf.size() + 1; ++i) {
+        if (i == context.getStart()) {
+            out.push_back('[');
+        }
+        if (i == piece.getStart()) {
+            out.push_back('(');
+        }
+        if (i == piece.getEnd()) {
+            out.push_back(')');
+        }
+        if (i == context.getEnd()) {
+            out.push_back(']');
+        }
+        if (i != textBuf.size()) {
+            out.push_back(textBuf[i]);
+        }
+    }
+    return utf16ToUtf8(out);
+}
+
+TEST(LayoutSplitterTest, LTR_Latin) {
+    struct TestCase {
+        std::string testStr;
+        std::vector<std::string> expects;
+    } testCases[] = {
+            {"(This is an example text.)",
+             {
+                     "[(This)] is an example text.", "This[( )]is an example text.",
+                     "This [(is)] an example text.", "This is[( )]an example text.",
+                     "This is [(an)] example text.", "This is an[( )]example text.",
+                     "This is an [(example)] text.", "This is an example[( )]text.",
+                     "This is an example [(text.)]",
+             }},
+            {"This( is an example )text.",
+             {
+                     "This[( )]is an example text.", "This [(is)] an example text.",
+                     "This is[( )]an example text.", "This is [(an)] example text.",
+                     "This is an[( )]example text.", "This is an [(example)] text.",
+                     "This is an example[( )]text.",
+             }},
+            {"This (is an example) text.",
+             {
+                     "This [(is)] an example text.", "This is[( )]an example text.",
+                     "This is [(an)] example text.", "This is an[( )]example text.",
+                     "This is an [(example)] text.",
+             }},
+            {"Th(is is an example te)xt.",
+             {
+                     "[Th(is)] is an example text.", "This[( )]is an example text.",
+                     "This [(is)] an example text.", "This is[( )]an example text.",
+                     "This is [(an)] example text.", "This is an[( )]example text.",
+                     "This is an [(example)] text.", "This is an example[( )]text.",
+                     "This is an example [(te)xt.]",
+             }},
+            {"This is an ex(amp)le text.",
+             {
+                     "This is an [ex(amp)le] text.",
+             }},
+            {"There are (three   spaces.)",
+             {
+                     "There are [(three)]   spaces.", "There are three[( )]  spaces.",
+                     "There are three [( )] spaces.", "There are three  [( )]spaces.",
+                     "There are three   [(spaces.)]",
+             }},
+    };
+
+    for (const auto& testCase : testCases) {
+        auto[text, range] = parseTestString(testCase.testStr);
+        uint32_t expectationIndex = 0;
+        for (auto[acContext, acPiece] : LayoutSplitter(text, range, false /* isRtl */)) {
+            ASSERT_NE(expectationIndex, testCase.expects.size());
+            const std::string expectString = testCase.expects[expectationIndex++];
+            auto[exContext, exPiece] = parseExpectString(expectString);
+            EXPECT_EQ(acContext, exContext)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+            EXPECT_EQ(acPiece, exPiece)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+        }
+        EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+    }
+}
+
+TEST(LayoutSplitterTest, RTL_Latin) {
+    struct TestCase {
+        std::string testStr;
+        std::vector<std::string> expects;
+    } testCases[] = {
+            {"(This is an example text.)",
+             {
+                     "This is an example [(text.)]", "This is an example[( )]text.",
+                     "This is an [(example)] text.", "This is an[( )]example text.",
+                     "This is [(an)] example text.", "This is[( )]an example text.",
+                     "This [(is)] an example text.", "This[( )]is an example text.",
+                     "[(This)] is an example text.",
+             }},
+            {"This( is an example )text.",
+             {
+                     "This is an example[( )]text.", "This is an [(example)] text.",
+                     "This is an[( )]example text.", "This is [(an)] example text.",
+                     "This is[( )]an example text.", "This [(is)] an example text.",
+                     "This[( )]is an example text.",
+             }},
+            {"This (is an example) text.",
+             {
+                     "This is an [(example)] text.", "This is an[( )]example text.",
+                     "This is [(an)] example text.", "This is[( )]an example text.",
+                     "This [(is)] an example text.",
+             }},
+            {"Th(is is an example te)xt.",
+             {
+                     "This is an example [(te)xt.]", "This is an example[( )]text.",
+                     "This is an [(example)] text.", "This is an[( )]example text.",
+                     "This is [(an)] example text.", "This is[( )]an example text.",
+                     "This [(is)] an example text.", "This[( )]is an example text.",
+                     "[Th(is)] is an example text.",
+             }},
+            {"This is an ex(amp)le text.",
+             {
+                     "This is an [ex(amp)le] text.",
+             }},
+            {"There are (three   spaces.)",
+             {
+                     "There are three   [(spaces.)]", "There are three  [( )]spaces.",
+                     "There are three [( )] spaces.", "There are three[( )]  spaces.",
+                     "There are [(three)]   spaces.",
+             }},
+    };
+
+    for (const auto& testCase : testCases) {
+        auto[text, range] = parseTestString(testCase.testStr);
+        uint32_t expectationIndex = 0;
+        for (auto[acContext, acPiece] : LayoutSplitter(text, range, true /* isRtl */)) {
+            ASSERT_NE(expectationIndex, testCase.expects.size());
+            const std::string expectString = testCase.expects[expectationIndex++];
+            auto[exContext, exPiece] = parseExpectString(expectString);
+            EXPECT_EQ(acContext, exContext)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+            EXPECT_EQ(acPiece, exPiece)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+        }
+        EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+    }
+}
+
+TEST(LayoutSplitterTest, LTR_CJK) {
+    struct TestCase {
+        std::string testStr;
+        std::vector<std::string> expects;
+    } testCases[] = {
+            {// All Kanji text
+             "(\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776)",
+             {
+                     "[(\u6614)]\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776",
+                     "\u6614[(\u8005)]\u8358\u5468\u5922\u70BA\u80E1\u8776",
+                     "\u6614\u8005[(\u8358)]\u5468\u5922\u70BA\u80E1\u8776",
+                     "\u6614\u8005\u8358[(\u5468)]\u5922\u70BA\u80E1\u8776",
+                     "\u6614\u8005\u8358\u5468[(\u5922)]\u70BA\u80E1\u8776",
+                     "\u6614\u8005\u8358\u5468\u5922[(\u70BA)]\u80E1\u8776",
+                     "\u6614\u8005\u8358\u5468\u5922\u70BA[(\u80E1)]\u8776",
+                     "\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1[(\u8776)]",
+             }},
+            {// Japanese text like as follows
+             // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+             "(\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002)",
+             {
+                     "[(\u672C)]\u65E5\u306F\u6674\u5929\u306A\u308A\u3002",
+                     "\u672C[(\u65E5\u306F)]\u6674\u5929\u306A\u308A\u3002",
+                     "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+                     "\u672C\u65E5\u306F\u6674[(\u5929\u306A\u308A\u3002)]",
+             }},
+            {// Japanese text like as follows
+             // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+             "\u672C\u65E5(\u306F\u6674\u5929\u306A)\u308A\u3002",
+             {
+                     "\u672C[\u65E5(\u306F)]\u6674\u5929\u306A\u308A\u3002",
+                     "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+                     "\u672C\u65E5\u306F\u6674[(\u5929\u306A)\u308A\u3002]",
+             }},
+    };
+
+    for (const auto& testCase : testCases) {
+        auto[text, range] = parseTestString(testCase.testStr);
+        uint32_t expectationIndex = 0;
+        for (auto[acContext, acPiece] : LayoutSplitter(text, range, false /* isRtl */)) {
+            ASSERT_NE(expectationIndex, testCase.expects.size());
+            const std::string expectString = testCase.expects[expectationIndex++];
+            auto[exContext, exPiece] = parseExpectString(expectString);
+            EXPECT_EQ(acContext, exContext)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+            EXPECT_EQ(acPiece, exPiece)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+        }
+        EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+    }
+}
+
+TEST(LayoutSplitterTest, RTL_CJK) {
+    struct TestCase {
+        std::string testStr;
+        std::vector<std::string> expects;
+    } testCases[] = {
+            {// All Kanji text
+             "(\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776)",
+             {
+                     "\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1[(\u8776)]",
+                     "\u6614\u8005\u8358\u5468\u5922\u70BA[(\u80E1)]\u8776",
+                     "\u6614\u8005\u8358\u5468\u5922[(\u70BA)]\u80E1\u8776",
+                     "\u6614\u8005\u8358\u5468[(\u5922)]\u70BA\u80E1\u8776",
+                     "\u6614\u8005\u8358[(\u5468)]\u5922\u70BA\u80E1\u8776",
+                     "\u6614\u8005[(\u8358)]\u5468\u5922\u70BA\u80E1\u8776",
+                     "\u6614[(\u8005)]\u8358\u5468\u5922\u70BA\u80E1\u8776",
+                     "[(\u6614)]\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776",
+             }},
+            {// Japanese text like as follows
+             // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+             "(\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002)",
+             {
+                     "\u672C\u65E5\u306F\u6674[(\u5929\u306A\u308A\u3002)]",
+                     "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+                     "\u672C[(\u65E5\u306F)]\u6674\u5929\u306A\u308A\u3002",
+                     "[(\u672C)]\u65E5\u306F\u6674\u5929\u306A\u308A\u3002",
+             }},
+            {// Japanese text like as follows
+             // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+             "\u672C\u65E5(\u306F\u6674\u5929\u306A)\u308A\u3002",
+             {
+                     "\u672C\u65E5\u306F\u6674[(\u5929\u306A)\u308A\u3002]",
+                     "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+                     "\u672C[\u65E5(\u306F)]\u6674\u5929\u306A\u308A\u3002",
+             }},
+    };
+
+    for (const auto& testCase : testCases) {
+        auto[text, range] = parseTestString(testCase.testStr);
+        uint32_t expectationIndex = 0;
+        for (auto[acContext, acPiece] : LayoutSplitter(text, range, true /* isRtl */)) {
+            ASSERT_NE(expectationIndex, testCase.expects.size());
+            const std::string expectString = testCase.expects[expectationIndex++];
+            auto[exContext, exPiece] = parseExpectString(expectString);
+            EXPECT_EQ(acContext, exContext)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+            EXPECT_EQ(acPiece, exPiece)
+                    << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+        }
+        EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+    }
+}
+
+}  // namespace
+}  // namespace minikin
diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp
index 21109cb..4b97cee 100644
--- a/tests/unittest/LayoutTest.cpp
+++ b/tests/unittest/LayoutTest.cpp
@@ -26,41 +26,13 @@
 
 namespace minikin {
 
-const float UNTOUCHED_MARKER = 1e+38;
-
-static void expectAdvances(std::vector<float> expected, float* advances, size_t length) {
-    EXPECT_LE(expected.size(), length);
+static void expectAdvances(const std::vector<float>& expected, const std::vector<float>& advances) {
+    EXPECT_LE(expected.size(), advances.size());
     for (size_t i = 0; i < expected.size(); ++i) {
         EXPECT_EQ(expected[i], advances[i])
                 << i << "th element is different. Expected: " << expected[i]
                 << ", Actual: " << advances[i];
     }
-    EXPECT_EQ(UNTOUCHED_MARKER, advances[expected.size()]);
-}
-
-static void resetAdvances(float* advances, size_t length) {
-    for (size_t i = 0; i < length; ++i) {
-        advances[i] = UNTOUCHED_MARKER;
-    }
-}
-
-static Layout doLayout(const std::string& text, const MinikinPaint& paint) {
-    Layout layout;
-    auto utf16 = utf8ToUtf16(text);
-    Range range(0, utf16.size());
-    layout.doLayout(utf16, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
-                    EndHyphenEdit::NO_EDIT);
-    return layout;
-}
-
-static Layout doLayoutWithPrecomputedPieces(const std::string& text, const MinikinPaint& paint,
-                                            const LayoutPieces& pieces) {
-    Layout layout;
-    auto utf16 = utf8ToUtf16(text);
-    Range range(0, utf16.size());
-    layout.doLayoutWithPrecomputedPieces(utf16, range, Bidi::FORCE_LTR, paint,
-                                         StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, pieces);
-    return layout;
 }
 
 class LayoutTest : public testing::Test {
@@ -80,11 +52,8 @@
     MinikinPaint paint(mCollection);
     paint.size = 10.0f;  // make 1em = 10px
     MinikinRect rect;
-    const size_t kMaxAdvanceLength = 32;
-    float advances[kMaxAdvanceLength];
     std::vector<float> expectedValues;
 
-    Layout layout;
     std::vector<uint16_t> text;
 
     // The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all glyph.
@@ -92,81 +61,73 @@
         SCOPED_TRACE("one word");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(70.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(70.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("two words");
         text = utf8ToUtf16("two words");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(90.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(90.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("three words");
         text = utf8ToUtf16("three words test");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(160.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(160.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("two spaces");
         text = utf8ToUtf16("two  spaces");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(110.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(110.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
 }
 
@@ -174,13 +135,9 @@
     MinikinPaint paint(mCollection);
     paint.size = 10.0f;  // make 1em = 10px
     MinikinRect rect;
-    const size_t kMaxAdvanceLength = 32;
-    float advances[kMaxAdvanceLength];
     std::vector<float> expectedValues;
     std::vector<uint16_t> text;
 
-    Layout layout;
-
     paint.wordSpacing = 5.0f;
 
     // The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all glyph.
@@ -188,89 +145,78 @@
         SCOPED_TRACE("one word");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(70.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(70.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("two words");
         text = utf8ToUtf16("two words");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(95.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(95.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
-        EXPECT_EQ(UNTOUCHED_MARKER, advances[text.size()]);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
         expectedValues[3] = 15.0f;
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("three words test");
         text = utf8ToUtf16("three words test");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(170.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(170.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
         expectedValues[5] = 15.0f;
         expectedValues[11] = 15.0f;
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("two spaces");
         text = utf8ToUtf16("two  spaces");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(120.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(120.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
         expectedValues[3] = 15.0f;
         expectedValues[4] = 15.0f;
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
 }
 
@@ -278,11 +224,8 @@
     MinikinPaint paint(mCollection);
     paint.size = 10.0f;  // make 1em = 10px
     MinikinRect rect;
-    const size_t kMaxAdvanceLength = 32;
-    float advances[kMaxAdvanceLength];
     std::vector<float> expectedValues;
 
-    Layout layout;
     std::vector<uint16_t> text;
 
     // Negative word spacing also should work.
@@ -292,86 +235,78 @@
         SCOPED_TRACE("one word");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(70.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(70.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("two words");
         text = utf8ToUtf16("two words");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(85.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(85.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
         expectedValues[3] = 5.0f;
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("three words");
         text = utf8ToUtf16("three word test");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(140.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(140.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
         expectedValues[5] = 5.0f;
         expectedValues[10] = 5.0f;
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
     {
         SCOPED_TRACE("two spaces");
         text = utf8ToUtf16("two  spaces");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(100.0f, layout.getAdvance());
         layout.getBounds(&rect);
         EXPECT_EQ(0.0f, rect.mLeft);
         EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(100.0f, rect.mRight);
         EXPECT_EQ(0.0f, rect.mBottom);
-        resetAdvances(advances, kMaxAdvanceLength);
-        layout.getAdvances(advances);
         expectedValues.resize(text.size());
         for (size_t i = 0; i < expectedValues.size(); ++i) {
             expectedValues[i] = 10.0f;
         }
         expectedValues[3] = 5.0f;
         expectedValues[4] = 5.0f;
-        expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+        expectAdvances(expectedValues, layout.getAdvances());
     }
 }
 
@@ -382,13 +317,11 @@
     std::vector<uint16_t> text = parseUnicodeString("'a' 'b' U+3042 U+3043 'c' 'd'");
     Range range(0, text.size());
 
-    Layout ltrLayout;
-    ltrLayout.doLayout(text, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
-                       EndHyphenEdit::NO_EDIT);
+    Layout ltrLayout(text, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
+                     EndHyphenEdit::NO_EDIT);
 
-    Layout rtlLayout;
-    rtlLayout.doLayout(text, range, Bidi::FORCE_RTL, paint, StartHyphenEdit::NO_EDIT,
-                       EndHyphenEdit::NO_EDIT);
+    Layout rtlLayout(text, range, Bidi::FORCE_RTL, paint, StartHyphenEdit::NO_EDIT,
+                     EndHyphenEdit::NO_EDIT);
 
     ASSERT_EQ(ltrLayout.nGlyphs(), rtlLayout.nGlyphs());
     ASSERT_EQ(6u, ltrLayout.nGlyphs());
@@ -407,17 +340,14 @@
     std::vector<uint16_t> text = parseUnicodeString("'1' '2' '3'");
     Range range(0, text.size());
 
-    Layout ltrLayout;
-    ltrLayout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                       EndHyphenEdit::NO_EDIT);
+    Layout ltrLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                     EndHyphenEdit::NO_EDIT);
 
-    Layout rtlLayout;
-    rtlLayout.doLayout(text, range, Bidi::RTL, paint, StartHyphenEdit::NO_EDIT,
-                       EndHyphenEdit::NO_EDIT);
+    Layout rtlLayout(text, range, Bidi::RTL, paint, StartHyphenEdit::NO_EDIT,
+                     EndHyphenEdit::NO_EDIT);
 
-    Layout defaultRtlLayout;
-    defaultRtlLayout.doLayout(text, range, Bidi::DEFAULT_RTL, paint, StartHyphenEdit::NO_EDIT,
-                              EndHyphenEdit::NO_EDIT);
+    Layout defaultRtlLayout(text, range, Bidi::DEFAULT_RTL, paint, StartHyphenEdit::NO_EDIT,
+                            EndHyphenEdit::NO_EDIT);
 
     const size_t nGlyphs = ltrLayout.nGlyphs();
     ASSERT_EQ(3u, nGlyphs);
@@ -436,7 +366,6 @@
 TEST_F(LayoutTest, hyphenationTest) {
     MinikinPaint paint(mCollection);
     paint.size = 10.0f;  // make 1em = 10px
-    Layout layout;
     std::vector<uint16_t> text;
 
     // The mock implementation returns 10.0f advance for all glyphs.
@@ -444,62 +373,44 @@
         SCOPED_TRACE("one word with no hyphen edit");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(70.0f, layout.getAdvance());
     }
     {
         SCOPED_TRACE("one word with hyphen insertion at the end");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::INSERT_HYPHEN);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::INSERT_HYPHEN);
         EXPECT_EQ(80.0f, layout.getAdvance());
     }
     {
         SCOPED_TRACE("one word with hyphen replacement at the end");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                        EndHyphenEdit::REPLACE_WITH_HYPHEN);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                      EndHyphenEdit::REPLACE_WITH_HYPHEN);
         EXPECT_EQ(70.0f, layout.getAdvance());
     }
     {
         SCOPED_TRACE("one word with hyphen insertion at the start");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
-                        EndHyphenEdit::NO_EDIT);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
+                      EndHyphenEdit::NO_EDIT);
         EXPECT_EQ(80.0f, layout.getAdvance());
     }
     {
         SCOPED_TRACE("one word with hyphen insertion at the both ends");
         text = utf8ToUtf16("oneword");
         Range range(0, text.size());
-        layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
-                        EndHyphenEdit::INSERT_HYPHEN);
+        Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
+                      EndHyphenEdit::INSERT_HYPHEN);
         EXPECT_EQ(90.0f, layout.getAdvance());
     }
 }
 
-TEST_F(LayoutTest, verticalExtentTest) {
-    MinikinPaint paint(mCollection);
-
-    std::vector<uint16_t> text = utf8ToUtf16("ab");
-    Range range(0, text.size());
-
-    Layout layout;
-    layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                    EndHyphenEdit::NO_EDIT);
-    MinikinExtent extents[text.size()];
-    layout.getExtents(extents);
-    for (size_t i = 0; i < text.size(); i++) {
-        EXPECT_EQ(-10.0f, extents[i].ascent);
-        EXPECT_EQ(20.0f, extents[i].descent);
-        EXPECT_EQ(0.0f, extents[i].line_gap);
-    }
-}
-
 TEST_F(LayoutTest, measuredTextTest) {
     // The test font has following coverage and width.
     // U+0020: 10em
@@ -518,9 +429,8 @@
         std::vector<uint16_t> text = utf8ToUtf16("I");
         std::vector<float> advances(text.size());
         Range range(0, text.size());
-        EXPECT_EQ(1.0f,
-                  Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                                      EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+        EXPECT_EQ(1.0f, Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                                            EndHyphenEdit::NO_EDIT, advances.data()));
         ASSERT_EQ(1u, advances.size());
         EXPECT_EQ(1.0f, advances[0]);
     }
@@ -529,9 +439,8 @@
         std::vector<uint16_t> text = utf8ToUtf16("IV");
         std::vector<float> advances(text.size());
         Range range(0, text.size());
-        EXPECT_EQ(6.0f,
-                  Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                                      EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+        EXPECT_EQ(6.0f, Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                                            EndHyphenEdit::NO_EDIT, advances.data()));
         ASSERT_EQ(2u, advances.size());
         EXPECT_EQ(1.0f, advances[0]);
         EXPECT_EQ(5.0f, advances[1]);
@@ -543,7 +452,7 @@
         Range range(0, text.size());
         EXPECT_EQ(16.0f,
                   Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
-                                      EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+                                      EndHyphenEdit::NO_EDIT, advances.data()));
         ASSERT_EQ(3u, advances.size());
         EXPECT_EQ(1.0f, advances[0]);
         EXPECT_EQ(5.0f, advances[1]);
@@ -551,66 +460,6 @@
     }
 }
 
-TEST_F(LayoutTest, doLayoutWithPrecomputedPiecesTest) {
-    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), paint, false /* dir */,
-                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
-
-        Layout outLayout = doLayoutWithPrecomputedPieces("I", MinikinPaint(fc), pieces);
-        EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
-    }
-    {
-        LayoutPieces pieces;
-
-        Layout inLayout = doLayout("I", MinikinPaint(fc));
-        inLayout.mAdvances[0] = MARKER1;
-        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".
-        EXPECT_NE(MARKER1, outLayout.mAdvances[0]);
-        EXPECT_NE(MARKER1, outLayout.mAdvances[1]);
-    }
-    {
-        LayoutPieces pieces;
-
-        Layout inLayout = doLayout("I", MinikinPaint(fc));
-        inLayout.mAdvances[0] = MARKER1;
-        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]);
-        EXPECT_EQ(MARKER1, outLayout.mAdvances[2]);
-    }
-    {
-        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), 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), paint, false /* dir */,
-                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
-
-        Layout outLayout = doLayoutWithPrecomputedPieces("I V", MinikinPaint(fc), pieces);
-        EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
-        EXPECT_EQ(MARKER2, outLayout.mAdvances[2]);
-    }
-}
-
 // TODO: Add more test cases, e.g. measure text, letter spacing.
 
 }  // namespace minikin
diff --git a/tests/unittest/LayoutUtilsTest.cpp b/tests/unittest/LayoutUtilsTest.cpp
index 4c18cce..79aa01e 100644
--- a/tests/unittest/LayoutUtilsTest.cpp
+++ b/tests/unittest/LayoutUtilsTest.cpp
@@ -29,7 +29,7 @@
     size_t size = 0U;
 
     ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
-    EXPECT_EQ(expected_breakpoint, getNextWordBreakForCache(buf, offset_in, size))
+    EXPECT_EQ(expected_breakpoint, getNextWordBreakForCache(U16StringPiece(buf, size), offset_in))
             << "Expected position is [" << query_str << "] from offset " << offset_in;
 }
 
@@ -40,7 +40,7 @@
     size_t size = 0U;
 
     ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
-    EXPECT_EQ(expected_breakpoint, getPrevWordBreakForCache(buf, offset_in, size))
+    EXPECT_EQ(expected_breakpoint, getPrevWordBreakForCache(U16StringPiece(buf, size), offset_in))
             << "Expected position is [" << query_str << "] from offset " << offset_in;
 }
 
diff --git a/tests/unittest/LineBreakerTestHelper.h b/tests/unittest/LineBreakerTestHelper.h
index 70d6521..9d973d5 100644
--- a/tests/unittest/LineBreakerTestHelper.h
+++ b/tests/unittest/LineBreakerTestHelper.h
@@ -33,8 +33,6 @@
 
     float getAt(size_t) const override { return mWidth; }
     float getMin() const override { return mWidth; }
-    float getLeftPaddingAt(size_t) const override { return 0; }
-    float getRightPaddingAt(size_t) const override { return 0; }
 
 private:
     float mWidth;
@@ -43,18 +41,24 @@
 // The run implemenataion for returning the same width for all characters.
 class ConstantRun : public Run {
 public:
-    ConstantRun(const Range& range, const std::string& lang, float width)
-            : Run(range), mPaint(nullptr /* font collection */), mWidth(width) {
+    ConstantRun(const Range& range, const std::string& lang, float width, float ascent,
+                float descent)
+            : Run(range),
+              mPaint(nullptr /* font collection */),
+              mWidth(width),
+              mAscent(ascent),
+              mDescent(descent) {
         mLocaleListId = LocaleListCache::getId(lang);
     }
 
     virtual bool isRtl() const override { return false; }
-    virtual bool canHyphenate() const override { return true; }
+    virtual bool canBreak() const override { return true; }
     virtual uint32_t getLocaleListId() const { return mLocaleListId; }
 
-    virtual void getMetrics(const U16StringPiece&, float* advances, MinikinExtent*,
+    virtual void getMetrics(const U16StringPiece&, std::vector<float>* advances, LayoutPieces*,
                             LayoutPieces*) const {
-        std::fill(advances, advances + mRange.getLength(), mWidth);
+        std::fill(advances->begin() + mRange.getStart(), advances->begin() + mRange.getEnd(),
+                  mWidth);
     }
 
     virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
@@ -63,10 +67,15 @@
         return std::make_pair(mWidth, MinikinRect());
     }
 
+    virtual MinikinExtent getExtent(const U16StringPiece& /* text */, const Range& /* range */,
+                                    const LayoutPieces& /* pieces */) const override {
+        return {mAscent, mDescent};
+    }
+
     virtual const MinikinPaint* getPaint() const { return &mPaint; }
 
     virtual float measureHyphenPiece(const U16StringPiece&, const Range& range,
-                                     StartHyphenEdit start, EndHyphenEdit end, float*,
+                                     StartHyphenEdit start, EndHyphenEdit end,
                                      LayoutPieces*) const {
         uint32_t extraCharForHyphen = 0;
         if (isInsertion(start)) {
@@ -78,21 +87,25 @@
         return mWidth * (range.getLength() + extraCharForHyphen);
     }
 
+    virtual void appendLayout(const U16StringPiece&, const Range&, const Range&,
+                              const LayoutPieces&, const MinikinPaint&, uint32_t, StartHyphenEdit,
+                              EndHyphenEdit, Layout*) const {}
+
 private:
     MinikinPaint mPaint;
     uint32_t mLocaleListId;
     float mWidth;
+    float mAscent;
+    float mDescent;
 };
 
 struct LineBreakExpectation {
-    LineBreakExpectation(const std::string& lineContent, float width, StartHyphenEdit startEdit,
-                         EndHyphenEdit endEdit)
-            : mLineContent(lineContent), mWidth(width), mStartEdit(startEdit), mEndEdit(endEdit){};
-
     std::string mLineContent;
     float mWidth;
     StartHyphenEdit mStartEdit;
     EndHyphenEdit mEndEdit;
+    float mAscent;
+    float mDescent;
 };
 
 static bool sameLineBreak(const std::vector<LineBreakExpectation>& expected,
@@ -134,6 +147,12 @@
         if (expected[i].mEndEdit != endHyphenEdit(edit)) {
             return false;
         }
+        if (expected[i].mAscent != actual.ascents[i]) {
+            return false;
+        }
+        if (expected[i].mDescent != actual.descents[i]) {
+            return false;
+        }
     }
     return true;
 }
@@ -146,8 +165,9 @@
 
         char lineMsg[128] = {};
         snprintf(lineMsg, sizeof(lineMsg),
-                 "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Text: \"%s\"\n", i, line.mWidth,
-                 line.mStartEdit, line.mEndEdit, line.mLineContent.c_str());
+                 "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Extent(%5.1f, %5.1f), Text: \"%s\"\n",
+                 i, line.mWidth, line.mStartEdit, line.mEndEdit, line.mAscent, line.mDescent,
+                 line.mLineContent.c_str());
         out += lineMsg;
     }
     return out;
@@ -172,8 +192,9 @@
         }
         char lineMsg[128] = {};
         snprintf(lineMsg, sizeof(lineMsg),
-                 "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Text: \"%s\"\n", i, lines.widths[i],
-                 startEdit, endEdit, hyphenatedStr.c_str());
+                 "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Extent(%5.1f, %5.1f), Text: \"%s\"\n",
+                 i, lines.widths[i], startEdit, endEdit, lines.ascents[i], lines.descents[i],
+                 hyphenatedStr.c_str());
         out += lineMsg;
     }
     return out;
diff --git a/tests/unittest/MeasuredTextTest.cpp b/tests/unittest/MeasuredTextTest.cpp
index e8ed408..1934ed8 100644
--- a/tests/unittest/MeasuredTextTest.cpp
+++ b/tests/unittest/MeasuredTextTest.cpp
@@ -45,7 +45,8 @@
     std::vector<uint16_t> text(CHAR_COUNT, 'a');
 
     std::unique_ptr<MeasuredText> measuredText =
-            builder.build(text, true /* compute hyphenation */, false /* compute full layout */);
+            builder.build(text, true /* compute hyphenation */, false /* compute full layout */,
+                          nullptr /* no hint */);
 
     ASSERT_TRUE(measuredText);
 
@@ -56,4 +57,410 @@
     EXPECT_EQ(expectedWidths, measuredText->widths);
 }
 
+TEST(MeasuredTextTest, getBoundsTest) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    EXPECT_EQ(MinikinRect(0.0f, 0.0f, 0.0f, 0.0f), mt->getBounds(text, Range(0, 0)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(0, 1)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), mt->getBounds(text, Range(0, 2)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(1, 2)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 130.0f, 0.0f), mt->getBounds(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, getBoundsTest_multiStyle) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+    uint32_t helloLength = 7;  // length of "Hello, "
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+    MinikinPaint paint2(font);
+    paint2.size = 20.0f;
+    builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    EXPECT_EQ(MinikinRect(0.0f, 0.0f, 0.0f, 0.0f), mt->getBounds(text, Range(0, 0)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(0, 1)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), mt->getBounds(text, Range(0, 2)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(1, 2)));
+    EXPECT_EQ(MinikinRect(0.0f, 0.0f, 0.0f, 0.0f), mt->getBounds(text, Range(7, 7)));
+    EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), mt->getBounds(text, Range(7, 8)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), mt->getBounds(text, Range(6, 8)));
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 190.0f, 0.0f), mt->getBounds(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, getExtentTest) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    EXPECT_EQ(MinikinExtent(0.0f, 0.0f), mt->getExtent(text, Range(0, 0)));
+    EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 1)));
+    EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 2)));
+    EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(1, 2)));
+    EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, getExtentTest_multiStyle) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+    uint32_t helloLength = 7;  // length of "Hello, "
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+    MinikinPaint paint2(font);
+    paint2.size = 20.0f;
+    builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    EXPECT_EQ(MinikinExtent(0.0f, 0.0f), mt->getExtent(text, Range(0, 0)));
+    EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 1)));
+    EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 2)));
+    EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(1, 2)));
+    EXPECT_EQ(MinikinExtent(0.0f, 0.0f), mt->getExtent(text, Range(7, 7)));
+    EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), mt->getExtent(text, Range(7, 8)));
+    EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), mt->getExtent(text, Range(6, 8)));
+    EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), mt->getExtent(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, buildLayoutTest) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+    Range fullContext(0, text.size());
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    MinikinPaint samePaint(font);
+    samePaint.size = 10.0f;
+
+    Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, samePaint,
+                                    StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    EXPECT_EQ(0u, layout.nGlyphs());
+
+    layout = mt->buildLayout(text, Range(0, 1), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(10.0f, layout.getAdvance());
+    EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(0, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(2u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(10.0f, layout.getX(1));
+    EXPECT_EQ(0.0f, layout.getY(1));
+    EXPECT_EQ(20.0f, layout.getAdvance());
+    EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(10.0f, layout.getCharAdvance(1));
+    EXPECT_EQ(2u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(1, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(10.0f, layout.getAdvance());
+    EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(0, text.size()), fullContext, samePaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(text.size(), layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    for (uint32_t i = 0; i < text.size(); ++i) {
+        EXPECT_EQ(layout.getFont(0), layout.getFont(i)) << i;
+        EXPECT_EQ(10.0f * i, layout.getX(i)) << i;
+        EXPECT_EQ(0.0f, layout.getY(i)) << i;
+        EXPECT_EQ(10.0f, layout.getCharAdvance(i)) << i;
+    }
+    EXPECT_EQ(130.0f, layout.getAdvance());
+    EXPECT_EQ(text.size(), layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 130.0f, 0.0f), layout.getBounds());
+}
+
+TEST(MeasuredTextTest, buildLayoutTest_multiStyle) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+    uint32_t helloLength = 7;  // length of "Hello, "
+    Range fullContext(0, text.size());
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+    MinikinPaint paint2(font);
+    paint2.size = 20.0f;
+    builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    MinikinPaint samePaint(font);
+    samePaint.size = 10.0f;
+
+    Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, samePaint,
+                                    StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    EXPECT_EQ(0u, layout.nGlyphs());
+
+    layout = mt->buildLayout(text, Range(0, 1), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(10.0f, layout.getAdvance());
+    EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(0, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(2u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(10.0f, layout.getX(1));
+    EXPECT_EQ(0.0f, layout.getY(1));
+    EXPECT_EQ(20.0f, layout.getAdvance());
+    EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(10.0f, layout.getCharAdvance(1));
+    EXPECT_EQ(2u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(1, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(10.0f, layout.getAdvance());
+    EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(7, 7), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    EXPECT_EQ(0u, layout.nGlyphs());
+
+    MinikinPaint samePaint2(font);
+    samePaint2.size = 20.0f;
+    layout = mt->buildLayout(text, Range(7, 8), fullContext, samePaint2, StartHyphenEdit::NO_EDIT,
+                             EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(20.0f, layout.getAdvance());
+    EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds());
+}
+
+TEST(MeasuredTextTest, buildLayoutTest_differentPaint) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+    Range fullContext(0, text.size());
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    MinikinPaint differentPaint(font);
+    differentPaint.size = 20.0f;
+
+    Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, differentPaint,
+                                    StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    EXPECT_EQ(0u, layout.nGlyphs());
+
+    layout = mt->buildLayout(text, Range(0, 1), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(20.0f, layout.getAdvance());
+    EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(0, 2), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(2u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(20.0f, layout.getX(1));
+    EXPECT_EQ(0.0f, layout.getY(1));
+    EXPECT_EQ(40.0f, layout.getAdvance());
+    EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(20.0f, layout.getCharAdvance(1));
+    EXPECT_EQ(2u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 20.0f, 40.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(1, 2), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(20.0f, layout.getAdvance());
+    EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(0, text.size()), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(text.size(), layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    for (uint32_t i = 0; i < text.size(); ++i) {
+        EXPECT_EQ(layout.getFont(0), layout.getFont(i)) << i;
+        EXPECT_EQ(20.0f * i, layout.getX(i)) << i;
+        EXPECT_EQ(0.0f, layout.getY(i)) << i;
+        EXPECT_EQ(20.0f, layout.getCharAdvance(i)) << i;
+    }
+    EXPECT_EQ(260.0f, layout.getAdvance());
+    EXPECT_EQ(text.size(), layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 20.0f, 260.0f, 0.0f), layout.getBounds());
+}
+
+TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) {
+    auto text = utf8ToUtf16("Hello, World!");
+    auto font = buildFontCollection("Ascii.ttf");
+    uint32_t helloLength = 7;  // length of "Hello, "
+    Range fullContext(0, text.size());
+
+    MeasuredTextBuilder builder;
+    MinikinPaint paint(font);
+    paint.size = 10.0f;
+    builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+    MinikinPaint paint2(font);
+    paint2.size = 20.0f;
+    builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+    auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+                            nullptr /* no hint */);
+
+    MinikinPaint differentPaint(font);
+    differentPaint.size = 30.0f;
+
+    Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, differentPaint,
+                                    StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    EXPECT_EQ(0u, layout.nGlyphs());
+
+    layout = mt->buildLayout(text, Range(0, 1), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(30.0f, layout.getAdvance());
+    EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(0, 2), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(2u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(30.0f, layout.getX(1));
+    EXPECT_EQ(0.0f, layout.getY(1));
+    EXPECT_EQ(60.0f, layout.getAdvance());
+    EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(30.0f, layout.getCharAdvance(1));
+    EXPECT_EQ(2u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(1, 2), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(30.0f, layout.getAdvance());
+    EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(7, 7), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    EXPECT_EQ(0u, layout.nGlyphs());
+
+    layout = mt->buildLayout(text, Range(7, 8), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(1u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(30.0f, layout.getAdvance());
+    EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(1u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(6, 8), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(2u, layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+    EXPECT_EQ(0.0f, layout.getX(0));
+    EXPECT_EQ(0.0f, layout.getY(0));
+    EXPECT_EQ(30.0f, layout.getX(1));
+    EXPECT_EQ(0.0f, layout.getY(1));
+    EXPECT_EQ(60.0f, layout.getAdvance());
+    EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+    EXPECT_EQ(30.0f, layout.getCharAdvance(1));
+    EXPECT_EQ(2u, layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), layout.getBounds());
+
+    layout = mt->buildLayout(text, Range(0, text.size()), fullContext, differentPaint,
+                             StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+    ASSERT_EQ(text.size(), layout.nGlyphs());
+    EXPECT_TRUE(layout.getFont(0));
+    for (uint32_t i = 0; i < text.size(); ++i) {
+        EXPECT_EQ(layout.getFont(0), layout.getFont(i)) << i;
+        EXPECT_EQ(30.0f * i, layout.getX(i)) << i;
+        EXPECT_EQ(0.0f, layout.getY(i)) << i;
+        EXPECT_EQ(30.0f, layout.getCharAdvance(i)) << i;
+    }
+    EXPECT_EQ(390.0f, layout.getAdvance());
+    EXPECT_EQ(text.size(), layout.getAdvances().size());
+    EXPECT_EQ(MinikinRect(0.0f, 30.0f, 390.0f, 0.0f), layout.getBounds());
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp
index a4479ad..ef1f6a9 100644
--- a/tests/unittest/OptimalLineBreakerTest.cpp
+++ b/tests/unittest/OptimalLineBreakerTest.cpp
@@ -39,6 +39,14 @@
 using line_breaker_test_helper::sameLineBreak;
 using line_breaker_test_helper::toString;
 
+// The ascent/descent of Ascii.ttf with text size = 10.
+constexpr float ASCENT = -80.0f;
+constexpr float DESCENT = 20.0f;
+
+// The ascent/descent of CustomExtent.ttf with text size = 10.
+constexpr float CUSTOM_ASCENT = -160.0f;
+constexpr float CUSTOM_DESCENT = 40.0f;
+
 class OptimalLineBreakerTest : public testing::Test {
 public:
     OptimalLineBreakerTest() {}
@@ -57,17 +65,25 @@
 
 protected:
     LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
-                                HyphenationFrequency frequency, float charWidth, float lineWidth) {
-        return doLineBreak(textBuffer, strategy, frequency, charWidth, "en-US", lineWidth);
+                                HyphenationFrequency frequency, float lineWidth) {
+        return doLineBreak(textBuffer, strategy, frequency, "en-US", lineWidth);
     }
 
     LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
-                                HyphenationFrequency frequency, float charWidth,
-                                const std::string& lang, float lineWidth) {
+                                HyphenationFrequency frequency, const std::string& lang,
+                                float lineWidth) {
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, textBuffer.size()), lang, charWidth);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuffer, true /* compute hyphenation */, false /* compute full layout */);
+        auto family1 = buildFontFamily("Ascii.ttf");
+        auto family2 = buildFontFamily("CustomExtent.ttf");
+        std::vector<std::shared_ptr<FontFamily>> families = {family1, family2};
+        auto fc = std::make_shared<FontCollection>(families);
+        MinikinPaint paint(fc);
+        paint.size = 10.0f;  // Make 1em=1px
+        paint.localeListId = LocaleListCache::getId(lang);
+        builder.addStyleRun(0, textBuffer.size(), std::move(paint), false);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuffer, true /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         return doLineBreak(textBuffer, *measuredText, strategy, frequency, lineWidth);
     }
 
@@ -84,7 +100,6 @@
 };
 
 TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
     constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
     constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
@@ -96,724 +111,724 @@
     constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
     // Note that disable clang-format everywhere since aligned expectation is more readable.
     {
-        constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 1000;
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 240;
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 230;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is an example " , 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."               ,  5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an example " , 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."               ,  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
 
         // clang-format off
         expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an ex-" , 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ample text."    , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an ex-" , 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ample text."    , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 170;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an exam-" , 16 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple text."        ,  9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an exam-" , 160, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple text."        ,  90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ample text."   , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ample text."   , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 16 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 160;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an exam-" , 16 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple text."        ,  9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an exam-" , 160, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple text."        ,  90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ample text."   , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ample text."   , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 15 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 150;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ample text."   , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ample text."   , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ample text."   , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ample text."   , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 130;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is an "   , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an "   , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 120;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This is an ", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example "   ,  7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."      ,  5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is an ", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example "   ,  70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."      ,  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is " ,  7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an exam-" ,  8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple text.",  9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is " ,  70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an exam-" ,  80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple text.",  90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 9 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 90;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This "   , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an "  , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This "   , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an "  , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an exam-" , 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple text.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This "   , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an "  , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This "   , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an "  , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an exam-" , 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple text.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 80;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This "   , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an "  , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This "   , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an "  , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an ex-"  , 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ample "  , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an ex-"  , 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ample "  , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 70;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This "   , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an "  , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This "   , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an "  , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an ex-"  , 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ample "  , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text."   , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an ex-"  , 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ample "  , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text."   , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 60;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "exa"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "exa"   , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "exam-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple "  , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "exam-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple "  , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 50;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "exa"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "exa"   , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "exam-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple "  , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "exam-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple "  , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 40;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "exa"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text"  , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "."     , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "exa"   , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text"  , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "."     , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an "   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an "   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "exa"   , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "exa"   , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "t"     , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ext."  , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "t"     , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ext."  , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an "  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ex-"  , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "am-"  , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "text" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "."    , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an "  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ex-"  , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "am-"  , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple " , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "text" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "."    , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "This ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is "  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an "  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ex-"  , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "am-"  , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is "  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an "  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ex-"  , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "am-"  , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple " , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "te"   , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xt."  , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "te"   , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xt."  , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 30;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
                 // TODO: Is this desperate break working correctly?
-                { "T"   , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "T"   , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "e"   , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xam" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t."  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "e"   , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xam" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "tex" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t."  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
                 // TODO: Is this desperate break working correctly?
-                { "T"   , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "T"   , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "e"   , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xam" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "e"   , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xam" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "te"  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "te"  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
                 // TODO: Is this desperate break working correctly?
-                { "T"   , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
-                { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t."  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "T"   , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ex-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "am-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+                { "ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "tex" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t."  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
                 // TODO: Is this desperate break working correctly?
-                {"T"   , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN},
-                {"am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN},
-                {"ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"T"   , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ex-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+                {"am-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+                {"ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
                 // TODO: Is this desperate break working correctly?
-                {"te"  , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"te"  , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 20;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Th" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "e"  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xa" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "mp" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "le ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xt" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "."  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "e"  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xa" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "mp" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "le ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "te" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xt" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "."  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "Th" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "an ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "e"  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "xa" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "mp" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "le ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "e"  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "xa" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "mp" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "le ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
                 // TODO: Is this desperate break working correctly?
-                { "t"  , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "ex" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "t"  , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "ex" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 10;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "m" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "l" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
-                { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+                { "T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "m" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "l" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+                { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -821,7 +836,6 @@
 }
 
 TEST_F(OptimalLineBreakerTest, testHyphenationStartLineChange) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
     constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
     // "hyphenation" is hyphnated to "hy-phen-a-tion".
@@ -833,40 +847,40 @@
 
     // Note that disable clang-format everywhere since aligned expectation is more readable.
     {
-        constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 1000;
         std::vector<LineBreakExpectation> expect = {
-                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
-                                        LINE_WIDTH);
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 18 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 180;
         std::vector<LineBreakExpectation> expect = {
-                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
-        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
-                                        LINE_WIDTH);
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 130;
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                {"czerwono-" ,  9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"-niebieska", 10 * CHAR_WIDTH,    START_HYPHEN, NO_END_HYPHEN},
+                {"czerwono-" ,  90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"-niebieska", 100,    START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
 
-        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
-                                        LINE_WIDTH);
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -874,10 +888,9 @@
 }
 
 TEST_F(OptimalLineBreakerTest, testZeroWidthLine) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
     constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
-    constexpr float LINE_WIDTH = 0 * CHAR_WIDTH;
+    constexpr float LINE_WIDTH = 0;
 
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
@@ -885,8 +898,7 @@
     {
         const auto textBuf = utf8ToUtf16("");
         std::vector<LineBreakExpectation> expect = {};
-        const auto actual =
-                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -894,10 +906,9 @@
     {
         const auto textBuf = utf8ToUtf16("A");
         std::vector<LineBreakExpectation> expect = {
-                {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual =
-                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -905,11 +916,10 @@
     {
         const auto textBuf = utf8ToUtf16("AB");
         std::vector<LineBreakExpectation> expect = {
-                {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"B", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"B", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
-        const auto actual =
-                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -927,10 +937,17 @@
         constexpr float LINE_WIDTH = 1.0;
         const auto textBuf = utf8ToUtf16("This is an example text.");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, true /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+
         const auto actual =
-                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+                doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -939,10 +956,17 @@
         constexpr float LINE_WIDTH = 0.0;
         const auto textBuf = utf8ToUtf16("This is an example text.");
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, true /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+
         const auto actual =
-                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+                doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -957,18 +981,20 @@
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
 
-    constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+    constexpr float LINE_WIDTH = 240;
     const auto textBuf = utf8ToUtf16("This is an example text.");
     {
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuf, true /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, true /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
 
         const auto actual =
                 doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
@@ -978,14 +1004,16 @@
     }
     {
         std::vector<LineBreakExpectation> expect = {
-                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
 
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measuredText = builder.build(
-                textBuf, true /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, true /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
         const auto actual =
                 doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
@@ -995,7 +1023,6 @@
 }
 
 TEST_F(OptimalLineBreakerTest, testEmailOrUrl) {
-    constexpr float CHAR_WIDTH = 10.0;
     constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
     constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
     constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
@@ -1004,61 +1031,61 @@
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
     {
-        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 240;
         const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
                 // TODO: Fix this. Prefer not to break inside URL.
-                {"This is an url: http://a", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {".b",                        2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an url: http://a", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".b",                        20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
         // clang-format off
         expect = {
-                {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"http://a.b",       10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an url: ", 150, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"http://a.b",       100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
     }
     {
-        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        constexpr float LINE_WIDTH = 240;
         const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"a@example.com"     , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a@example.com"     , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
 
-        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
-        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
         EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
                                                    << " vs " << std::endl
                                                    << toString(textBuf, actual);
@@ -1075,20 +1102,22 @@
     constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
     constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
 
-    constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+    constexpr float LINE_WIDTH = 240;
     {
         const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measured = builder.build(
-                textBuf, true /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measured =
+                builder.build(textBuf, true /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
 
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
                 // TODO: Fix this. Prefer not to break inside URL.
-                {"This is an url: http://a", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {".b",                        2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an url: http://a", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".b",                        20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
 
@@ -1103,8 +1132,8 @@
 
         // clang-format off
         expect = {
-                {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"http://a.b",       10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an url: ", 150, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"http://a.b",       100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
 
@@ -1120,15 +1149,17 @@
     {
         const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
         MeasuredTextBuilder builder;
-        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
-        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
-        std::unique_ptr<MeasuredText> measured = builder.build(
-                textBuf, true /* compute hyphenation */, false /* compute full layout */);
+        builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+        std::unique_ptr<MeasuredText> measured =
+                builder.build(textBuf, true /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
 
         // clang-format off
         std::vector<LineBreakExpectation> expect = {
-                {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
-                {"a@example.com",      13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+                {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a@example.com",      130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
         };
         // clang-format on
 
@@ -1151,5 +1182,828 @@
     }
 }
 
+TEST_F(OptimalLineBreakerTest, ExtentTest) {
+    constexpr HyphenationFrequency NO_HYPHEN = HyphenationFrequency::None;
+    const std::vector<uint16_t> textBuf = utf8ToUtf16("The \u3042\u3044\u3046 is Japanese.");
+
+    constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    {
+        constexpr float LINE_WIDTH = 1000;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+                 CUSTOM_ASCENT, CUSTOM_DESCENT},
+        };
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 200;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+                 CUSTOM_ASCENT, CUSTOM_DESCENT},
+        };
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 190;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042\u3044\u3046 is ", 100, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3044\u3046 is ", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 50;
+        std::vector<LineBreakExpectation> expect = {
+                {"The \u3042", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3044\u3046 is ", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"Japan", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ese.", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 40;
+        std::vector<LineBreakExpectation> expect = {
+                {"The ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u3042\u3044", 20, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3046 is ", 40, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"Japa", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"nese", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 20;
+        std::vector<LineBreakExpectation> expect = {
+                {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"he ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u3042", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3044\u3046 ", 20, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+                 CUSTOM_DESCENT},
+                {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"Ja", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"pa", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ne", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"se", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        std::vector<LineBreakExpectation> expect = {
+                {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u3042", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3044", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"\u3046 ", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+                {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"J", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"n", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_SingleChar) {
+    constexpr float CHAR_WIDTH = 10.0;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    const auto textBuf = utf8ToUtf16("This is an example \u2639 text.");
+
+    // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 19), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(19, 21, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(21, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+                                BreakStrategy::HighQuality, HyphenationFrequency::None,
+                                false /* justified */);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 100;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an ",   100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ",       70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u2639 text.",  100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",   40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an ",  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ",70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u2639 ", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"text.",   50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"T",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"n ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"m",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"l",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u2639 ",50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_MultipleChars) {
+    constexpr float CHAR_WIDTH = 10.0;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    const auto textBuf = utf8ToUtf16("This is an example text.");
+
+    // In this test case, assign a replacement run for "is an " with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 5), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(5, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+                                BreakStrategy::HighQuality, HyphenationFrequency::None,
+                                false /* justified */);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 100;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an ",   100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ",       70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",          50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",   40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an ",  50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"example ",70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",   50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"T",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"m",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"l",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_CJK) {
+    constexpr float CHAR_WIDTH = 10.0;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    // Example string: "Today is a sunny day." in Japanese.
+    const auto textBuf = utf8ToUtf16("\u672C\u65E5\u306F\u6674\u5929\u306A\u308A");
+
+    // In this test case, assign a replacement run for "\u6674\u5929" with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 3), "ja-JP", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(3, 5, 5 * CHAR_WIDTH, LocaleListCache::getId("ja-JP"));
+        builder.addCustomRun<ConstantRun>(Range(5, textBuf.size()), "ja-JP", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+                                BreakStrategy::HighQuality, HyphenationFrequency::None,
+                                false /* justified */);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 100;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F\u6674\u5929\u306A\u308A",
+                  100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 90;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F\u6674\u5929\u306A",
+                  90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u308A",
+                  10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 80;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F\u6674\u5929",
+                  80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u306A\u308A",
+                  20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 70;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929\u306A\u308A", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 60;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929\u306A", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u308A",             10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 50;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929",       50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u306A\u308A",       20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 40;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929",       50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u306A\u308A",       20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "\u6674\u5929" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"\u672C",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u65E5",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u306F",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u306A",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u308A",       10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+// http://b/119657685
+// Following test case is for verifying that the ReplacementSpan should not be broken into multiple
+// pieces. The actual break point is not a part of expectation. For example, it would be good to
+// break the starting offset of the ReplacementSpan for some case.
+TEST_F(OptimalLineBreakerTest, testReplacementSpan_GraphemeLineBreakWithMultipleRepalcementSpans) {
+    constexpr float CHAR_WIDTH = 10.0;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    const auto textBuf = utf8ToUtf16("ab de\u00A0\u00A0fg ij\u00A0\u00A0kl no\u00A0\u00A0pq st");
+
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addReplacementRun(0, 5, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(5, 7), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(7, 12, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(12, 14), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(14, 19, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(19, 21), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(21, 26, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+                                BreakStrategy::HighQuality, HyphenationFrequency::None,
+                                false /* justified */);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 1000;
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"ab de\u00A0\u00A0fg ij\u00A0\u00A0kl no\u00A0\u00A0pq st",
+                  260, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 250;
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"ab de\u00A0\u00A0fg ij\u00A0\u00A0kl no\u00A0\u00A0",
+                  210, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"pq st",
+                   50, NO_START_HYPHEN, NO_END_HYPHEN,       0,      0},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 180;
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"ab de\u00A0\u00A0fg ij\u00A0\u00A0",
+                  140, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"kl no\u00A0\u00A0pq st",
+                  120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 130;
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"ab de\u00A0\u00A0fg ij\u00A0",
+                  130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u00A0kl no\u00A0\u00A0pq st",
+                  130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 110;
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"ab de\u00A0",             60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u00A0fg ij\u00A0\u00A0", 80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"kl no\u00A0\u00A0",       70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"pq st",                   50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 60;
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"ab de\u00A0",  60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u00A0fg ij",  60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"kl no\u00A0",  60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u00A0pq st",  60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 50;
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"ab de",        50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"fg ij",        50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"kl no",        50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"pq st",        50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_with_punctuation) {
+    constexpr float CHAR_WIDTH = 10.0;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    const auto textBuf = utf8ToUtf16("This (is an) example text.");
+
+    // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+    auto doLineBreak = [=](float width) {
+        MeasuredTextBuilder builder;
+        builder.addCustomRun<ConstantRun>(Range(0, 6), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+        builder.addReplacementRun(6, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+        builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT,
+                                          DESCENT);
+
+        std::unique_ptr<MeasuredText> measuredText =
+                builder.build(textBuf, false /* compute hyphenation */,
+                              false /* compute full layout */, nullptr /* no hint */);
+        RectangleLineWidth rectangleLineWidth(width);
+        TabStops tabStops(nullptr, 0, 0);
+        return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+                                BreakStrategy::HighQuality, HyphenationFrequency::Normal,
+                                false /* justified */);
+    };
+
+    {
+        constexpr float LINE_WIDTH = 1000;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) example text.",
+                  260, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 250;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) example ", 200, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",                  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 190;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example text.", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 120;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ",       70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",          50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 110;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",    40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(is an) ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",    50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 60;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",  40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {") ex",   40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ample ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 50;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",  40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"(",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"is an",  50, NO_START_HYPHEN, NO_END_HYPHEN,      0,       0},
+                {") ex",   40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"ample ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text.",  50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 40;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"This ",  40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                // TODO(nona): This might be wrongly broken. "(is an" should be broken into "(" and
+                // "is an" as the desperate break.
+                {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {") ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"exa",    30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"mple ",  40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"text",   40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10;
+        // "is an" is a single replacement span. Do not break.
+        // clang-format off
+        std::vector<LineBreakExpectation> expect = {
+                {"T",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"h",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"i",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"s ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                // TODO(nona): This might be wrongly broken. "(is an" should be broken into "(" and
+                // "is an" as the desperate break.
+                {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {") ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"a",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"m",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"p",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"l",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e ",     10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"e",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"x",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"t",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {".",      10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+        // clang-format on
+        const auto actual = doLineBreak(LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testControllCharAfterSpace) {
+    constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+    constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
+    constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
+    const std::vector<uint16_t> textBuf = utf8ToUtf16("example \u2066example");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    {
+        constexpr float LINE_WIDTH = 90;
+        // Note that HarfBuzz assigns 0px for control characters regardless of glyph existence in
+        // the font.
+        std::vector<LineBreakExpectation> expect = {
+                {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+                {"\u2066example", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+        };
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
 }  // namespace
 }  // namespace minikin
diff --git a/tests/unittest/SystemFontsTest.cpp b/tests/unittest/SystemFontsTest.cpp
new file mode 100644
index 0000000..fe603a9
--- /dev/null
+++ b/tests/unittest/SystemFontsTest.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#include "minikin/SystemFonts.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/FontCollection.h"
+
+#include "FontTestUtils.h"
+
+namespace minikin {
+namespace {
+
+class TestableSystemFonts : public SystemFonts {
+public:
+    TestableSystemFonts() : SystemFonts() {}
+    virtual ~TestableSystemFonts() {}
+
+    std::shared_ptr<FontCollection> findFontCollection(const std::string& familyName) const {
+        return findFontCollectionInternal(familyName);
+    }
+
+    void registerFallback(const std::string& familyName,
+                          const std::shared_ptr<FontCollection>& fc) {
+        registerFallbackInternal(familyName, fc);
+    }
+
+    void registerDefault(const std::shared_ptr<FontCollection>& fc) { registerDefaultInternal(fc); }
+};
+
+TEST(SystemFontsTest, registerAndLookup) {
+    TestableSystemFonts systemFonts;
+    auto fc = buildFontCollection("Ascii.ttf");
+    systemFonts.registerFallback("sans", fc);
+    EXPECT_EQ(fc, systemFonts.findFontCollection("sans"));
+}
+
+TEST(SystemFontsTest, registerDefaultAndLookup) {
+    TestableSystemFonts systemFonts;
+    auto fc = buildFontCollection("Ascii.ttf");
+    systemFonts.registerDefault(fc);
+    EXPECT_EQ(fc, systemFonts.findFontCollection("unknown-name"));
+}
+
+TEST(SystemFontsTest, registerDefaultAndFallback) {
+    TestableSystemFonts systemFonts;
+    auto fc1 = buildFontCollection("Ascii.ttf");
+    auto fc2 = buildFontCollection("Bold.ttf");
+    systemFonts.registerDefault(fc1);
+    systemFonts.registerFallback("sans", fc2);
+    EXPECT_EQ(fc1, systemFonts.findFontCollection("unknown-name"));
+    EXPECT_EQ(fc2, systemFonts.findFontCollection("sans"));
+}
+
+}  // namespace
+}  // namespace minikin
diff --git a/tests/util/Android.bp b/tests/util/Android.bp
index 561643b..8bf125b 100644
--- a/tests/util/Android.bp
+++ b/tests/util/Android.bp
@@ -9,7 +9,7 @@
     ],
     cflags: ["-Wall", "-Werror"],
     export_include_dirs: ["."],
-    shared_libs: ["libxml2"],
+    shared_libs: ["libxml2", "libft2"],
     static_libs: ["libminikin"],
     header_libs: ["libminikin-headers-for-tests"],
 }
diff --git a/tests/util/FontTestUtils.cpp b/tests/util/FontTestUtils.cpp
index 8aaac3d..4143c04 100644
--- a/tests/util/FontTestUtils.cpp
+++ b/tests/util/FontTestUtils.cpp
@@ -58,12 +58,12 @@
         }
 
         xmlChar* variantXmlch = xmlGetProp(familyNode, (const xmlChar*)"variant");
-        FontFamily::Variant variant = FontFamily::Variant::DEFAULT;
+        FamilyVariant variant = FamilyVariant::DEFAULT;
         if (variantXmlch) {
             if (xmlStrcmp(variantXmlch, (const xmlChar*)"elegant") == 0) {
-                variant = FontFamily::Variant::ELEGANT;
+                variant = FamilyVariant::ELEGANT;
             } else if (xmlStrcmp(variantXmlch, (const xmlChar*)"compact") == 0) {
-                variant = FontFamily::Variant::COMPACT;
+                variant = FamilyVariant::COMPACT;
             }
         }
 
@@ -109,7 +109,8 @@
             family = std::make_shared<FontFamily>(variant, std::move(fonts));
         } else {
             uint32_t langId = registerLocaleList(std::string((const char*)lang, xmlStrlen(lang)));
-            family = std::make_shared<FontFamily>(langId, variant, std::move(fonts));
+            family = std::make_shared<FontFamily>(langId, variant, std::move(fonts),
+                                                  false /* isCustomFallback */);
         }
         families.push_back(family);
     }
@@ -128,12 +129,13 @@
     return std::make_shared<FontFamily>(std::move(fonts));
 }
 
-std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang) {
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang,
+                                            bool isCustomFallback) {
     auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath));
     std::vector<Font> fonts;
     fonts.push_back(Font::Builder(font).build());
-    return std::make_shared<FontFamily>(LocaleListCache::getId(lang), FontFamily::Variant::DEFAULT,
-                                        std::move(fonts));
+    return std::make_shared<FontFamily>(LocaleListCache::getId(lang), FamilyVariant::DEFAULT,
+                                        std::move(fonts), isCustomFallback);
 }
 
 }  // namespace minikin
diff --git a/tests/util/FontTestUtils.h b/tests/util/FontTestUtils.h
index ba85093..660438b 100644
--- a/tests/util/FontTestUtils.h
+++ b/tests/util/FontTestUtils.h
@@ -59,7 +59,16 @@
 /**
  * Build new FontFamily from single file with locale.
  */
-std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang);
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang,
+                                            bool isCustomFallback);
+
+/**
+ * Build new FontFamily from single file with locale.
+ */
+inline std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath,
+                                                   const std::string& lang) {
+    return buildFontFamily(filePath, lang, false /* isCustomFallback */);
+}
 
 }  // namespace minikin
 #endif  // MINIKIN_FONT_TEST_UTILS_H
diff --git a/tests/util/FreeTypeMinikinFontForTest.cpp b/tests/util/FreeTypeMinikinFontForTest.cpp
index 7f70772..1ea0631 100644
--- a/tests/util/FreeTypeMinikinFontForTest.cpp
+++ b/tests/util/FreeTypeMinikinFontForTest.cpp
@@ -29,7 +29,10 @@
 #include <log/log.h>
 #include FT_OUTLINE_H
 
+#include "minikin/MinikinExtent.h"
 #include "minikin/MinikinFont.h"
+#include "minikin/MinikinPaint.h"
+#include "minikin/MinikinRect.h"
 
 namespace minikin {
 namespace {
@@ -104,13 +107,11 @@
     bounds->mBottom = FTPosToFloat(bbox.yMin);
 }
 
-void FreeTypeMinikinFontForTest::GetFontExtent(MinikinExtent* extent,
-                                               const MinikinPaint& /* paint */,
+void FreeTypeMinikinFontForTest::GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint,
                                                const FontFakery& /* fakery */) const {
-    // TODO: Retrieve font metrics from FreeType.
-    extent->ascent = -10.0f;
-    extent->descent = 20.0f;
-    extent->line_gap = 0.0f;
+    float upem = mFtFace->units_per_EM;
+    extent->ascent = -static_cast<float>(mFtFace->ascender) * paint.size / upem;
+    extent->descent = -static_cast<float>(mFtFace->descender) * paint.size / upem;
 }
 
 }  // namespace minikin
diff --git a/tests/util/FreeTypeMinikinFontForTest.h b/tests/util/FreeTypeMinikinFontForTest.h
index d63b2bf..4b6ea05 100644
--- a/tests/util/FreeTypeMinikinFontForTest.h
+++ b/tests/util/FreeTypeMinikinFontForTest.h
@@ -17,6 +17,8 @@
 #ifndef MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
 #define MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
 
+#include <string>
+
 #include "minikin/MinikinFont.h"
 
 #include <ft2build.h>